En el tema anterior, git merge juntaba dos ramas solo. Funciona mientras cada rama toca cosas
distintas. Pero si las dos cambian las mismas líneas de un archivo, Git no sabe con cuál
quedarse: es un conflicto. No es un error ni algo roto: Git para y te pregunta.
En este tema trabajas en la redacción de Mareas, una revista que guarda sus artículos en Git. Varios redactores tocan la misma portada a la vez, cada uno en su rama, y al juntarlas chocan. Como siempre, cada sección tiene un ejercicio con una terminal.
Un conflicto: qué dice Git
El redactor propuso otro titular en la rama titular; mientras, la directora cambió el de main.
Al juntarlas:
tecnico@srv-pruebas:~/revista$ git merge titular Auto-merging articulos/portada.md CONFLICT (content): Merge conflict in articulos/portada.md Automatic merge failed; fix conflicts and then commit the result.
git status dice qué archivos chocan (en Unmerged paths, como both modified, porque las
dos ramas lo cambiaron) y qué hacer:
tecnico@srv-pruebas:~/revista$ 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: articulos/portada.md no changes added to commit (use "git add" and/or "git commit -a")
En corto, git status -s lo marca con UU. Y dentro del archivo, Git deja las dos
versiones entre marcas: de <<<<<<< HEAD a =======, lo de tu rama (la de ahora); de =======
a >>>>>>> titular, lo de la otra. Lo que no chocó (el subtítulo, la foto) ya está juntado:
tecnico@srv-pruebas:~/revista$ cat articulos/portada.md Portada <<<<<<< HEAD Titular: Agosto en llamas ======= Titular: Cuarenta grados >>>>>>> titular Subtítulo: Crónica de una ola de calor Foto: playa.jpg Pie: Bañistas en Cádiz
git diff, durante un conflicto, enseña un diff combinado: dos columnas de + y -, una por
rama. Y mientras no lo resuelvas, Git no deja seguir:
tecnico@srv-pruebas:~/revista$ git commit -m 'Sin resolver' U articulos/portada.md error: Committing is not possible because you have unmerged files. hint: Fix them up in the work tree, and then use 'git add/rm <file>' hint: as appropriate to mark resolution and make a commit. fatal: Exiting because of an unresolved conflict.
Resolverlo quedándote con una versión
A veces una de las dos versiones gana entera. git checkout --ours archivo deja la de tu rama
(la de HEAD); git checkout --theirs archivo, la de la otra. (git restore --ours y
--theirs hacen lo mismo, sin avisar.)
tecnico@srv-pruebas:~/revista$ git checkout --ours articulos/portada.md Updated 1 path from the index
Eso solo cambia el archivo. Para decirle a Git que ya está resuelto, se prepara con
git add; entonces status dice que solo falta terminar el merge:
tecnico@srv-pruebas:~/revista$ git add articulos/portada.md && git status On branch main All conflicts fixed but you are still merging. (use "git commit" to conclude merge) nothing to commit, working tree clean
Que no te despiste el nothing to commit: los archivos ya eran los de tu rama, pero el merge sigue
a medias. git commit lo termina: abre el editor con el mensaje ya escrito (Merge branch 'titular',
y en comentarios, qué archivos chocaron). Guardas, sales, y queda el commit de merge, con sus dos
padres, como cualquier otro merge.
Ejercicio
Uno de los dos titulares
Un redactor propuso otro titular en la rama titular; la directora cambió el de main. Junta titular con main: chocarán. La directora manda: se queda su titular (el de main).
Preparando la máquina…
Resolverlo juntando las dos
Otras veces lo bueno es un poco de cada: Marta escribió en la agenda los conciertos, y Luis, el
cine. Los dos crearon el mismo archivo, así que choca (add/add, y en corto AA):
tecnico@srv-pruebas:~/revista$ cat articulos/agenda.md Agenda <<<<<<< HEAD Cine de verano ======= Conciertos en el parque >>>>>>> agenda
Aquí no sirve --ours ni --theirs: se edita el archivo (con nano, por ejemplo) hasta que
quede como debe, quitando las tres marcas. Después, git add y git commit, como antes.
¿Ya se guardaron? No hace falta deshacer el merge: deja el archivo como debe (con nano, o con
git checkout <rama o commit> -- archivo, que trae la versión de ese commit) y guárdalo en un
commit nuevo, encima.
Ejercicio
Las dos agendas
Marta (en la rama agenda) y Luis (en main) escribieron cada uno la agenda, en articulos/agenda.md. Junta agenda con main y quédate con las dos cosas: primero los conciertos y después el cine, sin las marcas del conflicto.
Preparando la máquina…
Varios archivos a la vez
Un merge puede chocar en más de un archivo. Git los lista todos:
tecnico@srv-pruebas:~/revista$ git merge fotos Auto-merging articulos/portada.md CONFLICT (content): Merge conflict in articulos/portada.md Auto-merging sumario.txt CONFLICT (content): Merge conflict in sumario.txt Automatic merge failed; fix conflicts and then commit the result.
Se resuelven uno a uno, cada uno a su manera, y cada uno con su git add; git status va
diciendo cuáles faltan. Si intentas terminar antes de tiempo, Git dice cuál queda:
tecnico@srv-pruebas:~/revista$ git merge --continue U articulos/portada.md error: Committing is not possible because you have unmerged files. hint: Fix them up in the work tree, and then use 'git add/rm <file>' hint: as appropriate to mark resolution and make a commit. fatal: Exiting because of an unresolved conflict.
git merge --continue es lo mismo que git commit para terminar un merge, solo que se lee
mejor.
Ejercicio
Un conflicto en dos archivos
La rama fotos y main cambian la foto de la portada y el sumario. Junta fotos con main: de la portada, la foto de fotos (calle.jpg); del sumario, el de main ("Agenda cultural").
Preparando la máquina…
Echarse atrás: git merge –abort
Si el merge no tocaba (la rama no estaba lista, o te has liado resolviendo), git merge --abort
lo deja todo como antes del merge: la rama, el índice y los archivos. No dice nada:
tecnico@srv-pruebas:~/revista$ git merge --abort
tecnico@srv-pruebas:~/revista$ git status On branch main nothing to commit, working tree clean
Sin un merge a medias, Git avisa: fatal: There is no merge to abort (MERGE_HEAD missing).
(MERGE_HEAD es el archivo de .git donde Git apunta qué se estaba juntando.)
¿Te echaste atrás de más, con un git reset --hard que se llevó commits de main? No se han
borrado: git merge <hash> con el último que tenía main (git reflog, del tema 2, te lo dice)
los trae de vuelta.
Ejercicio
Un merge que no tocaba
Alguien empezó a juntar la rama borrador con main y lo dejó a medias, con un conflicto. Esa rama aún no está aprobada: deja main como estaba antes de ese merge.
Preparando la máquina…
Borrado en una rama, cambiado en la otra
No todos los conflictos son de líneas. Si una rama borra un archivo y la otra lo cambia, Git no sabe si quieres el archivo o no:
tecnico@srv-pruebas:~/revista$ git merge recorte CONFLICT (modify/delete): articulos/entrevista.md deleted in recorte and modified in HEAD. Version HEAD of articulos/entrevista.md left in tree. Automatic merge failed; fix conflicts and then commit the result.
Deja en la carpeta la versión cambiada, y status lo marca como deleted by them (UD en
corto). Tú decides: git rm archivo si se va, o git add archivo si se queda. Y
después, git commit.
Ejercicio
La entrevista que se cae
En la rama recorte se quitó la entrevista (no cabe en este número); en main, mientras, alguien corrigió una errata en ella. Junta recorte con main: la entrevista se cae del número.
Preparando la máquina…
Rebase: rehacer una rama encima de otra
Juntar con merge deja en la historia cada rama y su commit de merge. Hay otra forma:
git rebase main coge los commits de tu rama y los rehace uno a uno encima de lo último de
main, como si hubieras empezado la rama ahora. La historia queda en línea, sin merge.
tecnico@srv-pruebas:~/revista$ git log --oneline --graph --all * 7e0e0c1 (HEAD -> columna) La columna | * b20e8a2 (main) El sumario, con el tema |/ * d2996e8 Las dos agendas
tecnico@srv-pruebas:~/revista$ git rebase main Successfully rebased and updated refs/heads/columna.
tecnico@srv-pruebas:~/revista$ git log --oneline --graph --all * f72bb6d (HEAD -> columna) La columna * b20e8a2 (main) El sumario, con el tema * d2996e8 Las dos agendas
Fíjate en el hash: La columna ya no es 7e0e0c1 sino f72bb6d, porque es un commit nuevo
(con otro padre). Si ya estaba encima, Git dice Current branch columna is up to date.
Si al rehacer un commit choca, el rebase para en ese commit, con el conflicto como en un merge
(git status dice por dónde va). Lo resuelves igual (editar o --ours/--theirs, y git add) y
sigues con git rebase --continue. También puedes saltarte ese commit (git rebase --skip) o
dejarlo todo como antes (git rebase --abort). Un rebase parado se queda a medias, aunque cierres
la consola, hasta que alguien lo siga o lo deshaga: mientras, git status empieza por
interactive rebase in progress y Git no deja cambiar de rama.
Ejercicio
La columna, al día
La rama columna salió de main hace días, y main ha avanzado. Pon la columna encima de lo último de main, sin un commit de merge (que la historia quede en línea); main no se toca.
Preparando la máquina…
Y el revert con conflicto
En el tema 2, git revert deshacía un commit… salvo cuando lo que vino después tocaba las mismas
líneas. Ahora ya sabes qué pasa: es un conflicto, con su git revert --continue (para terminar),
--skip y --abort, igual que en un rebase.
Resumen
| Orden | Qué hace |
|---|---|
git status |
qué archivos chocan (both modified, UU…) y qué hacer |
git diff |
durante un conflicto, el diff combinado de las dos ramas |
git checkout --ours / --theirs archivo |
quedarse con lo de tu rama / con lo de la otra |
git add archivo |
marcarlo como resuelto |
git rm archivo |
resolver un borrado/cambiado borrándolo |
git commit / git merge --continue |
terminar el merge |
git merge --abort |
echarse atrás: todo como antes del merge |
git rebase main |
rehacer tu rama encima de main, en línea |
git rebase --continue / --skip / --abort |
seguir, saltar ese commit o echarse atrás |