git rebase

Este documento servirá para tratar en profundidad el comando git rebase. El comando Rebase también se ha explicado en las páginas Configuración de un repositorio y Reescritura del historial. Esta página analizará con más detalle la configuración y ejecución de git rebase. Aquí se cubrirán los casos de uso y los peligros habituales de Rebase.

Rebase es una de las dos utilidades Git que se especializa en integrar cambios de una rama a otra. La otra utilidad de integración de cambios es git merge. Merge es siempre un registro de cambios de avance. Como alternativa, rebase tiene potentes funciones de rescritura del historial. Para examinar detalladamente la opción Merge frente a Rebase, consulta nuestra guía Fusión frente a reorganización. El propio Rebase tiene 2 modos principales: modo "manual" e "interactivo". Analizaremos los diferentes modos de Rebase más detalladamente a continuación.

¿Qué es git rebase?

La reorganización es el proceso de mover o combinar una secuencia de confirmaciones en una nueva confirmación base. La reorganización es muy útil y se visualiza fácilmente en el contexto de un flujo de trabajo de ramas de funciones. El proceso general se puede visualizar de la siguiente manera:

Tutorial de Git: Git rebase

Desde una perspectiva del contenido, la reorganización consiste en cambiar la base de tu rama de una confirmación a otra haciendo que parezca que has creado la rama desde una confirmación diferente. Internamente, Git lo hace creando nuevas confirmaciones y aplicándolas a la base especificada. Es muy importante entender que, aunque la rama parece la misma, se compone de nuevas confirmaciones por completo.

Uso

La razón principal para reorganizar es mantener un historial del proyecto lineal. Por ejemplo, piensa en una situación en la que la rama maestra ha progresado desde que empezaste a trabajar en una rama de función. Quieres incorporar las últimas actualizaciones de la rama maestra en tu rama de función, pero quieres mantener el historial de la rama limpio para que parezca que has estado trabajando a partir de la rama maestra más reciente. Esto proporciona el beneficio posterior de una fusión limpia de la rama de función de nuevo a la rama maestra. ¿Por qué queremos mantener un "historial limpio"? Los beneficios de tener un historial limpio se vuelven evidentes cuando se realizan operaciones de Git para investigar la introducción de una regresión. Un escenario más realista sería el siguiente:

  1. Se identifica un error en la rama maestra. Una función que funcionaba perfectamente ahora falla.
  2. Un desarrollador examina el historial de la rama maestra usando git log. Debido al "historial limpio", el desarrollador puede razonar rápidamente sobre el historial del proyecto.
  3. El desarrollador no puede identificar cuando se introdujo el error usando git log por lo que ejecuta un git bisect.
  4. Como el historial de git está limpio, git bisect tiene un conjunto perfeccionado de confirmaciones para comparar cuando se busca la regresión. El desarrollador encuentra rápidamente la confirmación que introdujo el error y puede actuar en consecuencia.

Obtén más información sobre git log y git bisect en sus páginas de uso individuales.

Tienes dos opciones para integrar tu función en la rama maestra: fusionar directamente o reorganizar y después fusionar. La primera opción da como resultado una fusión a 3 bandas y una confirmación de fusión, mientras que la segunda resulta en una fusión con avance rápido y una historia perfectamente lineal. El siguiente diagrama demuestra cómo reorganizar en la rama maestra facilita una fusión con avance rápido.

Git rebase: rama en la maestra

Reorganizar es una forma habitual de integrar cambios de nivel superior en tu repositorio local. Si se incorporan cambios de nivel superior con Git merge, se produce una confirmación de fusión superflua cada vez que quieres ver cómo ha progresado el proyecto. Por otra parte, reorganizar es como decir “Quiero basar mis cambios en lo que ya han hecho todos”.

No reorganices el historial público

Como hemos explicado anteriormente en Reescritura del historial, nunca debes reorganizar las confirmaciones una vez que se hayan enviado a un repositorio público. La reorganización sustituiría las confirmaciones antiguas por las nuevas y parecería que esa parte del historial de tu proyecto se hubiera desvanecido de repente.

Git Rebase estándar frente Git Rebase interactivo

Git rebase interactivo es cuando git rebase acepta un argumento -- i. Significa "Interactivo". Sin ningún argumento, el comando se ejecuta en modo estándar. En ambos casos, vamos a asumir que hemos creado una rama de función aparte.

