Git Guide Parte número 2: la regla de oro y otros conceptos básicos de rebase

Veamos qué sucede cuando ejecuta git rebase y por qué debe tener cuidado. 

Esta es la segunda y tercera parte de la guía Git del blog Pierre de Wulf traducida por el equipo de Mail.ru Cloud Solutions . La primera parte se puede leer aquí .

La esencia del rebase


Cómo sucede exactamente el rebase:


Puede decir que rebase es desanclar la rama que desea mover y conectarla a otra rama. Esta definición es cierta, pero trata de mirar un poco más profundo. Si observa la documentación , esto es lo que dice sobre rebase: "Aplicar confirmaciones a otra rama (Vuelva a aplicar confirmaciones encima de otra sugerencia base)".

La palabra principal aquí es aplicar, porque rebase no es solo copiar y pegar ramas en otra rama. Rebase toma secuencialmente todas las confirmaciones de la rama seleccionada y las vuelve a aplicar a la nueva rama.

Este comportamiento lleva a dos puntos:

  1. Al volver a aplicar las confirmaciones, Git crea nuevas confirmaciones. Incluso si contienen los mismos cambios, Git se considera como confirmaciones nuevas e independientes.
  2. Git rebase anula las confirmaciones y no elimina las antiguas. Esto significa que una vez que se haya completado el rebase, las confirmaciones anteriores continuarán almacenándose en la subcarpeta / gjects de la carpeta .git. Si no comprende completamente cómo Git almacena y considera las confirmaciones, lea la primera parte de este artículo.

Aquí hay una interpretación más correcta de lo que sucede durante el rebase:


Como puede ver, la rama de características contiene confirmaciones completamente nuevas. Como se mencionó anteriormente, el mismo conjunto de cambios, pero objetos completamente nuevos desde el punto de vista de Git. 

También significa que los viejos commits no se destruyen. Simplemente se vuelven inaccesibles directamente. Si recuerdas, una rama es solo un enlace a una confirmación. Por lo tanto, si ni una rama ni una etiqueta se refieren a una confirmación, no se puede acceder a ella usando Git, aunque sigue estando presente en el disco.

Ahora hablemos de la regla de oro.

Regla de rebase de oro


La regla de rebase de oro es: “¡ NUNCA rebase una rama compartida! ". Una rama compartida se refiere a una rama que existe en el repositorio de red y con la que otras personas pueden trabajar, excepto usted.

A menudo, esta regla se aplica sin la comprensión adecuada, por lo tanto, analizaremos por qué apareció, especialmente porque ayudará a comprender mejor el trabajo de Git.

Veamos una situación en la que un desarrollador rompe la regla de oro y lo que sucede en este caso.

Supongamos que Bob y Anna están trabajando juntos en un proyecto. A continuación se muestra el aspecto de los repositorios Bob y Anna y el repositorio original en GitHub:


Todos los usuarios tienen repositorios sincronizados con GitHub.

Ahora Bob, rompiendo la regla de oro, realiza un rebase y, al mismo tiempo, Anna, trabajando en la rama de características, crea una nueva confirmación:


¿Ves lo que sucederá?

Bob está tratando de ejecutar una confirmación de inserción; se niega a algo como esto:


Git no tuvo éxito porque Git no sabía cómo combinar la rama característica de Bob con la rama característica de GitHub.

La única solución que le permite a Bob presionar es usar la tecla de fuerza, que le dice al repositorio de GitHub que elimine la rama de características y acepte la que Bob está presionando para esta rama. Después de eso tenemos la siguiente situación:


Ahora Anna quiere lanzar sus cambios, y esto es lo que sucederá:


Esto es normal, Git le dijo a Anna que no tiene una versión sincronizada de la rama de características, es decir, su versión de la rama y la versión de la rama en GitHub son diferentes. Anna debe completar el tirón. De la misma manera que Git combina una rama local con una rama en el repositorio cuando presiona, Git intenta fusionar la rama en el repositorio con una rama local cuando tira.

Antes de hacer commits de extracción en las ramas locales y de GitHub, tenga este aspecto:

A--B--C--D'   origin/feature // GitHub
A--B--D--E    feature        // Anna

Cuando tira, Git se fusiona para eliminar la diferencia en los repositorios. Y entonces, ¿a qué conduce esto:


Commit M es un commit de fusión. Finalmente, las ramas de características de Anna y GitHub están completamente fusionadas. Anna dio un suspiro de alivio, todos los conflictos se resolvieron, puede realizar un empujón. 

Bob está tirando, ahora todo está sincronizado:


Mirando el desorden resultante, tenías que asegurarte de la importancia de la regla de oro. También tenga en cuenta que tal desastre fue creado por un solo desarrollador y en una rama que se comparte entre solo dos personas. Imagina estar en un equipo de diez personas. 

Uno de los muchos beneficios de Git es que puede retroceder sin problemas en cualquier momento. Pero cuantos más errores se cometan, como se describe, más difícil es hacerlo.

