Advertisement
  1. Code
  2. Git

Reescribiendo la Historia con Git Rebase

Scroll to top
Read Time: 9 min

() translation by (you can also view the original English article)

En el flujo fundamental de Git, tu desarrollas una nueva característica en una rama dedicada a eso, luego la mergeas a la rama de producción una vez que está finalizada. Eso hace de git merge una herramienta integral para combinar ramas. Sin embargo, no es lo único que ofrece Git.

Combining branches by merging them togetherCombining branches by merging them togetherCombining branches by merging them together
Combining branches by merging them together

Como una alternativa al escenario de arriba, pudieras combinar las ramas con el comando git rebase. En lugar de enlazar las ramas con un commit de merge, el hacer rebase a una rama mueve completamente la rama con la nueva característica hacia la punta de master como se muestra abajo.

Combining branches with git rebaseCombining branches with git rebaseCombining branches with git rebase
Combining branches with git rebase

Esto sirve para el mismo propósito que git merge, la integración de commits de diferentes ramas. Pero hay dos razones por las que quisiéramos optar por un rebase sobre un merge:

  • Resulta en una historia lineal del proyecto.
  • Nos da la oportunidad de limpiar commits locales.

En este tutorial, exploraremos estas dos casos comunes de git rebase. Desafortunadamente, los beneficios de git rebase vienen con una transigencia. Cuando es usado incorrectamente, puede ser una de las operaciones más peligrosas que se pueden realizar en un repositorio Git. Así que, también estaremos tomando una mirada cuidadosa a los peligros de hacer rebase.

Prerrequisitos

Este tutorial asume que estás familiarizado con los comandos básicos de Git y flujos de colaboración. Debes sentirte cómodo enviando tus cambios al área de preparación (staging), commiteando instantáneas, desarrollando características en ramas aisladas, mergeando ramas, y empujando/jalando ramas hacia/desde repositorios remotos.

1. Haciendo Rebase para una historia lineal

El primer caso de uso que exploraremos envuelve una historia de proyecto divergente. Considera un repositorio donde tu rama de producción se ha movido hacia adelante mientras se está desarrollando una nueva característica:

Developing a feature on a feature branchDeveloping a feature on a feature branchDeveloping a feature on a feature branch

Para hacer un rebase en la rama feature de la rama master, correrías los siguientes comandos:

1
git checkout feature
2
git rebase master

Esto trasplanta la rama feature desde su locación actual hacia la punta de la rama master:

Transplanting the feature branch to the tip of the master branchTransplanting the feature branch to the tip of the master branchTransplanting the feature branch to the tip of the master branch

Hay dos escenarios en los que querrías hacer esto. Primero, si la nueva característica depende de nuevos commits en master, ahora tendría acceso a ellos. Segundo, si la nueva característica fue completada, ahora sería preparada para un merge de avance rápido (fast-forward) hacia master. En ambos casos, hacer rebase resulta en una historia lineal, donde git merge resultaría en commits de merge innecesarios.

Por ejemplo, considera que pasaría si se integrarán los commits más recientes con un merge en lugar de hacer un rebase:

1
git checkout feature
2
git merge master

Esto nos daría un commit de merge extra en la rama feature. Más aún, esto pasaría cada vez que quisiéramos incorporar commits más recientes en la rama de la nueva característica. Eventualmente, la historia del proyecto estaría llena de commits de merge sin significado.

Integrating upstream commits with a mergeIntegrating upstream commits with a mergeIntegrating upstream commits with a merge
Integrating upstream commits with a merge

Este mismo beneficio se puede ver cuando se mergea en la otra dirección. Sin hacer un rebase, integrar la rama feature terminada en master requiere un commit de merge. Mientras este es un commit de merge con significado (en el sentido de que representa una nueva característica completada), la historia resultante está llena de bifurcaciones:

Integrating a completed feature with a mergeIntegrating a completed feature with a mergeIntegrating a completed feature with a merge
Integrating a completed feature with a merge