# Create a feature branch based off of master
git checkout -b feature_branch master
# Edit files
git commit -a -m "Adds new feature"

Git rebase en modo estándar tomará automáticamente las confirmaciones de tu rama de trabajo actual y las aplicará al encabezado de la rama anterior.

git rebase <base>

 

Esto reorganiza automáticamente la rama actual en <base>, que puede ser cualquier tipo de referencia de confirmación (por ejemplo, un ID, un nombre de rama, una etiqueta o una referencia relativa a HEAD).

Ejecutar git rebase con la marca -i inicia una sesión de reorganización interactiva. En vez de mover de forma ciega todas las confirmaciones a la nueva base, la reorganización interactiva te da la oportunidad de alterar las confirmaciones individuales en el proceso. Eso te permite limpiar el historial eliminando, dividiendo y alterando una serie existente de confirmaciones. Es como Git commit --amend a la máxima potencia.

git rebase --interactive <base>

 

Esto reorganiza la rama actual en <base>, pero utiliza una sesión de reorganización interactiva. Se abre un editor en el que puedes introducir comandos (descritos a continuación) para cada confirmación que se reorganizará. Estos comandos determinan cómo se transferirán las confirmaciones individuales a la nueva base. Puedes reordenar también el listado de confirmaciones para cambiar el orden de las propias confirmaciones. Una vez que hayas especificado comandos para cada confirmación en la reorganización, Git empezará a realizar confirmaciones aplicando los comandos de reorganización. Los comandos de edición de reorganización son los siguientes:


pick 2231360 some old commit
pick ee2adc2 Adds new feature
# Rebase 2cf755d..ee2adc2 onto 2cf755d (9 commands)
#
# Commands:
# p, pick = use commit
# r, reword = use commit, but edit the commit message
# e, edit = use commit, but stop for amending
# s, squash = use commit, but meld into previous commit
# f, fixup = like "squash", but discard this commit's log message
# x, exec = run command (the rest of the line) using shell
# d, drop = remove commit

Comandos de reorganización adicionales

Tal y como se explica en la página Reescritura del historial, la reorganización se puede usar para cambiar las confirmaciones más antiguas y múltiples, los archivos confirmados y múltiples mensajes. Aunque estas son las aplicaciones más comunes, git rebase también tiene opciones de comando adicionales que pueden ser útiles en aplicaciones más complejas.

  • git rebase -- d significa que durante la reorganización la confirmación se descartará del bloque de confirmación combinado final.
  • git rebase -- p deja la confirmación como está. No modificará el mensaje ni el contenido de la confirmación y seguirá siendo una confirmación individual en el historial de ramas.
  • git rebase -- x durante la reorganización ejecuta un guion de shell de línea de comandos en cada confirmación marcada. Un ejemplo útil sería ejecutar el conjunto de pruebas de tu código base en confirmaciones específicas, lo que podría ayudar a identificar regresiones durante una reorganización.

Resumen

La reorganización interactiva te da control completo sobre el aspecto del historial de tu proyecto. Esto les confiere mucha libertad a los desarrolladores, porque les permite confirmar un historial "desordenado" mientras se centran en escribir código para después volver y limpiarlo tras eso.

A la mayoría de desarrolladores les gusta usar una reorganización interactiva para pulir una rama de función antes de fusionarla en la base de código principal. Esto les da la oportunidad de combinar confirmaciones insignificantes, eliminar las obsoletas y asegurarse de que todo lo demás está en orden antes de confirmar el historial del proyecto "oficial". Para todos los demás, parecerá como si toda la función se hubiera desarrollado en una única serie de confirmaciones bien planificadas.

El poder real de la reorganización interactiva se puede ver en el historial de la rama maestra resultante. Los demás creerán que eres un desarrollador brillante que implementó la nueva función con la cantidad perfecta de confirmaciones la primera vez. Así es como la reorganización interactiva puede mantener el historial de un proyecto limpio y significativo.

Opciones de configuración

Hay unas pocas propiedades de reorganización que pueden configurarse mediante git config. Estas opciones alterarán el aspecto del resultado de git rebase.

  • rebase.stat: un booleano que está definido como falso de manera predeterminada. La opción activa y desactiva la visualización del contenido diffstat visual que muestra qué ha cambiado desde la última reorganización.
  • rebase.autoSquash: un valor booleano que activa y desactiva el comportamiento --autosquash.
  • rebase.missingCommitsCheck: se puede definir en múltiples valores que cambian el comportamiento de reorganización acerca de las confirmaciones que faltan.
