Iagovar

Git merge y git rebase: Entiende cómo funcionan

Diagramas y listas numeradas para entender git merge y git rebase
Diagrama de git merge y git rebase

¿Aprendiendo Git últimamente? es normal sentirse abrumado. Aquí el comentario de un desarrollador profesional, sacado de un foro, para que entiendas el contexto en el que estas:

Todavía recuerdo la primera vez que empecé a trabajar con git, que me advirtieron que nunca usara rebase, porque podía ser peligroso. Fue sólo cuando me mudé a otro entorno de trabajo, que aprendí a usarlo, y sólo hace unos cinco meses que se puede añadir --rebase con el botón pull por defecto. Pero aún así me encuentro usando Git Bash shell a menudo.

También me llevó mucho tiempo aprender que origin era el remoto por defecto y que se pueden añadir más remotos y que también se puede hacer push a un repositorio en una unidad USB.

He estado trabajando con git durante unos cuatro años y es sólo en el último año que me siento un poco cómodo con él. Pero aún así, cuando busco un comando de git, me siento abrumado por la gran cantidad de opciones.

Por otro lado, todavía me sorprende la cantidad de comandos que tengo que teclear para hacer algo tan simple como volver a basar mis cambios no confirmados con el último commit de origin. O lo que tienes que hacer para aplastar (squash) tus commits en la rama actual desde tu último push a master (o develop).

Git es como un conjunto de herramientas relativamente pequeñas e intrincadas, que a menudo desconciertan a los principiantes que sólo quieren realizar un número limitado de operaciones de acuerdo con un cierto estilo de trabajo.

Conceptos que debes repasar

Para acompañar este artículo te recomiendo usar este script que puedes ejecutar con bash si estás en Linux, o con la consola git bash que viene incluída en Git para Windows.

Algunas diferencias entre merge y rebase

Característica git merge git rebase
Crea un nuevo commit No
Modifica la historia del repo No
Se usa para fusionar ramas
Usa un commit de fusión No
Mantiene la historia sin cambios No
Requiere push especial No Quizá git push -f
Aplica cambios al código No
Fácil de revertir No
Sencillez Alta Baja

Git Merge

Diagrama de git merge y git rebase

Git intentará fusionar ambas ramas automáticamente y creará un merge commit nuevo. Si esto no se puede hacer automáticamente, Git te solicitará que resuelvas los conflictos (tendrás que editar archivos) y continues con el proceso.

Un ejemplo del mensaje de un merge commit

Si ejecutásemos un git log -1 --verbose veríamos algo como esto:


commit:   e2bc6ed88d6c8f1152e82b5d4f4a9d8a5c5ccbe6 (HEAD -> develop, feature)
Merge:    6f7b3d4 a4a4f9a
Author:   Iago Var <[email protected]>
Date:     Mon Jan 1 12:00:00 2022 -0600

Merge branch 'feature' into develop

This merge integrates the changes made in the 'feature' branch into the 'develop' branch.

Changes made in 'feature':
- Added a new feature
- Fixed a bug

Changes made in 'develop':
- Improved performance
- Fixed another bug

Conflicts:
- file1.txt: resolved
- file2.txt: resolved

Git Merge Fast-Forward

La situación más habitual es que tanto la rama main como la feature hayan avanzado, produciéndose un diagrama similar al superior, pero puede ser que la rama master no lo haya hecho, de tal forma que Git puede ejecutar un merge por el método llamado fast-forward, que sería tal que así:

Diagrama de git merge fast-forward

Como se puede entender la rama feature simplemente como un desarrollo lineal con respecto a master, git estima que lo más conveniente y limpio para el historial es simplemente agarrar los commits de feature y ponerlos a continuación de los de master.

No hay nada que resolver, asi que se puede hacer así sin problema.

Squash + merge

El squash es una técnica de Git que se utiliza para fusionar varios commits en un solo commit. La idea de hacer un squash es simplificar la historia de una rama eliminando commits redundantes o que no aporten información relevante. Al hacer un squash, se fusionan los cambios de varios commits en un único commit, manteniendo solo la información relevante.

Este es el aspecto que tendría el grafo después de un merge + squash:

Diagrama de git merge squash

