Capítulo 11 de 33 8 secciones 7 min

Juntar dos ramas y entender el conflicto

El merge, y qué hacer cuando Git no sabe con cuál quedarse

Juntar dos ramas es un comando. El conflicto aparece solo cuando las dos tocaron <strong>la misma línea del mismo archivo</strong>, y no es un error: es Git diciendo que esa decisión es tuya y no suya. Se resuelve editando el archivo a mano, que es más simple de lo que suena 🤝

El caso fácil, que es casi siempre

mkdir tienda
cd tienda
git init -q
printf 'ciudad,monto\nLima,1200\n' > ventas.csv
git add ventas.csv
git commit -q -m "Primeras ventas de Lima"
git switch -q -c catalogo-clientes
printf 'bodega,ciudad\nBodega Sol,Lima\n' > clientes.csv
git add clientes.csv
git commit -q -m "Se agrega el catalogo de bodegas"
git switch -q main
git merge catalogo-clientes
Updating 54da6e2..f4c221f
Fast-forward
 clientes.csv | 2 ++
 1 file changed, 2 insertions(+)
 create mode 100644 clientes.csv

Fast-forward: como main no se había movido, Git simplemente adelantó el puntero. Ni siquiera hizo un commit de fusión, porque no hacía falta decidir nada.

ls
git log --oneline
clientes.csv
ventas.csv
f4c221f Se agrega el catalogo de bodegas
54da6e2 Primeras ventas de Lima

Cuando las dos ramas avanzaron

Ahora que las dos tengan commits propios, tocando archivos distintos:

git switch -q -c precios
printf 'precio\n34.90\n' > precios.csv
git add precios.csv
git commit -q -m "Se agrega la lista de precios"
git switch -q main
printf 'Arequipa,890\n' >> ventas.csv
git add ventas.csv
git commit -q -m "Se agregan las ventas de Arequipa"
git merge --no-edit precios
Merge made by the 'ort' strategy.
 precios.csv | 2 ++
 1 file changed, 2 insertions(+)
 create mode 100644 precios.csv

Aquí sí hubo un commit de fusión, el que junta las dos líneas de tiempo. Se ve bonito en el dibujo:

git log --oneline --graph
*   2948846 Merge branch 'precios'
|\  
| * e04f587 Se agrega la lista de precios
* | 82a23b8 Se agregan las ventas de Arequipa
|/  
* f4c221f Se agrega el catalogo de bodegas
* 54da6e2 Primeras ventas de Lima

Y ahora el conflicto

Dos ramas que tocan la misma línea del mismo archivo:

git switch -q -c corrige-lima
sed -i 's/Lima,1200/Lima,1350/' ventas.csv
git add ventas.csv
git commit -q -m "Se corrige el monto de Lima segun el sistema"
git switch -q main
sed -i 's/Lima,1200/Lima,1290/' ventas.csv
git add ventas.csv
git commit -q -m "Se corrige el monto de Lima segun la bodega"
git merge --no-edit corrige-lima
Auto-merging ventas.csv
CONFLICT (content): Merge conflict in ventas.csv
Automatic merge failed; fix conflicts and then commit the result.

Eso no es que se haya roto nada. Es Git diciendo: una rama dice 1350 y la otra 1290, y yo no sé cuál es la buena. Y tiene razón, esa decisión es tuya.

Cómo se ve un conflicto por dentro

cat ventas.csv
ciudad,monto
<<<<<<< HEAD
Lima,1290
=======
Lima,1350
>>>>>>> corrige-lima
Arequipa,890

Git marcó la zona en disputa con tres señales:

  • <<<<<<< HEAD, aquí empieza lo tuyo, lo de la rama donde estás.
  • =======, la frontera.
  • >>>>>>> corrige-lima, aquí termina lo de la rama que traes.

Resolverlo es borrar esas líneas raras y dejar el archivo como debe quedar. Se puede a mano en cualquier editor. Aquí lo hago con un comando para que quede ejecutado:

printf 'ciudad,monto\nLima,1290\nArequipa,890\n' > ventas.csv
cat ventas.csv
git add ventas.csv
git commit -q -m "Se resuelve el conflicto de Lima con el monto de la bodega"
git log --oneline -1
ciudad,monto
Lima,1290
Arequipa,890
64abe86 Se resuelve el conflicto de Lima con el monto de la bodega

Listo. El git add es lo que le dice a Git "ya lo decidí", y el commit cierra la fusión 🎉

La salida de emergencia

Si el conflicto es más grande de lo que esperabas, no hay que resolverlo a la fuerza. Se puede volver atrás como si nada:

git switch -q -c otro-precio
printf 'ciudad,monto\nLima,9999\nArequipa,890\n' > ventas.csv
git add ventas.csv
git commit -q -m "Se prueba otro monto para Lima"
git switch -q main
printf 'ciudad,monto\nLima,1000\nArequipa,890\n' > ventas.csv
git add ventas.csv
git commit -q -m "Se vuelve a corregir el monto de Lima"
git merge --no-edit otro-precio || true
git merge --abort
git status --short
cat ventas.csv
Auto-merging ventas.csv
CONFLICT (content): Merge conflict in ventas.csv
Automatic merge failed; fix conflicts and then commit the result.
ciudad,monto
Lima,1000
Arequipa,890

