Resolver un merge conflict en Git


Lección 43 / 53

Resolver un merge conflict en Git

Git Guía Git Guía Git español

Mencionamos anteriormente que pueden ocurrir conflictos cuando se ejecuta un git pull, git merge o git rebase en Git que impiden que Git complete automáticamente la operación.

Un conflicto en el sentido de sistemas de control de versiones es una situación en la que el sistema intenta integrar dos historias diferentes que han realizado cambios en las mismas líneas de un archivo.

El proceso de fusión puede entrar en un estado de conflicto en dos momentos diferentes:

  • al comienzo del merge - ocurre cuando hay cambios en la working copy o en la staging area; tales modificaciones “pending” no pueden ser administrados por Git y dependerá del desarrollador “limpiar” su espacio de trabajo con git stash, git checkout, git commit o git reset
  • durante el merge - en tal caso, el conflicto es en realidad entre el branch en uso y la que se fusiona, el conflicto es el realidad entre el branch en uso y el que se fusiona, un conflicto que implica cambios realizados en los commit de dos branch.

NOTA: como se vio en la sección anterior, en la fusión traemos los cambios presentes en una rama dentro de la rama actualmente presente en la working copy. Más adelante en esta sección nos referiremos a estas ramas como "incoming" y "current" respectivamente.

Probemos algunos "ejercicios" de merge conflict en Git. Intentemos crear el caso en el que queremos informar los cambios realizados por nosotros en una rama a la rama principal, en la situación en la que otro desarrollador web ha agregado un commit a la rama principal después de la creación de nuestra rama de desarrollo.
 

Merge conflict

Merge conflict
 

Creamos un repository al que añadimos un archivo con un commit (con la opción --author para simular mejor quién confirmó qué).

$ mkdir git-merge-test
$ cd git-merge-test
$ git init .
$ echo "initial content to edit later" > merge.txt
$ git add merge.txt
$ git commit -m "initial content" --author="Someone <someone@example.com>"
[main (root-commit) 0a99708] initial content
Author: Someone <someone@example.com>
1 file changed, 1 insertion(+)
create mode 100644 merge.txt

Creamos un nuevo branch, aportamos los cambios al archivo y guardamos los cambios en un commit.

$ git checkout -b merge-me
Switched to a new branch 'merge-me'

$ echo "new content to merge later" > merge.txt
$ git commit -am "edited the content for conflict" --author "Me <me@example.com>"
[merge-me 15416df] edited the content for conflict
Author: Me <me@example.com>
1 file changed, 1 insertion(+), 1 deletion(-)

Volvemos branch main y añadimos una nueva línea de texto al archivo, guardándolo después en un commit.

$ git checkout main
Switched to branch 'main'

$ echo "content added to previous" >> merge.txt
$ git commit -am "appended content to merge.txt" --author="Frank <frank@example.com>"
[main 90640eb] appended content to merge.txt
Author: Frank <frank@example.com>
1 file changed, 1 insertion(+)

 

Entonces, intentemos fusionar los cambios en la rama merge-me (incoming) en la branch main (current)

$ git merge merge-me
Auto-merging merge.txt
CONFLICT (content): Merge conflict in merge.txt
Automatic merge failed; fix conflicts and then commit the result.

$ git status
On branch main
You have unmerged paths.
  (fix conflicts and run "git commit")
  (use "git merge --abort" to abort the merge)

Unmerged paths:
  (use "git add <file>..." to mark resolution)
        both modified:   merge.txt

no changes added to commit (use "git add" and/or "git commit -a")

Al intentar ejecutar git merge se informa un conflicto. El proceso de merge se detiene, dejando su copia en un estado pendiente de resolución.

Risolver un merge conflict en Git

Con el comando git status, podemos resaltar la presencia de un conflicto durante la ejecución de una merge. Veamos cómo encontrar más detalles sobre qué cambió exactamente entre las dos versiones de la historia y cómo intervenir.

En el ejemplo del conflicto “forzado” sabemos que el problema está en el archivo merge.txt, el cual ha recibido cambios en las mismas líneas en ambas ramas.

En el momento en que se detiene la fusión, el archivo en conflicto tiene cambios de ambas versiones del archivo:

$ cat merge.txt
<<<<<<< HEAD
initial content to edit later
content added to previous
=======
new content to merge later
>>>>>>> merge-me

