2.2 Un conflicto

Veamos lo que sucede cuando hay un conflicto. Hagamos checkout de example9/branchB y mezclemos example9/branchA:

Listing 2.9:Mezclando
$ git merge example9/branchA 
Auto-merging example.py 
CONFLICT (content): Merge conflict in example.py 
Automatic merge failed; fix conflicts and then commit the result. 
$ git status 
On branch example9/branchB 
You have unmerged paths. 
  (fix conflicts and run "git commit") 
  (use "git merge --abort" to abort the merge) 
 
Changes to be committed: 
        modified:   module.py 
 
Unmerged paths: 
  (use "git add <file>..." to mark resolution) 
        both modified:   example.py 
 
$ git ls-tree -r HEAD 
100644 blob 2f78cf5b66514f2506d9af5f3dadf3dee7aa6d9f    .gitignore 
100755 blob 500616dc70f4847f244d29d827a192b7fa03de93    example.py 
100644 blob 2d1fe2ea52267ab6e75cca853c393fc6929a0e45    module.py 
$ git ls-files -s 
100644 2f78cf5b66514f2506d9af5f3dadf3dee7aa6d9f 0       .gitignore 
100755 b0a7eae10629dec61246f86c08f2432f0e276675 1       example.py 
100755 500616dc70f4847f244d29d827a192b7fa03de93 2       example.py 
100755 ad957360597fb9bd3e83d4bfa869f6e19b7fbf2b 3       example.py 
100644 475f980e6c6e24f8fc4a144e498fa1c1c59da370 0       module.py

Podemos ver que example.py aparece en Unmerged paths, y module.py está listado en Changes to be committed. .gitignore no sale listado en git status. .gitignore no ha cambiado entre HEAD, index y lo que está en el árbol de trabajo así que no hay nada que reportar sobre él. module.py cambió como parte del proceso de mezcla sin conflictos y por eso está presente tanto en HEAD como en el index con un id different. El archivo que está actualmente en el árbol de trabajo es justo como está en el index y por eso sale listado en Changes to be committed. Lo interesante es con respecto a example.py. En vez de tener un solo ítem del archivo en el index, hay 3 y cada instancia tiene un index stage diferente del 1 al 3. Cuando hay un conflicto de contenido en un archivo (como en este caso), git va a guardar 3 versiones del archivo en el index. Stage número 1 es el archivo como está en el ancestro común6 . Stage número 2 es el archivo como está en HEAD. Stage número 3 es el archivo como está en la otra rama. Vean por sí mismos:

Listing 2.10:Verificando los ids de example.py en las diferentes versiones
$ git ls-tree $( git merge-base HEAD MERGE_HEAD ) example.py 
100755 blob b0a7eae10629dec61246f86c08f2432f0e276675    example.py 
$ git ls-tree HEAD example.py 
100755 blob 500616dc70f4847f244d29d827a192b7fa03de93    example.py 
$ git ls-tree MERGE_HEAD example.py 
100755 blob ad957360597fb9bd3e83d4bfa869f6e19b7fbf2b    example.py

Stage número 0, como vimos antes, significa que el archivo no tiene cambios o que fue agregado al index.

Resolvamos el conflicto como habíamos hecho antes (lo que significa editar ambos archivos) y entonces veamos la salida de comandos git:

Listing 2.11:git status luego de modificar los archivos
$ git status 
On branch example9/branchB 
You have unmerged paths. 
  (fix conflicts and run "git commit") 
  (use "git merge --abort" to abort the merge) 
 
Changes to be committed: 
        modified:   module.py 
 
Unmerged paths: 
  (use "git add <file>..." to mark resolution) 
        both modified:   example.py 
 
Changes not staged for commit: 
  (use "git add <file>..." to update what will be committed) 
  (use "git restore <file>..." to discard changes in working directory) 
        modified:   module.py 
 
$ git ls-tree -r HEAD 
100644 blob 2f78cf5b66514f2506d9af5f3dadf3dee7aa6d9f    .gitignore 
100755 blob 500616dc70f4847f244d29d827a192b7fa03de93    example.py 
100644 blob 2d1fe2ea52267ab6e75cca853c393fc6929a0e45    module.py 
$ git ls-files -s 
100644 2f78cf5b66514f2506d9af5f3dadf3dee7aa6d9f 0       .gitignore 
100755 b0a7eae10629dec61246f86c08f2432f0e276675 1       example.py 
100755 500616dc70f4847f244d29d827a192b7fa03de93 2       example.py 
100755 ad957360597fb9bd3e83d4bfa869f6e19b7fbf2b 3       example.py 
100644 475f980e6c6e24f8fc4a144e498fa1c1c59da370 0       module.py

Aunque los ids de los objetos en index y HEAD no han cambiado, el contenido de los archivos cambió en el árbol de trabajo. Dado que module.py tiene un id diferente entre index y HEAD, git lo muestra en Changes to be committed. Dado que el contenido del archivo en el árbol de trabajo es diferente de index, también se lista en Changes not staged for commit. example.py no ha sido agregado al index, así que para git sigue con conflictosy mantiene las 3 versiones en el index y se lista en Unmerged paths.

Agreguemos module.py primero y veamos qué sucede:

Listing 2.12:Luego de agregar module.py
$ git add module.py 
$ git status 
On branch example9/branchB 
You have unmerged paths. 
  (fix conflicts and run "git commit") 
  (use "git merge --abort" to abort the merge) 
 