merge --abort deja todo exactamente como estaba antes de intentar la fusión. Saber que existe quita casi todo el miedo 🧯

La trampa

Resuelves un conflicto en el CSV de ventas, borras las marcas raras, guardas y sigues con tu día.

$ git add ventas.csv
$ git commit -m "Se resuelve el conflicto"
$ cat ventas.csv

ciudad,monto
Lima,1290
=======
Lima,1350
Arequipa,890
Qué está mal

Se te quedó una marca dentro del archivo 🙈

Al borrar a mano es facilísimo dejarse un ======= o un >>>>>>>, y Git no revisa: para él el conflicto está resuelto en cuanto haces el add.

En un CSV eso te mete una fila basura en el análisis. En un archivo de código, el programa no arranca y el error no dice nada de un conflicto, así que puedes pasarte una hora buscando en el sitio equivocado.

La costumbre: antes de cerrar la fusión, git diff --check, que busca justo esas marcas. Y si el archivo es de datos, ábrelo y míralo.

Comprueba que se entendió

Comprueba que lo tienes

Te sale CONFLICT al hacer un merge. ¿Qué significa?

  • Que rompiste el repositorio
  • Que dos ramas cambiaron la misma línea y Git necesita que tú decidas
  • Que hay que borrar una de las dos ramas
  • Que la fusión falló y hay que empezar de nuevo

Ejercicios

1. Una fusión sin drama

Crea una rama que toque un archivo nuevo y júntala.

git switch -q -c canales
printf 'canal\nbodega\nminimarket\n' > canales.csv
git add canales.csv
git commit -q -m "Se agrega el catalogo de canales"
git switch -q main
git merge --no-edit canales
Updating e0a1cf9..a4578ab
Fast-forward
 canales.csv | 3 +++
 1 file changed, 3 insertions(+)
 create mode 100644 canales.csv

Archivos distintos, cero conflictos. La inmensa mayoría de las fusiones son así de aburridas, que es como deben ser 😌

2. Comprueba que llegó todo

Mira los archivos y la historia después de la fusión.

ls
git log --oneline -3
canales.csv
clientes.csv
precios.csv
ventas.csv
a4578ab Se agrega el catalogo de canales
e0a1cf9 Se vuelve a corregir el monto de Lima
64abe86 Se resuelve el conflicto de Lima con el monto de la bodega

El trabajo de las dos ramas junto, y en la historia se ve de dónde salió cada cosa.

3. Provoca un conflicto a propósito

Cambia la misma línea en dos ramas. Tiene que fallar.

git switch -q -c canal-horeca
sed -i 's/minimarket/horeca/' canales.csv
git add canales.csv
git commit -q -m "El segundo canal es horeca"
git switch -q main
sed -i 's/minimarket/mayorista/' canales.csv
git add canales.csv
git commit -q -m "El segundo canal es mayorista"
git merge --no-edit canal-horeca
Auto-merging canales.csv
CONFLICT (content): Merge conflict in canales.csv
Automatic merge failed; fix conflicts and then commit the result.

Misma línea, dos verdades distintas. Git levanta la mano y te pregunta.

4. Mira el archivo en conflicto

Abre el CSV y busca las tres marcas.

cat canales.csv
canal
bodega
<<<<<<< HEAD
mayorista
=======
horeca
>>>>>>> canal-horeca

Arriba lo tuyo, abajo lo que traes, y la frontera en medio. Una vez que lo ves una vez, ya no asusta más 👀

5. Resuélvelo quedándote con los dos

A veces la respuesta no es elegir, es juntar. Deja los dos canales.

printf 'canal\nbodega\nmayorista\nhoreca\n' > canales.csv
git add canales.csv
git commit -q -m "Se resuelve el conflicto dejando los dos canales"
cat canales.csv
canal
bodega
mayorista
horeca

Resolver un conflicto es decidir qué debe quedar, y eso incluye la opción de que quede todo 🤗

6. Busca marcas olvidadas

Comprueba que no quedó ninguna marca suelta en el repositorio.

grep -rn "<<<<<<<\|=======\|>>>>>>>" . --exclude-dir=.git || echo "limpio"
limpio

Este grep es la versión manual de la costumbre de la trampa. Vale la pena correrlo después de cada conflicto que resuelvas a mano.

7. Cancela una fusión a mitad

Empieza un conflicto y vuelve atrás sin resolverlo.

git switch -q -c canal-detalle
sed -i 's/mayorista/detalle/' canales.csv
git add canales.csv
git commit -q -m "El canal se llama detalle"
git switch -q main
sed -i 's/mayorista/tradicional/' canales.csv
git add canales.csv
git commit -q -m "El canal se llama tradicional"
git merge --no-edit canal-detalle || true
git merge --abort
git status --short
cat canales.csv
Auto-merging canales.csv
CONFLICT (content): Merge conflict in canales.csv
Automatic merge failed; fix conflicts and then commit the result.
canal
bodega
tradicional
horeca

Como si nunca hubiera pasado. Cuando un conflicto te supera, esta es la salida y no hay que sentirse mal por usarla 🚪

Lo que te llevas

Un conflicto no es un error, es Git preguntándote algo que solo tú puedes contestar.

¿Tienes alguna duda o consulta?