Full Transcript

Tema 31. Sistemas de control de versiones de código fuente. Arquitecturas de almacenamiento. Flujos de trabajo. Uso de ramas. Los sistemas de control de versiones de código fuente son herramientas que permiten a los desarrolladores de software registrar y controlar los cambios en el código fuente d...

Tema 31. Sistemas de control de versiones de código fuente. Arquitecturas de almacenamiento. Flujos de trabajo. Uso de ramas. Los sistemas de control de versiones de código fuente son herramientas que permiten a los desarrolladores de software registrar y controlar los cambios en el código fuente de un proyecto de software. Estos sistemas son esenciales en el desarrollo de software colaborativo, ya que permiten a múltiples desarrolladores trabajar en el mismo código fuente sin perder el seguimiento de las modificaciones realizadas por cada uno de ellos. Con un SCV se puede: - Mantener el histórico de todos los cambios realizados sobre archivos y carpetas a lo largo del tiempo. Esto permite volver a cualquier punto del pasado en cualquier momento - Comparar un punto del tiempo con otro para conocer los cambios exactos que se han producido entre ambos. - Conocer el autor de cada cambio y facilitan mucho el trabajo en paralelo de varias personas en varias características. Existen diferentes sistemas de control de versiones de código fuente disponibles en el mercado, entre los cuales se destacan: - Git: Es uno de los sistemas de control de versiones más populares y utilizados en la actualidad. Fue creado para el desarrollo del kernel de Linux. - Subversion (SVN): Es un sistema de control de versiones centralizado. Aunque ha perdido popularidad frente a Git, sigue siendo utilizado en muchos proyectos. - Mercurial: Es otro sistema de control de versiones distribuido que se utiliza en proyectos de software libre y de código abierto. - Perforce: Es un sistema de control de versiones comercial que se utiliza principalmente en proyectos de gran envergadura. Los sistemas de control de versiones de código fuente pueden utilizar diferentes **arquitecturas de almacenamiento** para guardar los archivos de código fuente y los metadatos asociados con ellos. A continuación, se describen algunas de las arquitecturas de almacenamiento más comunes utilizadas en estos sistemas: [Sistemas de Control de Versiones Locales] Un método de control de versiones, usado por muchas personas, es copiar los archivos a otro directorio (quizás indicando la fecha y hora en que lo hicieron, si son ingeniosos). Este método es muy común porque es muy sencillo, pero también es tremendamente propenso a errores. Es fácil olvidar en qué directorio te encuentras y guardar accidentalmente en el archivo equivocado o sobrescribir archivos que no querías. Para afrontar este problema los programadores desarrollaron hace tiempo VCS locales que contenían una simple base de datos, en la que se llevaba el registro de todos los cambios realizados a los archivos. Diagrama Descripción generada automáticamente [Sistemas de Control de Versiones Centralizados] El siguiente gran problema con el que se encuentran las personas es que necesitan colaborar con desarrolladores en otros sistemas. Los sistemas de Control de Versiones Centralizados fueron desarrollados para solucionar este problema. Estos sistemas, como Subversion y Perforce, tienen un único servidor que contiene todos los archivos versionados y varios clientes que descargan los archivos desde ese lugar central. Este ha sido el estándar para el control de versiones por muchos años. ![Interfaz de usuario gráfica, Diagrama, Aplicación Descripción generada automáticamente](media/image2.png) Ventajas: - Todas las personas saben hasta cierto punto en qué están trabajando los otros colaboradores del proyecto. - Los administradores tienen control detallado sobre qué puede hacer cada usuario. - Es mucho más fácil administrar un CVCS que tener que lidiar con bases de datos locales en cada cliente. Desventajas: - El servidor centralizado cae, entonces durante ese tiempo nadie podrá colaborar o guardar cambios en archivos en los que hayan estado trabajando. - Si el disco duro en el que se encuentra la base de datos central se corrompe, y no se han realizado copias de seguridad adecuadamente, se perderá toda la información del proyecto, con excepción de las copias instantáneas que las personas tengan en sus máquinas locales. - Los VCS locales sufren de este mismo problema: Cuando tienes toda la historia del proyecto en un mismo lugar, te arriesgas a perderlo todo. [Sistemas de Control de Versiones Distribuidos] Los sistemas de Control de Versiones Distribuidos ofrecen soluciones para los problemas que han sido mencionados. En un DVCS (como Git, Mercurial, Bazaar o Darcs), los clientes no solo descargan la última copia instantánea de los archivos, sino que se replica completamente el repositorio. De esta manera, si un servidor deja de funcionar y estos sistemas estaban colaborando a través de él, cualquiera de los repositorios disponibles en los clientes puede ser copiado al servidor con el fin de restaurarlo. Cada clon es realmente una copia completa de todos los datos. Diagrama Descripción generada automáticamente En estos sistemas de control de versiones se puede trabajar sin necesidad de conectarse a la red. **Flujos de trabajo** El flujo de trabajo de un sistema de control de versiones indica cómo se relacionan los distintos usuarios para colaborar entre sí en la consecución de los objetivos del proyecto. En los sistemas centralizados cada desarrollador es un nodo de trabajo más o menos en igualdad con respecto al repositorio central. Los sistemas distribuidos cada desarrollador es potencialmente un nodo o un repositorio - es decir, cada desarrollador puede contribuir a otros repositorios y mantener un repositorio público en el cual otros pueden basar su trabajo y al cual pueden contribuir. [Flujo de trabajo centralizado] En sistemas centralizados, habitualmente solo hay un modelo de colaboración - el flujo de trabajo centralizado. Un repositorio o punto central que acepta código y todos sincronizan su trabajo con él. Unos cuantos desarrolladores son nodos de trabajo - consumidores de dicho repositorio - y sincronizan con ese punto. Esto significa que, si dos desarrolladores clonan desde el punto central, y ambos hacen cambios, solo el primer desarrollador en subir sus cambios lo podrá hacer sin problemas. El segundo desarrollador debe fusionar el trabajo del primero antes de subir sus cambios, para no sobrescribir los cambios del primero. En un sistema de control de versiones distribuido también se puede trabajar de este modo configurando un único repositorio. [Flujo de trabajo con un Gestor de Integraciones] En este flujo de trabajo cada desarrollador tiene acceso de escritura a su propio repositorio público y acceso de lectura a los repositorios de todos los demás. Habitualmente, este escenario suele incluir un repositorio canónico, representante \"oficial\" del proyecto. Para contribuir en este tipo de proyecto, crearás tu propio clon público del mismo y enviarás (push) tus cambios a este. Después, enviarás una petición a la persona gestora del proyecto principal, para que recupere y consolide (pull) en él tus cambios. Ella podrá añadir tu repositorio como un remoto, chequear tus cambios localmente, fusionarlos (merge) con su rama y enviarlos (push) de vuelta a su repositorio. El proceso funciona de la siguiente manera: - La persona gestora del proyecto enviar (push) a su repositorio público (repositorio principal). - Una persona que desea contribuir clona dicho repositorio y hace algunos cambios. - La persona colaboradora envía (commit) a su propia copia pública. - Esta persona colaboradora envía a la gestora un correo electrónico solicitándole recupere e integre los cambios. - La gestora añade como remoto el repositorio de la colaboradora y fusiona (merge) los cambios localmente. - La gestora envía (push) los cambios fusionados al repositorio principal. La principal ventaja de esta forma de trabajar es que puedes continuar trabajando, y la persona gestora del repositorio principal podrá recuperar (pull) tus cambios en cualquier momento. Las personas colaboradoras no tienen por qué esperar a que sus cambios sean incorporados al proyecto (cada cual puede trabajar a su propio ritmo). [Flujo de trabajo con Dictador y Tenientes] Es una variante del flujo de trabajo con múltiples repositorios. Se utiliza generalmente en proyectos muy grandes, con cientos de colaboradores. Unos gestores de integración se encargan de partes concretas del repositorio; y se denominan tenientes. Todos los tenientes rinden cuentas a un gestor de integración; conocido como el dictador benevolente. El repositorio del dictador benevolente es el repositorio de referencia, del que recuperan (pull) todos los colaboradores. El proceso funciona como sigue: - Los desarrolladores habituales trabajan cada uno sobre su repositorio y reorganizan (rebase) su trabajo desde el repositorio \"oficial\". Este repositorio \"oficial\" es del dictador benevolente. - Los tenienentes fusionan (merge) los repositorios de los desarrolladores sobre su propio repositorio. - El dictador fusiona los repositorios de los tenientes en su propio repositorio de referencia para permitir que los desarrolladores reorganicen (rebase) sus repositorios desde el repositorio \"oficial\". Esta manera de trabajar no es muy habitual, pero es muy útil en proyectos muy grandes o en organizaciones fuertemente jerarquizadas. Permite a la persona integradora principal delegar gran parte del trabajo; recolectando el fruto de múltiples puntos de trabajo antes de integrarlo en el proyecto. **Uso de ramas** Cualquier sistema de control de versiones moderno tiene algún mecanismo para soportar el uso de ramas. Cuando hablamos de ramificaciones, significa que se ha tomado la rama principal de desarrollo (master) y a partir de ahí se ha continuado trabajando sin seguir la rama principal de desarrollo. [Ramas de largo recorrido] En algunos proyectos se tienen varias ramas siempre abiertas, que indican diversos grados de estabilidad del contenido. Por ejemplo, en la rama 'master' es frecuente mantener únicamente lo que es totalmente estable. Luego se tienen otras ramas que revelan distintos grados de estabilidad. Por ejemplo, podríamos tener una rama \'beta\' (versión beta) y otra \'alfa\' (versión alfa), en las que se va trabajando. Cuando se alcanza cierto grado de estabilidad superior a la rama en la que se está entonces se realiza una fusión con rama de estabilidad superior. [Ramas puntuales] Las ramas puntuales, también llamadas ramas de soporte, son ramas que se crean de forma puntual para realizar una funcionalidad muy concreta. Por ejemplo, añadir una nueva característica (se les llama ramas de nueva funcionalidad) o corregir un fallo concreto (se les llama ramas para corregir error). Este tipo de ramas permiten trabajar centrándonos exclusivamente en el desarrollo de una característica concreta y cuando esta esté concluida se fusiona con una de las ramas de largo recorrido (normalmente con la de más bajo nivel de estabilidad, para que sea probada en ese entorno). La fusión solo se realiza cuando se está \'seguro\' de que esa característica está correctamente implementada, en lugar de fusionar en el orden que se van desarrollando las cosas. Esto permite por un lado tener un historial de las distintas versiones que se han tenido hasta conseguir la funcionalidad. Por otro lado, permiten que el historial de las ramas de largo recorrido no sea \'ensuciados\' con distintas modificaciones relativas a una funcionalidad concreta. El uso de este tipo de ramas permite más flexibilidad a la hora de probar posibles soluciones. Solo se fusiona con ramas de largo recorrido una vez que estamos seguros de elegir la solución mejor. Un tipo de ramas de este tipo que tienen un funcionamiento especial son las llamadas ramas de versión o ramas de release. Este tipo de ramas se crean para dar soporte a la preparación de una nueva versión de producción. Permiten tener bajo control el contenido de la versión y poder realizar cierto mantenimiento sobre ella (añadirle pequeñas mejoras y corrección de errores). Es frecuente y una buena práctica utilizar en el nombre de la rama un prefijo que indique el tipo de rama de la que se trata. Por ejemplo, podría usar 'feature-' para ramas de nueva funcionalidad, 'hotfix-' para ramas que arreglan errores, y 'release-' para ramas de versión. **GIT** [[https://git-scm.com/book/es/v2/Inicio\-\--Sobre-el-Control-de-Versiones-Una-breve-historia-de-Git]](https://git-scm.com/book/es/v2/Inicio---Sobre-el-Control-de-Versiones-Una-breve-historia-de-Git) [[https://aulab.es/articulos-guias-avanzadas/96/resolver-un-merge-conflict-en-git]](https://aulab.es/articulos-guias-avanzadas/96/resolver-un-merge-conflict-en-git) [[https://aulab.es/articulos-guias-avanzadas/61/repository-en-git]](https://aulab.es/articulos-guias-avanzadas/61/repository-en-git) Características: - Copias instantáneas, no diferencias: - Casi todas las operaciones son locales: - Git tiene integridad: - Git generalmente solo añade información: Cuando realizas acciones en Git, casi todas ellas sólo añaden información a la base de datos de Git. Es muy difícil conseguir que el sistema haga algo que no se pueda enmendar, o que de algún modo borre información. Como en cualquier VCS, puedes perder o estropear cambios que no has confirmado todavía. Pero después de confirmar una copia instantánea en Git es muy difícil perderla. - Las tres secciones principales del proyecto GIT: Git tiene tres estados principales en los que se pueden encontrar tus archivos: confirmado (committed), modificado (modified), y preparado (staged) dependiendo en qué secciones del proyecto GIT se encuentren: El directorio de Git (Git directory), el directorio de trabajo (working directory), y el área de preparación (staging area). - El directorio de Git es donde se almacenan los metadatos y la base de datos de objetos para tu proyecto. Es la parte más importante de Git, y es lo que se copia cuando clonas un repositorio desde otra computadora. - El directorio de trabajo es una copia de una versión del proyecto. Estos archivos se sacan de la base de datos comprimida en el directorio de Git, y se colocan en disco para que los puedas usar o modificar. - El área de preparación es un archivo, generalmente contenido en tu directorio de Git, que almacena información acerca de lo que va a ir en tu próxima confirmación. A veces se le denomina índice ("index"), pero se está convirtiendo en estándar el referirse a ella como el área de preparación. **Repository en Git** Un repository Git rastrea y guarda el historial de cambios realizados en los archivos en el directory donde se inicializó el repository. Hay dos modos para obtener un repository Git: - Inicializar el repository en un directory que actualmente no está bajo control de versiones (git init). Un directory que no está bajo control de versiones es un directorio normal del sistema de archivos. Para convertirlo en un directorio bajo GIT nos colocamos en ese directorio y ejecutamos "git init". Esto crea un subdirectorio nuevo llamado.git, el cual contiene todos los archivos necesarios del repositorio. Si deseas empezar a controlar versiones de archivos existentes (a diferencia de un directorio vacío), probablemente deberías comenzar el seguimiento de esos archivos y hacer una confirmación inicial. Puedes conseguirlo con unos pocos comandos git add para especificar qué archivos quieres controlar, seguidos de un git commit para confirmar los cambios: \$ git add \*.c \$ git add LICENSE \$ git commit -m \'initial project version\' Además, se crea de forma automática una rama llamada rama "master". - Clonar un repository existente (git clone): Cada versión de cada archivo de la historia del proyecto es descargada por defecto cuando ejecutas git clone. ![](media/image6.png) - - - - **Directorio de trabajo** Se puede pedir a GIT (directa o indirectamente) que extraiga un commit específico del historial en este directorio. Podremos hacer cambios en los archivos extraídos del snapshot (commit) o agregamos/eliminamos/movemos/copiamos algunos. Git puede establecer qué archivos han cambiado en el directorio de trabajo en comparación con el commit del que se extrajo el directorio de trabajo. En particular, los archivos en el directorio de trabajo pueden encontrarse en los siguientes estados: - No modificado - el archivo local y el snapshot del que se extrajo tienen el mismo contenido. - Sin rastrear - el archivo local no está presente en el snapshot. - Modificado - el archivo local contiene modificaciones respecto al snapshot de donde se extrajo. **Área de preparación** El área de preparación en Git es el lugar \"virtual\" para agregar los cambios presentes en el directorio de trabajo que se desea guardar como un commit. El área de preparación en Git es donde se almacenan los cambios que formarán parte de la próxima confirmación. Es posible marcar tanto archivos completos como partes individuales de cambios en un archivo. Los archivos en este área estarán en estado: preparado. **Operaciones básicas en GIT** [Operaciones en GIT] Diagrama Descripción generada automáticamente **git add** El comando git add añade un cambio del directorio de trabajo al entorno de stage. Indica a Git que quieres incluir actualizaciones en uno o varios archivos en el próximo commit, pero este trabajo aún sigue en tu máquina. Puedes añadir tus archivos uno por uno, todos a la vez e incluso especificar reglas: \# Añadir todos los archivos git add. \# Añadir un archivo concreto git add \[filename\] \# Añadir todos los archivos omitiendo los nuevos git add \--all \# Añadir todos los archivos con una extensión específica git add \*.txt \# Añadir todos los archivos dentro de un directorio git add docs/ \# Añadir todos los archivos dentro de un directorio y con una extensión específica git add docs/\*.txt **git commit** El comando git commit captura una instantánea de los cambios preparados en ese momento del proyecto (los que hayamos añadido con git add anteriormente). Las instantáneas confirmadas pueden considerarse como versiones «seguras» de un proyecto: Git no las cambiará nunca a no ser que se lo pidas expresamente. Los commits son puntos de control a través de los cuales podemos viajar para ir a versiones anteriores en nuestro desarrollo. Lo que se comitea aún no está subido al repositorio remoto. Su sintaxis es: git commit -m \"Descriptive text for this commit\" Podemos añadir los ficheros modificados y hacer el commit en un único paso del siguiente modo: git commit -a -m \"Descriptive text for this commit\" \# ó git commit -am \"Descriptive text for this commit\" **git push** Este comando se utiliza para subir nuestros commits al repositorio remoto y compartirlos con el resto del equipo. Su sintaxis es: \# Push de tus commits la primera vez, si quieres enviar tu rama al repositorio remoto git push -u origin \[branch\_name\] \# Push \"normal\" git push \[remote\] \[branch\_name\] Los argumentos remote y branch son opcionales, git ya tiene la información del remoto y de la rama actual. Sin embargo, son obligatorios la primera vez (además habrá que utilizar el flag -u) que vayas a realizar un push del repositorio, o de una rama que hayas creado en local, de este modo creará la misma rama en el repositorio remoto. **git pull** El comando git pull se emplea para extraer y descargar contenido desde un repositorio remoto y actualizar al instante el repositorio local para reflejar ese contenido. git pull El comando git pull es, en realidad, una combinación de dos comandos, git fetch seguido de git merge. En la primera etapa de la operación git pull ejecutará un git fetch en la rama local a la que apunta HEAD. Una vez descargado el contenido, git pull entrará en un flujo de trabajo de fusión. Se creará una nueva confirmación de fusión y se actualizará HEAD para que apunte a la nueva confirmación. **git status** Para determinar qué archivos están en qué estado. **git log** Para acceder al histórico de commits utilizaremos el comando git log **git branch** Se utiliza para crear una rama nueva, que apuntará a la instantánea de la rama donde nos encontráramos. git branch \[nombre\_rama\] Si tenemos solo una rama máster, la situación es la siguiente: ![Gráfico en cascada Descripción generada automáticamente con confianza baja](media/image8.png) (98ca9, 34ac2, f30ab son diferentes versiones del proyectos confirmadas) ¿Qué sucede cuando creas una nueva rama? Bueno...​, simplemente se crea un nuevo apuntador para que lo puedas mover libremente. Por ejemplo, supongamos que quieres crear una rama nueva denominada \"testing\". Para ello, usarás el comando git branch: \$ git branch testing Esto creará un nuevo apuntador apuntando a la misma confirmación donde estés actualmente. Diagrama Descripción generada automáticamente ![Diagrama Descripción generada automáticamente](media/image10.png) Para saltar de una rama a otra, tienes que utilizar el comando git checkout. Hagamos una prueba, saltando a la rama testing recién creada: \$ git checkout testing (\*) Es importante destacar que cuando saltas a una rama en Git, los archivos de tu directorio de trabajo cambian. Si saltas a una rama antigua, tu directorio de trabajo retrocederá para verse como lo hacía la última vez que confirmaste un cambio en dicha rama. Si Git no puede hacer el cambio limpiamente, no te dejará saltar. Se podría haber hecho: \$ git checkout -b testing Branch+checkout Esto mueve el apuntador HEAD a la rama testing. Y además actualiza los archivos en el directorio de trabajo para que coincidan con la versión almacenada en esa rama y le indica a Git que a partir de ahora registre todos los commit nuevos en esa rama. Diagrama Descripción generada automáticamente **git checkout** Con este comando se puede movernos a una rama. Con el comando anterior solo creamos la rama, pero no nos movimos a ella. Para empezar a trabajar en una rama nueva después de crearla nos debemos mover a la rama y a partir de ahí, crear nuevas instantáneas. git checkout \[nombre\_rama\] También nos podemos mover a un commit que no sea el actual. git checkout \[nombre\_commit\] Si ahora hacemos una confirmación de unos cambios: ![Diagrama Descripción generada automáticamente](media/image12.png) Observamos algo interesante: la rama testing avanza, mientras que la rama master permanece en la confirmación donde estaba cuando lanzaste el comando git checkout para saltar. Volvamos ahora a la rama master: \$ git checkout master Diagrama Descripción generada automáticamente Esto supone que los cambios que hagas desde este momento en adelante, divergirán de la antigua versión del proyecto. Si volvemos ahora a hacer cambios y confirmarlos: ![Diagrama Descripción generada automáticamente](media/image14.png) **Fusionar** Hemos llegado a esta situación: Diagrama Descripción generada automáticamente Se ha creado la rama Hotfix para hacer alguna corrección, cuando estamos seguros de que la solución es correcta, se incorporan los cambios a la rama master para ponerlos en producción. Esto se hace con el comando git merge: \$ git checkout master \$ git merge hotfix Esto es lo que se denomina "avance rápido" ("fast forward"). ![Diagrama Descripción generada automáticamente](media/image16.png) Tras haber resuelto el problema urgente que había interrumpido tu trabajo, puedes volver a donde estabas. Pero antes, es importante borrar la rama hotfix, ya que no la vamos a necesitar más, puesto que apunta exactamente al mismo sitio que la rama master. Esto lo puedes hacer con la opción -d del comando git branch: \$ git branch -d hotfix Ahora partimos de: Diagrama Descripción generada automáticamente Porque se ha hecho: \$ git checkout iss53 \$ vim index.html \$ git commit -a -m \'finished the new footer \[issue 53\]\' Cabe destacar que todo el trabajo realizado en la rama hotfix no está en los archivos de la rama iss53. Ahora querenos fusionar (merge) iss53 con la rama master. Para ello, de forma similar a como antes has hecho con la rama hotfix, vas a fusionar la rama iss53. Simplemente, activa (checkout) la rama donde deseas fusionar y lanza el comando git merge: \$ git checkout master \$ git merge iss53 En este caso, el registro de desarrollo había divergido en un punto anterior. Debido a que la confirmación en la rama actual no es ancestro directo de la rama que pretendes fusionar, Git tiene cierto trabajo extra que hacer. Git realizará una fusión a tres bandas, utilizando las dos instantáneas apuntadas por el extremo de cada una de las ramas y por el ancestro común a ambas. ![Diagrama Descripción generada automáticamente](media/image18.png) En lugar de simplemente avanzar el apuntador de la rama, Git crea una nueva instantánea (snapshot) resultante de la fusión a tres bandas; y crea automáticamente una nueva confirmación de cambios (commit) que apunta a ella. Nos referimos a este proceso como \"fusión confirmada\" y su particularidad es que tiene más de un padre. Imagen de la pantalla de un celular Descripción generada automáticamente con confianza media Vale la pena destacar el hecho de que es el propio Git quien determina automáticamente el mejor ancestro común para realizar la fusión; a diferencia de otros sistemas tales como CVS o Subversion, donde es el desarrollador quien ha de determinar cuál puede ser dicho mejor ancestro común. Esto hace que en Git sea mucho más fácil realizar fusiones. Ahora que todo tu trabajo ya está fusionado con la rama principal, no tienes necesidad de la rama iss53. Por lo que puedes borrarla y cerrar manualmente el problema en el sistema de seguimiento de problemas de tu empresa. \$ git branch -d iss53 **Conflictos** En algunas ocasiones, los procesos de fusión no suelen ser fluidos. Si hay modificaciones dispares en una misma porción de un mismo archivo en las dos ramas distintas que pretendes fusionar, Git no será capaz de fusionarlas directamente. Por ejemplo, si en tu trabajo del problema \#53 has modificado una misma porción que también ha sido modificada en el problema hotfix, verás un conflicto como este: \$ git merge iss53 Auto-merging index.html CONFLICT (content): Merge conflict in index.html Automatic merge failed; fix conflicts and then commit the result. Git no crea automáticamente una nueva fusión confirmada (merge commit), sino que hace una pausa en el proceso, esperando a que tú resuelvas el conflicto. Para ver qué archivos permanecen sin fusionar en un determinado momento conflictivo de una fusión, puedes usar el comando git status: \$ git status On branch master You have unmerged paths. (fix conflicts and run \"git commit\") Unmerged paths: (use \"git add \\...\" to mark resolution) both modified: index.html no changes added to commit (use \"git add\" and/or \"git commit -a\") Todo aquello que sea conflictivo y no se haya podido resolver, se marca como \"sin fusionar\" (unmerged). Git añade a los archivos conflictivos unos marcadores especiales de resolución de conflictos que te guiarán cuando abras manualmente los archivos implicados y los edites para corregirlos. El archivo conflictivo contendrá algo como: \\> iss53:index.html Donde nos dice que la versión en HEAD (la rama master, la que habías activado antes de lanzar el comando de fusión) contiene lo indicado en la parte superior del bloque (todo lo que está encima de =======) y que la versión en iss53 contiene el resto, lo indicado en la parte inferior del bloque. Para resolver el conflicto, has de elegir manualmente el contenido de uno o de otro lado. Por ejemplo, puedes optar por cambiar el bloque, dejándolo así: \ please contact us at email.support\@github.com \ Esta corrección contiene un poco de ambas partes y se han eliminado completamente las líneas \\>. Tras resolver todos los bloques conflictivos, has de lanzar comandos git add para marcar cada archivo modificado. Marcar archivos como preparados (staged) indica a Git que sus conflictos han sido resueltos. **Diferentes maneras de fusionar:** [[https://git-scm.com/book/es/v2/Ramificaciones-en-Git-Procedimientos-Básicos-para-Ramificar-y-Fusionar]](https://git-scm.com/book/es/v2/Ramificaciones-en-Git-Procedimientos-B%C3%A1sicos-para-Ramificar-y-Fusionar) Para obtener más información de GIT [[https://git-scm.com/book/es/v2/Fundamentos-de-Git-Obteniendo-un-repositorio-Git]](https://git-scm.com/book/es/v2/Fundamentos-de-Git-Obteniendo-un-repositorio-Git) https://git-scm.com/book/es/v2/Ramificaciones-en-Git-¿Qué-es-una-rama%3F

Use Quizgecko on...
Browser
Browser