Git, de forma sencilla: cómo cambiar el historial con rebase—parte I
Or Kamara
9 de junio de 2020
0 minutos de lecturaÚltimamente, estaba trabajando en una funcionalidad grande que incluía varios cambios en distintas áreas del código. Para que el PR de esta funcionalidad fuera fácil de revisar, dividí toda la funcionalidad en varios commits. Con el tiempo, me di cuenta rápidamente de que esas divisiones no eran suficientes: tenía que hacer muchos cambios en cada uno de esos commits (por algunos problemas que encontré o correcciones de CR).
En ese momento, ya conocía los fundamentos del mágico comando interactivo git rebase y sabía que podías usarlo para «reescribir tu historial». Pero los cambios que tenía que hacer eran más complicados de lo que sabía, así que empecé a explorar las opciones de este comando.
Para quienes no conocen el comando, este es el resultado de ejecutar git rebase -i BRANCH:
Recuerdo perfectamente la primera vez que ejecuté este comando. Mi reacción fue algo así: Bueno, ¿qué está pasando aquí? Alguien me había prometido una forma interactiva y sencilla de cambiar mis commits, pero lo único que veía era una interfaz muy complicada… Literalmente tuve que dedicar tiempo a leer sobre cada una de esas opciones, porque al salir de esta pantalla podía perder todo el trabajo que había hecho (#DesarrolladorAsustado).
Incluso cuando intenté salir de la pantalla y cancelar todo, no fue una experiencia sencilla… Tuve que buscar en Google cómo hacerlo y descubrí que tenía que eliminar todas las líneas de commits para que no hubiera cambios. Así de fácil (y poco elegante). Lo primero que pensé fue: «Pero espera, no confío del todo en todo lo que dice Google. ¿Y si lo hago y desaparece todo mi trabajo?»
Entonces decidí que no había otra opción y empecé a investigar esos comandos.
En este artículo, quiero explicar distintos problemas que los desarrolladores deben resolver al trabajar en PR con varios commits. No explicaré cada detalle de esos comandos, ya que hay varios artículos que explican qué sucede tras bambalinas. En cambio, te presentaré algunas situaciones que cualquiera de nosotros podría encontrar al trabajar con git y sugeriré la solución adecuada con git rebase -i.
Como este tema es bastante extenso, dividiré esta publicación en dos partes. En esta primera parte repasaré los fundamentos de Rebase y dos situaciones comunes. En la segunda parte, presentaré otras cuatro situaciones comunes y algunas conclusiones.
¡Sin más preámbulos, empecemos con la primera parte!
Rebase en pocas palabras
Empecemos por la siguiente situación: tú y un amigo trabajan en un nuevo proyecto emocionante. Acabas de crear el repositorio (y hacer un primer commit llamado A) y ahora es hora de programar. Para trabajar de forma más eficiente, decidieron dividirse las tareas. Tú te encargas de la funcionalidad principal y tu amigo de crear la estructura de la aplicación.
Así que ambos parten del mismo punto: el commit A. Después de 5 horas de trabajo intenso, quieres terminar y combinar los cambios con master. Tu amigo es el primero en combinar sus cambios y crea 3 commits nuevos sobre A: B + C + D. Ahora es tu turno de combinar con master todo tu trabajo de la FeatureBranch: los commits E + F + G. Así se ve:
Para probar la funcionalidad que creaste, necesitas usar algunas funciones del framework, más específicamente, las del commit D. En otras palabras, necesitas cambiar la base de tu rama del commit A al commit D, como si hubieras creado tu rama desde la versión más reciente:
Este flujo explica exactamente por qué se necesita git rebase: para mover una secuencia de commits a un nuevo commit base. Tras bambalinas, git crea commits nuevos y los aplica a la nueva base especificada.
Más específicamente, en este artículo hablaremos del modo interactivo.
Al ejecutar rebase con la opción -i, puedes modificar commits individuales como parte del proceso básico de rebase. ¡Aquí es donde empieza la magia! En el modo interactivo, puedes elegir qué acción quieres realizar en cada uno de tus commits: combinar, dividir, cambiar el nombre, reordenar y más.
Intentaremos entender cada una de estas opciones con situaciones comunes en las que git rebase puede ser útil.
Situaciones comunes (¡y cómo resolverlas!)
Veamos algunas situaciones comunes en las que podrías querer cambiar el historial de tu código y cómo hacerlo con Git:
1. Cambiar el mensaje de un commit anterior (reword)
Hicimos varios cambios en el código y los dividimos perfectamente en distintos commits, pero luego notamos que hay un error tipográfico en uno de los mensajes de commit:
Como probablemente ves, el mensaje del segundo commit es 3nd commit (ups); vamos a corregirlo.
Si el error tipográfico estuviera en el último commit, podríamos haber usado el comando git commit --amend. Pero como está en medio del grafo de git, necesitamos hacer algo más complicado.
En este caso, usaremos el comando git rebase -i master (master puede ser otra rama) y elegiremos la opción reword para el segundo commit (a21bd1d):
Después de guardar el archivo, se abrirá un nuevo editor con la opción de editar el mensaje del commit:
Lo siguiente es cambiar la primera línea por el nuevo mensaje de commit que queremos, feat: 2nd commit, y volver a guardar.
El grafo de git ya está corregido:
2. Cambiar el contenido de un commit anterior (squash, fixup)
Acabas de recibir comentarios de CR de tu compañero y tienes que hacer algunos cambios pequeños: agregar un archivo nuevo y cambiar el contenido de uno existente.
Como probablemente sabes, cada commit debería representar un bloque de lógica, no solo cambios en archivos. Una de las cosas que debemos evitar es tener mensajes de commit como CR fixes o, en nuestro caso, incluso really small CR fixes. Lo ideal es combinar todos los commits para que, al final, cada commit incluya todos los cambios pertinentes (incluidas las correcciones de CR).
¿Cómo lo hacemos? Para eso sirven las opciones squash y fixup.
Primero, debemos crear commits temporales con los cambios necesarios:
En el ejemplo anterior, creamos 2 commits nuevos:
5641a86: cambia el contenido del archivo que creamos en el primer commit17a73c1: agrega un archivo nuevo al segundo commit
Pero ahora queremos evitar que el grafo de git quede así, y combinar 5641a86 con 271c66e y 17a73c1 con e2952e7. Para eso, debemos ejecutar el comando git rebase -i master. Como la vez anterior, esta es la primera pantalla que veremos:
Ahora, cambiemos el orden de las líneas según el orden deseado de los commits y usemos la opción squash en los commits 5641a86 y 17a73c1:
Como resultado de estos cambios, git empezará a procesar los commits (desde 271c66e hasta fdb5f71) y combinará el contenido de los commits marcados con squash con el commit anterior. ¡El grafo de git ahora se ve exactamente como queríamos!
Otra opción que podemos usar en lugar de squash es fixup. Ambas hacen prácticamente lo mismo, excepto que fixup descarta el mensaje del commit.
Una nota importante: a veces, al combinar commits, podemos provocar conflictos en el código. Cuando git detecta conflictos, te pedirá que los resuelvas, prepares los cambios y continúes el proceso de rebase con git rebase --continue. Recuerda que siempre puedes ejecutar git rebase --abort si no tienes claro qué hacer. Este comando detiene el proceso de rebase y devuelve el repositorio a su estado original (antes de ejecutar git rebase -i).
Para terminar
¡Eso es todo por la primera parte sobre cómo cambiar el historial de git con Rebase! En la segunda parte, veremos otras cuatro situaciones comunes y sugeriremos cómo resolverlas.
Comienza con Capture the Flag
Aprende a resolver desafíos de Capture the Flag con nuestro taller virtual 101 a pedido.