Changes to be committed: 
        modified:   module.py 
 
Unmerged paths: 
  (use "git add <file>..." to mark resolution) 
        both modified:   example.py 
 
$ git ls-tree -r HEAD 
100644 blob 2f78cf5b66514f2506d9af5f3dadf3dee7aa6d9f    .gitignore 
100755 blob 500616dc70f4847f244d29d827a192b7fa03de93    example.py 
100644 blob 2d1fe2ea52267ab6e75cca853c393fc6929a0e45    module.py 
$ git ls-files -s 
100644 2f78cf5b66514f2506d9af5f3dadf3dee7aa6d9f 0       .gitignore 
100755 b0a7eae10629dec61246f86c08f2432f0e276675 1       example.py 
100755 500616dc70f4847f244d29d827a192b7fa03de93 2       example.py 
100755 ad957360597fb9bd3e83d4bfa869f6e19b7fbf2b 3       example.py 
100644 42004985f8888627d3985174325a235401568e0b 0       module.py

Para este archivo, se comporta como sucedía antes cuando no había conflictos. Los ids para este archivo son diferentes are entre HEAD e index y por eso sale listado en Changes to be committed. El archivo en el árbol de trabajo es como está en el index ahora y por eso git no tiene más nada que reportar sobre él. example.py sigue con conflictos y por eso no hay cambios en cuanto a lo que veíamos previamente.

Ahora agreguemos example.py:

Listing 2.13:Luego de agregar example.py
$ git add example.py 
$ git status 
On branch example10/branchB 
All conflicts fixed but you are still merging. 
  (use "git commit" to conclude merge) 
 
Changes to be committed: 
        modified:   example.py 
        modified:   module.py 
 
$ git ls-tree -r HEAD 
100644 blob 2f78cf5b66514f2506d9af5f3dadf3dee7aa6d9f    .gitignore 
100755 blob 500616dc70f4847f244d29d827a192b7fa03de93    example.py 
100644 blob 2d1fe2ea52267ab6e75cca853c393fc6929a0e45    module.py 
$ git ls-files -s 
100644 2f78cf5b66514f2506d9af5f3dadf3dee7aa6d9f 0       .gitignore 
100755 ad957360597fb9bd3e83d4bfa869f6e19b7fbf2b 0       example.py 
100644 42004985f8888627d3985174325a235401568e0b 0       module.py

Ahora que el archivo que (originalmente) tenía conflictos ha sido agregado, tenemos un solo ítem para él en el index. Dado que los ids no son los mismos entre HEAD e index, git lo reporta en Changed to be committed y dado que el contenido del archivo en el árbol de trabajo es como en el index, git no tiene más nada que decir sobre él. Creemos una nueva revisión en este momento y veamos qué sucede.

Listing 2.14:git commit
$ git commit -m "A new revision" 
[example9/branchB 9f80666] A new revision 
$ git status 
On branch example9/branchB 
nothing to commit, working tree clean 
$ git ls-tree -r HEAD 
100644 blob 2f78cf5b66514f2506d9af5f3dadf3dee7aa6d9f    .gitignore 
100755 blob ad957360597fb9bd3e83d4bfa869f6e19b7fbf2b    example.py 
100644 blob 42004985f8888627d3985174325a235401568e0b    module.py 
$ git ls-files -s 
100644 2f78cf5b66514f2506d9af5f3dadf3dee7aa6d9f 0       .gitignore 
100755 ad957360597fb9bd3e83d4bfa869f6e19b7fbf2b 0       example.py 
100644 42004985f8888627d3985174325a235401568e0b 0       module.py

Como sucedió anteriormente cuando no había conflictos, cuando se crea una nueva revisión, los ids en HEAD son actualizados a los mismos ids del index7 . Dado que index y HEAD tienen los mismos ids, no hay nada listado en Changes to be committed y dado que no hay diferencias entre el index y el arbol de trabajo, no hay más nada que reportar.

Si se detienen a pensarlo, aparte de los 3 items listados en index mientras el archivo tenía conflictos, no hay otras diferencias en cuanto a lo que se guardó en index o en las revisiones. Dentro de todo, git no guarda ninguna información sobre los archivos que tuvieron conflictos cuando se crea una revisión. Simplemente crea una nueva revisión con sus múltiples padres, los archivos y sus contenidos (finales) justo como en cualquier otra revisión:

Listing 2.15:Verificando la información de la revisión
$ git cat-file -p HEAD 
tree 396032b1546d75672f3a85c13a858d3b187d2046 
parent 3cd9cfd24b13fa2381c5cc8009275b961ea7a26b 
parent dfde76c316ff0a070ddc7560f86b7279b73ed807 
author Developer A <dev.a@localhost> 1592110970 -0600 
committer Developer A <dev.a@localhost> 1592111469 -0600 
 
A new revision 
$ git cat-file -p HEAD^{tree} 
100644 blob 2f78cf5b66514f2506d9af5f3dadf3dee7aa6d9f    .gitignore 
100755 blob ad957360597fb9bd3e83d4bfa869f6e19b7fbf2b    example.py 
100644 blob 42004985f8888627d3985174325a235401568e0b    module.py

Copyright 2020 Edmundo Carmona Antoranz