Un historial de Git ordenado y lineal

Una de las cosas que se pasan por alto en muchos proyectos basados en Git es el valor de un historial de confirmaciones lineal. De hecho, muchas herramientas y directrices desalientan los flujos de trabajo de Git que buscan un historial lineal. Me parece triste, ya que una historia ordenada es muy valiosa, y hay un flujo de trabajo directo que garantiza una historia lineal.

Historial lineal vs no lineal

Un historial lineal es simplemente un historial de Git en el que todas las confirmaciones vienen una tras otra. I. e. no encontrará ninguna fusión de ramas con historiales de confirmaciones independientes.

1 - no lineal vs lineal

¿Por qué desea un historial lineal?

Además de ser ordenado y lógico, una historia lineal es útil cuando:

  • Mirando la historia. Una historia no lineal puede ser muy difícil de seguir, a veces hasta el punto de que la historia es simplemente incomprensible.
  • Cambios de retroceso. Por ejemplo: “¿Se introdujo la característica A antes o después de la corrección de error B?”.
  • Rastreando errores. Git tiene una función muy cuidada llamada Git bisect, que se puede usar para encontrar rápidamente qué confirmación introdujo un error o regresión. Sin embargo, con un historial no lineal, Git bisect se vuelve difícil o incluso imposible de usar.
  • Revertir cambios. Supongamos que ha encontrado un commit que causó una regresión, o que desea eliminar una característica que se suponía que no debía salir en una versión específica. Con un poco de suerte(si su código no ha cambiado demasiado), simplemente puede revertir la (s) confirmación (es) no deseada (s) utilizando Git revert. Sin embargo, si tiene un historial no lineal, tal vez con muchas fusiones de ramas cruzadas, esto será significativamente más difícil.

Probablemente haya otro puñado de situaciones en las que un historial lineal es muy valioso, dependiendo de cómo uses Git.

El punto es: Cuanto menos lineal sea tu historial, menos valioso será.

Causas de un historial no lineal

En resumen, cada confirmación de fusión es una fuente potencial de un historial no lineal. Sin embargo, hay diferentes tipos de confirmaciones de fusión.

Combinación de una rama en master

Cuando haya terminado con el tema de la rama y se quieren integrar en maestro, un método común es la fusión de la rama en maestro. Tal vez algo parecido:

git checkout mastergit pullgit merge --no-ff my-topic-branch

Una buena propiedad con este método es que conservas la información sobre qué confirmaciones formaban parte de tu rama temática (una alternativa sería omitir “–no-ff”, lo que permitiría a Git hacer un avance rápido en lugar de una fusión, en cuyo caso podría no estar tan claro cuál de las confirmaciones pertenecía realmente a tu rama temática).

El problema con la fusión a maestro surge cuando su rama temática se basa en un maestro antiguo en lugar de la última punta de maestro. En este caso, inevitablemente obtendrá una historia no lineal.

2 - merging-old-branch

Que esto sea o no un problema común depende en gran medida de cuán activo esté el repositorio Git, cuántos desarrolladores estén trabajando simultáneamente, etc.

Fusionar desde el maestro en una rama temática

A veces desea actualizar su rama para que coincida con la última rama maestra (p. ej. hay algunas características nuevas en master que desea incluir en su rama temática, o encuentra que no puede fusionar su rama temática en master porque hay conflictos).

Un método común, que incluso es recomendado por algunos, es combinar la punta de maestro en su rama de tema. ¡Esta es una fuente importante de historia no lineal!

3 - fusionar maestro en tema

La solución: Rebase!

Git rebase es una función muy útil que deberías usar si quieres un historial lineal. A algunos les resulta incómodo el concepto de rebase, pero en realidad es bastante simple: reproduce los cambios (confirmaciones) en tu rama encima de una nueva confirmación.

Por ejemplo, puedes usar Git rebase para cambiar la raíz de tu rama de tema de un maestro antiguo a la punta del maestro más reciente. Asumiendo que tienes tu rama de tema comprobada, puedes hacer lo siguiente::

git fetch origingit rebase origin/master

4 - cambio de base

Tenga en cuenta que la operación de fusión correspondiente sería fusionar la punta del maestro en su rama temática (como se muestra en la figura anterior). El contenido resultante de los archivos de tu rama de tema sería el mismo, independientemente de si haces un rebase o una fusión. Sin embargo, la historia es diferente (lineal vs no lineal!).