Resumiendo, debes seguir estos pasos:

  1. Asegúrate de que estás en la rama en la que quieres aplicar los cambios. Por ejemplo, si quieres fusionar la rama feature en la rama master, debes ejecutar el comando git checkout master para moverte a la rama master.

  2. Fusiona los cambios de la rama feature en la rama master con el comando git merge --squash feature. Este comando fusionará los cambios de la rama feature en la rama master, pero no creará el commit final.

  3. Revisa los cambios fusionados y modifícalos si es necesario. Puedes usar el comando git status para ver qué archivos han cambiado y el comando git diff para ver los cambios en detalle.

  4. Crea el commit final con el comando git commit. Al ejecutar este comando, se abrirá el editor de texto configurado en tu sistema y podrás escribir el mensaje del commit. Una vez que hayas escrito el mensaje y guardado el archivo, se creará el commit final y se aplicarán los cambios fusionados.

¿Y qué pasa con los conflictos?

Cuando Git no puede fusionar dos ramas de forma automática, se produce un conflicto de fusión. Esto suele ocurrir cuando las dos ramas han modificado el mismo archivo de forma incompatible y Git no sabe cuál de las dos modificaciones deben mantenerse.

Cuando se produce un conflicto de fusión, Git no puede crear el commit final y muestra un mensaje de error similar a este:


error: El commit final de la fusión falló (para más detalles, ejecuta "git merge --abort").
error: Se encontraron conflictos en el archivo '<archivo>'.
error: Por favor, soluciona los conflictos y luego haz un commit manual.

Azure tiene un buen resumen de qué hacer para resolver conflictos en un Merge, pero básicamente los pasos serían los siguientes.

  1. Asegúrate de que estás en la rama en la que se ha producido el conflicto. Por ejemplo, si has intentado fusionar la rama feature en la rama master y se ha producido un conflicto, debes ejecutar el comando git checkout master para moverte a la rama master.

  2. Identifica los archivos en conflicto con el comando git status. Git mostrará una lista de archivos que tienen conflictos y te indicará que debes solucionarlos antes de poder hacer el commit final.

  3. Abre los archivos en conflicto y resuelve las diferencias manualmente. Git añadirá líneas especiales al archivo para indicar el inicio y el fin de los conflictos y para mostrar las modificaciones de cada rama. Por ejemplo, puedes ver algo como esto (ejemplo sacado de Azure):

     
     <<<<<<< HEAD
     Esta es la línea de código de la rama master.
     =======
     Esta es la línea de código de la rama feature.
     >>>>>>> feature
     
     
  4. Elimina las líneas especiales y deja solo la versión correcta del archivo. Una vez que hayas resuelto el conflicto, debes añadir el archivo al área de preparación con el comando git add .

  5. Crea el commit final con el comando git commit. Al ejecutar este comando, se abrirá el editor de texto configurado en tu sistema y podrás escribir el mensaje del commit. Una vez que hayas escrito el mensaje y guardado el archivo, se creará el commit final y se aplicarán los cambios fusionados.

Git rebase

Git rebase es una operación que modifica el commit de origen de otro commit (su padre, base, o como prefieras llamarlo). Se usa, o bien para modificar el origen de una rama como la feature que hemos estado usando, o bien para integrar los cambios de una rama en master, y hacer que aparezcan linealmente.

El comando que se usa, git rebase rama (o git rebase -i rama para el modo interactivo) se podría entender mejor como git rebase ON rama.

Con ejemplos lo entenderás mejor.

Actualizando nuestra rama master local

Alguien ha actualizado la rama master en nuestro repo remoto, y obtenemos los cambios con git pull.

actualizando nuestra rama master local

Modificando el origen de nuestra rama feature

Sin embargo, ahora nuestra rama feature está trabajando sobre un código en origen desactualizado. Es posible que los cambios que se hayan aplicado en la rama master tengamos que aplicarlos en nuestra feature para que, más adelante, nuestro código pueda ser integrado, y evitar conflictos.

O quizá simplemente haya nuevas funcionalidades que otra persona haya implementado, y nos puedan ser útiles, quién sabe.

Asi que queremos incorporar esos cambios en nuestra rama feature, sobre la que estamos trabajando.

Modificando el origen de nuestra rama feature con rebase

Integrando nuestra rama feature de forma lineal en master

OJO: Modificación del historial: Se recomienda el uso de Merge en su lugar.

Ahora nuestra rama feature ya tiene como commit padre el último commit de master. Puede que queramos seguir con el desarrollo, pero pongamos que la vida nos sonríe y no necesitamos desarrollar nada más en nuestra feature, y que además no existen sorpresas.

Nos queda simplemente integrar feature en master. Aunque mucha gente no recomienda hacer esto, usaremos también rebase para ello.

Integrando feature en master

Ahora podemos ejecutar un git push y dejar nuestra rama master en el repo remoto igual. Si estás trabajando con alguien más, pregunta, porque la puedes liar.

Herramientas para aprender Git visualmente

Más referencias