Cuando haces un rebase antes de mergear, Git puede hacer un avance rápido de master hacia la punta de feature. Encontrarás una historia lineal de cómo tu proyecto ha progresado en la salida de git log —los commits en feature están pulcramente agrupados encima de los commits en master. Esto no es necesariamente el caso cuando las ramas son enlazadas con un commit de merge.

Rebasing before mergingRebasing before mergingRebasing before merging
Rebasing before merging

Resolviendo Conflictos

Cuando corres git rebase, , Git toma cada commit en la rama y los mueve, uno por uno, hacia la nueva base. Si uno de estos commits alteran las misma líneas de código que los commits más recientes, resultará en un conflicto.

El comando git merge te deja resolver todos los conflictos de la rama al final del merge, el cual es uno de los propósitos primarios de un commit de merge. Sin embargo, funciona un poco diferente cuando haces un rebase. Los conflictos son resueltos a base de commits individuales. Así que, si git rebase encuentra un conflicto, detendrá el procedimiento de rebase y mostrará un mensaje de advertencia:

1
Auto-merging readme.txt
2
CONFLICT (content): Merge conflict in readme.txt
3
Failed to merge in the changes.
4
....
5
When you have resolved this problem, run "git rebase --continue".
6
If you prefer to skip this patch, run "git rebase --skip" instead.
7
To check out the original branch and stop rebasing, run "git rebase --abort".

Visualmente, así es como se ve la historia de tu proyecto cuando git rebase encuentra un conflicto:

Los conflictos pueden ser inspeccionados corriendo git status. La salida del comando se ve muy similar a un conflicto de merge.

1
Unmerged paths:
2
  (use "git reset HEAD <file>..." to unstage)
3
  (use "git add <file>..." to mark resolution)
4
5
both modified:   readme.txt
6
7
no changes added to commit (use "git add" and/or "git commit -a")

Para resolver el conflicto, abre el archivo con conflicto (reader.txt en el ejemplo de arriba), encuentra las líneas afectadas, y manualmente editalas para obtener el resultado deseado. Luego, dile a Git que el conflicto está resuelto enviando el archivo al área de espera (staging):

1
git add readme.txt

Nota que esta es la misma manera en la que marcas un conflicto con git merge como resuelto. Pero recuerda que estas en la mitad de un proceso de rebase —no querrás olvidarte del resto de los commits que necesitan ser movidos. El último paso es decirle a Gt que termine el rebase con la opción --continue:

1
git rebase --continue

Esto moverá el resto de los commits, uno por uno, y si algún otro conflicto surge, tendrás que repetir este proceso de nuevo.

Si no quieres resolver conflictos, puedes optar por cualquiera de las banderas --skip ó --abort. Esta última es particularmente útil si no tienes idea que esta pasando y solo quieres regresar a lo seguridad.

1
# Ignora el commit que causó el conflicto

2
git rebase --skip
3
4
# Aborta completamente el rebase y vuelve al estado inicial

5
git rebase --abort

2. Haciendo rebase para limpiar commits locales

Hasta ahora, solo hemos usado git rebase para mover ramas, pero es mucho más poderoso que eso. Pasándole la bandera -i, puedes empezar a hacer una sesión de rebase interactiva. El rebase interactivo te deja definir precisamente como cada commit será movido hacia la nueva base. Esto te da la oportunidad de limpiar la historia de una rama de característica nueva antes de compartirla con otros desarrolladores.

Por ejemplo, digamos que terminaste de trabajar en tu rama feature y estas listo para integrarla a master. Para comenzar una sesión de rebase interactiva, corre el siguiente comando:

1
git checkout feature
2
git rebase -i master

Esto abrirá un editor conteniendo todos los commits en feature que están a punto de ser movidos:

1
pick 5c43c2b [Descripción para el commit más viejo]
2
pick b8f3240 [Descripción para el segundo commit más viejo]
3
pick c069f4a [Descripción para el commit más reciente]