Esto suena muy bien. Sin embargo, hay un par de advertencias con rebase que debe tener en cuenta.

Advertencia 1: Rebase crea nuevas confirmaciones

Rebase una rama realmente creará nuevas confirmaciones. Las nuevas confirmaciones tendrán diferentes SHA: s que las confirmaciones antiguas. Esto generalmente no es un problema, pero se encontrará con problemas si rebase una rama que existe fuera de su repositorio local (por ejemplo, si su rama ya existe en origin).

Si tuviera que enviar una rama rebasada a un repositorio remoto que ya contiene la misma rama (pero con el historial antiguo), lo haría:

  1. Tienes que forzar la inserción de la rama (por ejemplo, git push --force-with-lease), ya que Git no te permitirá insertar un nuevo historial en una rama existente de otro modo. Esto reemplaza efectivamente el historial de la rama remota dada con un nuevo historial.
  2. Posiblemente haga que alguien más esté muy descontento, ya que su versión local de la rama dada ya no coincide con la rama en el control remoto, lo que puede provocar todo tipo de problemas.

En general, evite sobrescribir el historial de una rama en un control remoto (la única excepción es reescribir el historial de una rama que está en revisión de código, dependiendo de cómo funcione el sistema de revisión de código, pero esa es una discusión diferente).

Si necesita cambiar la base de una rama que se comparte con otros en un control remoto, un flujo de trabajo sencillo consiste en crear una nueva rama que debe cambiar de base, en lugar de cambiar la base de la rama original. Asumiendo que tiene my-topic-branch retirado, puede hacer:

git checkout -b my-topic-branch-2git fetch origingit rebase origin/mastergit push -u origin my-topic-branch-2

…y luego dile a las personas que trabajan en my-topic-branch que continúen trabajando en my-topic-branch-2 en su lugar. La rama antigua es entonces efectivamente obsoleta, y no debe volver a fusionarse con master.

Advertencia 2: Resolver conflictos en una rebase puede ser más trabajo que en una fusión

Si obtiene un conflicto en una operación de fusión, resolverá todos los conflictos como parte de esa confirmación de fusión.

Sin embargo, en una operación de rebase, puede potencialmente obtener un conflicto por cada confirmación en la rama que rebase.

En realidad, muchas veces encontrará que si tiene un conflicto en una confirmación, encontrará conflictos relacionados (muy similares) en confirmaciones posteriores en su rama, simplemente porque las confirmaciones en una rama temática tienden a estar relacionadas (por ejemplo, modificando las mismas partes del código).

La mejor manera de minimizar los conflictos es hacer un seguimiento de lo que está sucediendo en master y evitar que una rama temática se ejecute durante demasiado tiempo sin cambiar la base de datos. Lidiar con pequeños conflictos por adelantado de vez en cuando es más fácil que manejarlos todos en un gran conflicto feliz al final.

Algunas advertencias para usuarios de GitHub

GitHub es genial en muchas cosas. Es fantástico para el alojamiento de Git, y tiene una maravillosa interfaz web con navegación de código, buena funcionalidad de reducción de precios, Gist, etc.

Las solicitudes de extracción, por otro lado, tienen algunas funciones que frustran activamente el historial lineal de Git. Sería muy bienvenido si GitHub realmente solucionara estos problemas, pero hasta entonces debes ser consciente de las deficiencias:

“Solicitud de extracción de fusión “permite fusiones no lineales a master

Puede ser tentador presionar el botón verde y amigable” Solicitud de extracción de fusión ” para fusionar una rama temática en master. Especialmente porque dice ” Esta rama está actualizada con la rama base. La fusión se puede realizar automáticamente”.

 Petición de extracción de fusión de GitHub

Lo que GitHub realmente está diciendo aquí, es que la rama se puede fusionar a master sin conflictos. No comprueba si la rama de solicitud de extracción se basa en el último maestro.

En otras palabras, si desea un historial lineal, debe asegurarse de que la rama de solicitud de extracción se rebase en la parte superior del último maestro. Por lo que puedo decir, dicha información no está disponible a través de la interfaz web de GitHub (a menos que esté utilizando “ramas protegidas”, consulte a continuación), por lo que debe hacerlo desde su cliente Git local.