También tenga en cuenta que las confirmaciones duplicadas aparecen en el repositorio de red. En nuestro caso, D y D ', que contienen los mismos datos. De hecho, el número de confirmaciones duplicadas puede ser tan grande como el número de confirmaciones en su rama rebaseada.

Si aún no está convencido, presentemos a Emma, ​​el tercer desarrollador. Ella trabaja en la rama de características antes de que Bob cometa su error y actualmente quiera presionar. Supongamos que para cuando ella empuje nuestro pequeño guión anterior ya se haya completado. Esto es lo que sale:


¡Oh, ese Bob!

Este texto puede hacerle pensar que el rebase solo se usa para mover una rama a la parte superior de otra rama. Esto es opcional: puede cambiar la base en la misma rama.

Beauty pull rebase


Como viste anteriormente, los problemas de Anna podrían haberse evitado si ella hubiera usado el rebase pull. Consideremos esta pregunta con más detalle.

Digamos que Bob trabaja en una rama que se aparta del maestro, entonces su historia puede verse así:




Bob decide que es hora de tirar, lo que, como ya entendió, generará cierta confusión. Dado que el repositorio de Bob se estaba alejando de GitHub, Git le preguntará si la fusión se realizó, y el resultado es este:


Esta solución funciona y funciona bien, sin embargo, puede ser útil saber que hay otras soluciones al problema. Uno de ellos es pull-rebase.

Cuando haces pull-rebase, Git intenta averiguar qué confirmaciones solo están en tu rama y cuáles están en el repositorio de la red. Git luego combina los commits del repositorio de red con el último commit presente tanto en los repositorios locales como de red. Luego, reescribe tus compromisos locales hasta el final de la rama. 

Suena complicado, así que ilustramos:

  1. Git presta atención solo a las confirmaciones que se encuentran tanto en su repositorio como en el de la red:

    Parece un clon local del repositorio de GitHub.
  2. Git rebase commits locales:


Como recordarán, cuando el rebase Git aplica los commits uno por uno, es decir, en este caso aplica el commit maestro E, luego F. al final de la rama. El resultado es rebase a sí mismo. Se ve bien, pero surge la pregunta: ¿por qué hacer esto?

En mi opinión, el mayor problema con la fusión de sucursales es que la historia de los compromisos está contaminada. Por lo tanto, pull-rebase es una solución más elegante. Incluso iría más lejos y diría que cuando necesite descargar los últimos cambios en su sucursal, siempre debe usar pull-rebase. Pero debe recordar: dado que rebase aplica todos los commits por turno, cuando reescribe 20 commits, es posible que tenga que resolver 20 conflictos uno tras otro. 

Por lo general, puede usar el siguiente enfoque: un gran cambio realizado hace mucho tiempo es la fusión, dos pequeños cambios realizados recientemente en un pull-rebase.

Fuerza rebase sobre


Supongamos que su historial de confirmación se ve así:




Por lo tanto, desea volver a crear la rama de la característica 2 en la rama maestra. Si realiza un rebase regular en la rama maestra, obtenga esto:


Es ilógico que commit D exista en ambas ramas: en la característica 1 y la característica 2. Si mueve la rama de la característica 1 al final de la rama maestra, resulta que commit D se aplicará dos veces.

Suponga que necesita obtener un resultado diferente:


Para implementar tal escenario, git rebase es exactamente lo que se pretende.

Primero, lea la documentación:

SYNOPSIS
       git rebase [-i | --interactive] [<options>] [--exec <cmd>]
               [--onto <newbase> | --keep-base] [<upstream> [<branch>]]
       git rebase [-i | --interactive] [<options>] [--exec <cmd>] 
[--onto <newbase>]
               --root [<branch>]
       git rebase (--continue | --skip | --abort | --quit | --edit-todo 
| --show-current-patch)


Estamos interesados ​​en esto:

OPTIONS
       --onto <newbase>
          Starting point at which to create the new commits. If the 
--onto option is not specified, the starting point is <upstream>. May be 
any valid commit, and not just an existing branch name.


Use esta opción para indicar en qué punto crear nuevas confirmaciones.

Si no se especifica esta opción, el flujo ascendente se convertirá en el punto de partida.

Para comprender, daré una imagen más:

A--B--C        master
    \
     D--E      feature1
         \
          F--G feature2

Here we want to rebase feature2 to master beginning from feature1
                           |                                |
                        newbase                         upstream


Es decir, la rama maestra es newbase, y la rama de la característica 1 está en sentido ascendente.

Por lo tanto, si desea obtener el resultado como en la última figura, debe ejecutar git rebase --onto master feature1 en la rama feature2.

Buena suerte

Traducido con el soporte de Mail.ru Cloud Solutions .

Qué más leer sobre el tema :

  1. La primera parte de la guía Git.
  2. Mi segundo año como desarrollador independiente .
  3. Nuestro canal de telegramas sobre transformación digital


All Articles