De particular interés son los marker de conflicto colocados por Git que nos permiten saber que:

  • la líneas entre <<<<<<< y ======= contienen las líneas en conflicto en el archivo tal como estaban en el commit del branch “current”
  • las líneas entre ======= y >>>>>>> contienen las líneas en conflicto en el archivo tal como estaban en el commit del branch “incoming”.

Hay tres formas de resolver conflictos en un archivo y proceder con la fusión, que obviamente pueden y deben elegirse caso por caso:

  • aceptar completamente los cambios que ya estaban en el branch “current” (en nuestro caso main) con git checkout --ours merge.txt
  • aceptar completamente los cambios que provienen del branch “incoming” (en nuestro caso new_branch_to_merge_later) con git checkout --their merge.txt
  • modificar el archivo para que contenga el contenido correcto y, de acuerdo con ambos cambios, declarar que el conflicto se ha resuelto con git add merge.txt y continuar con el merge a través de un commit.

Si quisiéramos proceder con esta última opción, tendremos que modificar el archivo hasta conseguir el contenido adecuado y después:

$ cat merge.txt
new content to merge later
content added to previous

$ git status
On branch main
You have unmerged paths.
  (fix conflicts and run "git commit")
  (use "git merge --abort" to abort the merge)

Unmerged paths:
  (use "git add <file>..." to mark resolution)
        both modified:   merge.txt

no changes added to commit (use "git add" and/or "git commit -a")

$ git add merge.txt

$ git status
On branch main
All conflicts fixed but you are still merging.
  (use "git commit" to conclude merge)

Changes to be committed:
modified:   merge.txt

Una vez efectuado el commit, la history de nuestro repository será la siguiente:

$ git log --pretty=format:'%h %s (%an)' --date=short  --graph
*   c9802c8 merged content due conflict (Me)
|\
| * 15416df edited the content for conflict (Me)
* | 90640eb appended content to merge.txt (Frank)
|/
* 0a99708 initial content (Someone)

Ten en cuenta que, en este caso, nuestro commit inicial en la rama permanece intacta (nos lo dice hecho que todavía tiene el mismo hash, 15416df), y los cambios necesarios para resolver el conflicto se han incluido en el commit de fusión.

De hecho, podemos comparar la diferencia entre los dos últimos commit en la rama actual con:

$ git diff HEAD^1
diff --git a/merge.txt b/merge.txt
index c560b6f..732761b 100644
--- a/merge.txt
+++ b/merge.txt
@@ -1,2 +1,2 @@
-initial content to edit later
+new content to merge later
content added to previous
Guía Git en español 1 ¿Qué es Git? 2 Nacimiento de Git 3 Principales características de Git 4 Línea de comando UI en Git 5 Cómo instalar Git 6 5 comandos Git para desarrolladores individuales 7 5 comandos Git para desarrollar en colaboración 8 Repository en Git 9 Commit en Git 10 Working Copy en Git 11 Staging Area en Git 12 Branch en Git 13 Remote en Git 14 Inicializar un nuevo repository con git init 15 Crear una copia de un repository remoto en Git con git clone 16 Configurar las opciones de Git con git config 17 El comando Git add en Git 18 El comando Git commit en Git 19 El comando Git diff en Git 20 El comando Git stash en Git 21 .gitignore : los archivos ignored en Git 22 El comando Git status en Git 23 El comando Git log en Git 24 El comando Git tag en Git 25 El comando Git blame en Git 26 El comando Git checkout en Git 27 El comando Git revert en Git 28 El comando Git reset en Git 29 El comando Git rm en Git 30 La opción Git commit –amend en Git 31 Git rebase –interactive en Git 32 Atajos para comandos frecuentes en Git 33 Repository compartido en Git 34 El modelo Fork & pull 35 El comando Git remote en Git 36 Los principales repository remote de Git: Github, Gitlab y Bitbucket 37 El comando Git fetch en Git 38 El comando Git push en Git 39 El comando Git pull en Git 40 El comando Git branch en Git 41 El comando Git checkout en Git 42 El comando Git merge en Git 43 Resolver un merge conflict en Git 44 Entender mejor el contenido de los commit durante un conflicto de merge en Git 45 Workflow Git centralizado 46 Workflow Git feature branching 47 Workflow Git trunk-based 48 Enfoque “forking” en Git 49 Gitflow en Git 50 Mensajes de commit en Git 51 Tagging & Versioning en Git 52 La opción merge en Git 53 La opción rebase en Git

© 2022 Aulab. Todos los derechos reservados • P.IVA: IT07647440721 • Política de privacidad