Esta lista define cómo es que la rama feature va a verse después del rebase. Cada línea representa un commit y el comando pick antes de cada hash de commit define que es lo que va a pasar durante el rebase. Noda que los commits están listados desde el más viejo al más reciente. Alterando este listado, ganas control completo sobre la historia del proyecto.

Si quieres cambiar el orden de los commits, simplemente reordena las líneas. Si quieres cambiar el mensaje de un commit, usa el comando reword. Si quieres combinar dos commits, cambia el comando pick por squash. Esto desplegará todos los cambios en ese commit en el commit de arriba. Por ejemplo, si aplicaste un squash a el segundo commit en la lista de arriba, la rama feature se verá como la siguiente después de guardar y cerrar el editor:

Squashing the 2nd commit with an interactive rebaseSquashing the 2nd commit with an interactive rebaseSquashing the 2nd commit with an interactive rebase
Squashing the 2nd commit with an interactive rebase

El comando  edit es particularmente poderoso. Cuando alcanza el commit especificado, Git pausará el procedimiento de rebase, muy parecido a como cuando encuentra un conflicto. Esto te da la oportunidad de alterar el contenido del commit con git commit --amend o incluso agregar más commit con los comandos estándar git add/git commit. Cualquier commit nuevo que agregues será parte de la nueva rama.

Hacer un rebases interactivos puede tener un impacto profundo en tu flujo de trabajo. En lugar de preocuparte por romper cambios encapsulados en commits, puedes enfocarte en escribir código. Si terminas commiteando algo que debería ser un solo cambio en diferentes instantáneas, entonces ya no es un problema —reescribe la historia con git rebase -i y aplicales un squash a todos para tener un commit más significativo.

3. Peligros de hacer rebase

Ahora que tienes un entendimiento de git rebase, podemos hablar de cuándo no usarlo. Internamente, hacer un rebase no mueve los commits en una nueva rama. En su lugar, crea nuevos commits que contienen los cambios deseados. Con esto en mente, hacer un rebase se puede visualizar mejor como lo siguiente:

Después de hacer un rebase, los commits en feature tendrán diferentes hashes. Esto significa que no solo posicionamos una rama —literalmente, reescribimos la historia del proyecto. Esto es un efecto secundario muy importante de git rebase.

Cuando estás trabajando solo en un proyecto, reescribir la historia no es asunto importante. Sin embargo, tan pronto como empiezas a trabajar en un ambiente colaborativo, puede convertirse en algo peligroso. Si reescribes commits que otros desarrolladores están usando (e.g commits en la rama master), se verá como si esos commits hayan desaparecido la próxima vez que ellos traten de jalar tu trabajo. Esto resulta en un escenario confuso del cual es difícil recuperarse.

Con esto en mente, nunca deberías hacer un rebase de commits que han sido empujados a un repositorio público a menos que estés seguro que nadie ha basado su trabajo en ellos.

Conclusión

Este tutorial introdujo los dos usos más comunes de git rebase. Hablamos mucho acerca de mover ramas, pero ten en cuenta que hacer rebase realmente se trata de controlar la historia de tu proyecto. El poder de reescribir commits después de los hechos te libera para enfocarte en tus tareas de desarrollo en lugar de separar tu trabajo en instantáneas.

Nota que hacer rebase es totalmente un agregado opcional a tu caja de herramientas de Git. Puedes hacer todo lo que necesitas con comandos git merge. De hecho, esto es más seguro, ya que evita la posibilidad de reescribir la historia pública. Sin embargo, si entiendes los riesgos, git rebase puede ser una forma mucha más limpia de integrar ramas comparado con commits de merge.

¡Sé el primero en conocer las nuevas traducciones–sigue @tutsplus_es en Twitter!

Advertisement
Did you find this post useful?
Want a weekly email summary?
Subscribe below and we’ll send you a weekly email summary of all new Code tutorials. Never miss out on learning about the next big thing.
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.