warn Imprime el resultado de advertencia en modo interactivo que advierte de las confirmaciones eliminadas.

error

Detiene la reorganización e imprime los mensajes de advertencia de confirmación eliminados.

ignore

Definido de forma predeterminada, ignora las advertencias de confirmación que faltan.
  • rebase.instructionFormat: una cadena con formato git log que se utilizará para formatear la visualización de reorganización interactiva.

Aplicación de reorganización avanzada

El argumento de la línea de comandos  --onto puede pasarse a git rebase. En el modo --onto de git rebase, el comando se expande a:

 git rebase --onto <newbase> <oldbase>

El comando --onto permite una forma más potente de reorganización que permite pasar referencias específicas para que estén en los extremos de una reorganización.
Digamos que tenemos un repositorio de ejemplo con ramas como las siguientes:


   o---o---o---o---o master
        \
         o---o---o---o---o featureA
              \
               o---o---o featureB

 

featureB se basa en featureA. Sin embargo, nos damos cuenta de que featureB no depende de ninguno de los cambios de featureA y podría simplemente separarse de la maestra.

 git rebase --onto master featureA featureB

featureA es la <oldbase>. master se convierte en la <newbase> y featureB es la referencia para qué HEAD de la <newbase> apuntará. Entonces los resultados son:


                      o---o---o featureB
                     /
    o---o---o---o---o master
     \
      o---o---o---o---o featureA

Comprensión de los peligros de la reorganización

Una advertencia que hay que tener en cuenta al trabajar con Git Rebase es que los conflictos de fusión pueden llegar a ser más frecuentes durante un flujo de trabajo de reorganización. Esto ocurre si tienes una rama longeva que se ha desviado de la maestra. En algún momento querrás reorganizar con respecto a la maestra y, en ese momento, podría contener muchas nuevas confirmaciones con las que podrían entrar en conflicto tus cambios de la rama. Esto se soluciona fácilmente reorganizando tu rama con respecto a la maestra y realizando confirmaciones con más frecuencia. Los argumentos de la línea de comandos --continue y --abort pueden pasarse a git rebase para hacer avanzar o restablecer la reorganización cuando se abordan conflictos.

Una advertencia de reorganización más seria son las confirmaciones perdidas de la reescritura del historial interactiva. Si se ejecuta una reorganización en modo interactivo y se ejecutan subcomandos como squash o drop, eliminará las confirmaciones del registro inmediato de tu rama. A primera vista puede parecer como si las confirmaciones hubieran desaparecido para siempre. Mediante git reflog estas confirmaciones pueden restaurarse y toda la reorganización puede deshacerse. Para obtener más información sobre cómo usar git reflog para encontrar confirmaciones perdidas, visita nuestra página de documentación de Git reflog.

Git Rebase en sí mismo no es muy peligroso. Los casos peligrosos de verdad se producen cuando se ejecuta una reorganización interactiva de reescritura del historial y se envían de forma forzada los resultados a una rama remota que está compartida con otros usuarios. Este patrón debería evitarse, porque tiene la capacidad de sobrescribir el trabajo de otros usuarios remotos cuando incorporan cambios.

Recuperación de una reorganización de nivel superior

Si otro usuario ha reorganizado y ha realizado un envío forzado a la rama a la que estás confirmando, un git pull sobrescribirá cualquier confirmación que hayas basado en la rama anterior con el extremo que se envió de forma forzada. Afortunadamente, usando git reflog puedes obtener el registro de referencia de la rama remota. En el registro de referencia de la rama remota, puedes encontrar una referencia antes de que se reorganizara. Entonces puedes reorganizar tu rama con respecto a esa referencia remota utilizando la opción --onto tal y como se ha explicado antes en la sección Aplicación de reorganización avanzada.

Resumen

En este artículo hemos tratado el uso de git rebase. Hemos explicado casos de uso básicos y avanzados, así como ejemplos más avanzados. Algunos puntos de discusión clave son los siguientes:

  • modos de git rebase estándar frente a interactivo
  • opciones de configuración de git rebase
  • comando --onto de git rebase
  • confirmaciones perdidas de git rebase

Hemos analizado el uso de git rebase con otras herramientas como git reflog, git fetch y git push. Visita las páginas correspondientes para obtener más información.

¿Listo para aprender a usar Git?

Prueba este tutorial interactivo.

Comienza ahora