Incluso si la solicitud de extracción se rebasa correctamente, no hay garantía de que la operación de fusión en la interfaz web de GitHub sea atómica (p. ej. alguien puede enviar cambios a master antes de que se realice la operación de fusión, y GitHub no se quejará).

Así que, en realidad, la única manera de asegurarse de que sus ramas se rebasen correctamente en la parte superior del último maestro es hacer la operación de fusión localmente y empujar el maestro resultante manualmente. Algo a lo largo de las líneas:

git checkout mastergit pullgit checkout my-pullrequest-branchgit rebase mastergit checkout mastergit merge --no-ff my-pullrequest-branchgit push origin master

Si tienes mala suerte y alguien logra empujar cambios a master entre tus operaciones de tracción y empuje, tu operación de empuje será denegada. Sin embargo, esto es algo bueno, ya que garantiza que su operación sea atómica. Solo git reset --hard origin/master y repita los pasos anteriores hasta que se complete.

Nota: Respete las directrices de su proyecto revisión y pruebas de código w.r.t. Por ejemplo, si está ejecutando pruebas automáticas (compilaciones, análisis estático, pruebas unitarias, tests) como parte de una solicitud de extracción, probablemente debería volver a enviar su rama rebasada (ya sea usando git push-f o abriendo una nueva PR) en lugar de simplemente actualizar la rama maestra manualmente.

La funcionalidad de ramas protegidas fomenta las fusiones desde el maestro

Si está utilizando ramas protegidas y verificaciones de estado en su proyecto de GitHub, en realidad obtiene protección contra la fusión de una rama de solicitud de extracción en el maestro a menos que se base en el último maestro (creo que la razón es que las verificaciones de estado realizadas en la rama PR deberían seguir siendo válidas después de la fusión con el maestro).

Sin embargo If Si la rama pull request no se basa en el último maestro, se le presenta un botón amigable llamado “Actualizar rama”, con el texto “Esta rama está desactualizada con la rama base. Fusiona los últimos cambios de master en esta rama”.

github-update-branch

En este punto, su mejor opción es rebasar la rama localmente y empujarla a la fuerza a la solicitud de extracción. Asumiendo que tiene my-pullrequest-branch retirado, haga:

git fetch origingit rebase origin/mastergit push -f

Desafortunadamente, las solicitudes de extracción de GitHub no funcionan bien con empujes de fuerza, por lo que parte de la información de revisión de código puede perderse en el proceso. Si eso no es aceptable, considere crear una nueva solicitud de extracción (por ejemplo, enviar su rama rebasada a una nueva rama remota y crear una solicitud de extracción desde la nueva rama).

Conclusión

Si te importa un historial lineal:

  • Rebase su rama de tema en la parte superior del último maestro antes de fusionarlo en maestro.
  • No combines master en tu rama temática. Rebase en su lugar.
  • Al compartir tu rama temática con otras personas, crea una nueva rama siempre que necesites rebasarla (my-topic-branch-2, my-topic-branch-3, …).
  • Si estás usando solicitudes de extracción de GitHub, ten en cuenta:
    • El botón “Fusionar” no garantiza que la rama PR esté basada en el último maestro. Rebase manualmente cuando sea necesario.
    • Si está utilizando ramas protegidas con comprobaciones de estado, nunca presione el botón” Actualizar rama”. Rebase manualmente en su lugar.

Si no te importa demasiado un historial Git lineal, ¡feliz fusión!

Lista de deseos para GitHub

Para ti, buena gente que trabaja en GitHub: Agrega soporte para flujos de trabajo de rebase en solicitudes de extracción (estas podrían ser opciones de repositorio de inclusión voluntaria).

  • Agregue la posibilidad de deshabilitar el botón/operación de fusión en las solicitudes de extracción si la rama no se basa en el último maestro (esto no debería requerir el uso de comprobaciones de estado).
  • Agregue un botón “Rebase en el último maestro”. En muchos casos, esta debe ser una operación libre de conflictos que se puede hacer fácilmente a través de la interfaz web.
  • Conservar el historial de solicitudes de extracción (confirmaciones, comentarios, comments) después de una rebase / push de fuerza.

Leave a Reply