¿Cómo funciona Git Merge?

Recuerde que un repositorio de Git está estructurado como un gráfico acíclico dirigido: cada confirmación de Git es una instantánea del árbol junto con referencias a sus confirmaciones principales (generalmente una principal, pero la confirmación inicial no tiene padres, mientras que una confirmación de fusión tiene dos o más padres). Por lo tanto, cada confirmación hace referencia recursivamente a un conjunto de confirmaciones de antepasados. Por lo general, es más útil pensar que un commit representa un parche, al tomar la diferencia entre los árboles principales del commit y el árbol del commit. De esta manera, uno piensa en el árbol de un commit como el resultado de aplicar todos los parches de su antepasado. El árbol de una fusión se compromete entre dos ramas, por lo tanto, puede considerarse como el resultado de aplicar todos los parches en la unión de los conjuntos de antepasados ​​de las dos ramas.

Pero no es así como se implementa realmente git merge , tanto porque sería terriblemente lento como porque requeriría volver a resolver todos los conflictos de fusión que alguna vez ocurrieron en el camino. Entonces, ¿qué está pasando realmente?

Me gusta pensar en esto matemáticamente: dado commits [math] A [/ math] y [math] B [/ math], el commit de fusión [math] A \ vee B [/ math] está representado por [math] [A \ vee B] = [A] + [B] – [C] [/ math], donde [math] C [/ math] es la base de fusión (ancestro común más reciente) de [math] A [/ math] y [matemáticas] B [/ matemáticas]. Tenemos que “restar” [matemática] [C] [/ matemática] porque de lo contrario ese contenido sería doblemente contado por [matemática] [A] [/ matemática] y [matemática] [B] [/ matemática]. Esta operación [matemática] x + y – w [/ matemática] se denomina fusión tripartita . Puede pensar en esto como aplicar el parche [matemático] y – w [/ matemático] a [matemático] x [/ matemático], o como aplicar el parche [matemático] x – w [/ matemático] a [matemático] y [ /mates].

De hecho, esta operación no se implementa literalmente con diff y patch, sino con el algoritmo utilizado para construirlos: el algoritmo de subsecuencia común más largo. La diferencia [matemática] x – w [/ matemática] entre dos secuencias [matemática] x, w [/ matemática] de líneas es justo lo que obtenemos cuando los alineamos a lo largo de su subsecuencia común más larga (y tal vez desechemos los largos que son comunes a ambas secuencias). Para construir la combinación tripartita [matemática] x + y – w [/ matemática], alineamos [matemática] x [/ matemática] y [matemática] w [/ matemática] a lo largo de su subsecuencia común más larga, alineamos [matemática] y [/ math] y [math] w [/ math] a lo largo de su subsecuencia común más larga, y luego genera cada línea que

  • común a las tres secuencias, o
  • presente en [matemáticas] x [/ matemáticas] pero ausente en [matemáticas] y [/ matemáticas] y [matemáticas] w [/ matemáticas], o
  • presente en [matemáticas] y [/ matemáticas] pero ausente en [matemáticas] x [/ matemáticas] y [matemáticas] w [/ matemáticas],

mientras eliminamos las líneas que son

  • presente en [matemática] y [/ matemática] y [matemática] w [/ matemática] pero ausente en [matemática] x [/ matemática], o
  • presente en [matemática] x [/ matemática] y [matemática] w [/ matemática] pero ausente en [matemática] y [/ matemática].

Aquí hay un ejemplo:

  x: w: y: ↦ fusionado:
 leche leche leche leche
 jugo jugo
 harina harina harina harina
                 salchicha salchicha
 huevos huevos huevos huevos
         mantequilla mantequilla

El orden de las líneas de [math] x [/ math], [math] y [/ math] y [math] w [/ math] solo puede imponer un orden parcial en las líneas de salida de la fusión de tres vías. Si es así, es porque el mismo bloque de [math] w [/ math] fue editado de dos maneras diferentes entre [math] x [/ math] y [math] y [/ math], así que declaramos que ese bloque es un fusionar conflicto y presentarlo al usuario para que lo resuelva manualmente.

Cuando git muestra un conflicto de fusión, por defecto solo verá los bloques x e y del conflicto:

  <<<<<<>>>>>> y

Sin embargo, los conflictos de combinación se vuelven mucho más fáciles de resolver cuando puede ver la base de combinación [math] w [/ math]. Recomiendo activar esto en ~/.gitconfig ejecutando git config –global merge.conflictstyle diff3.

  <<<<<<>>>>>> y

Ahora puede ver que esto resuelve:

  Tomé dos huevos y tres salchichas para el desayuno.

(Tenga en cuenta que esta operación es simétrica wrt intercambiando [math] w [/ math] con el resultado, por lo que realmente necesita ver [math] w [/ math]).

Hay otros dos casos a considerar: puede haber líneas que son

  • presente en [matemáticas] x [/ matemáticas] y [matemáticas] y [/ matemáticas] pero ausente en [matemáticas] w [/ matemáticas], o
  • presente en [matemáticas] w [/ matemáticas] pero ausente en [matemáticas] x [/ matemáticas] y [matemáticas] y [/ matemáticas].

Algunos algoritmos de fusión de tres vías siempre etiquetarán líneas tales como conflictos de fusión. Sin embargo, Git generará o eliminará felizmente dichas líneas, respectivamente, siempre que las líneas circundantes no cambien. Este efecto se denomina fusión limpia accidental . En ocasiones es conveniente en la práctica, especialmente cuando el usuario arruinó y fusionó dos versiones diferentes del mismo parche entre sí. Pero creo que, en general, no es una buena idea ocultar tales errores y desear que este comportamiento se pueda desactivar. Intenta evitar aprovecharlo.

Si ha prestado mucha atención, es posible que haya descubierto un pequeño agujero en mi descripción anterior: dado que los commits [math] A [/ math] y [math] B [/ math] pueden contener commits de fusión, su más reciente ¡Un ancestro común podría no ser único! En general, puede haber muchos ancestros comunes más recientes [matemáticas] C_1, \ ldots, C_k [/ matemáticas]. En este caso, git merge procede de manera recursiva : primero construye la fusión [math] C = C_1 \ vee \ ldots \ vee C_k [/ math], y la usa como base de fusión para la fusión de tres vías [math] [A] + [B] – [C] [/ matemáticas]. Esta es la razón por la cual la estrategia de fusión predeterminada de Git se llama recursive .

El | w
Tomé un huevo y dos salchichas para el desayuno.
=======
Tomé dos huevos y dos salchichas para el desayuno.
>>>>>>> y

Ahora puede ver que esto resuelve:

  Tomé dos huevos y tres salchichas para el desayuno.

(Tenga en cuenta que esta operación es simétrica wrt intercambiando [math] w [/ math] con el resultado, por lo que realmente necesita ver [math] w [/ math]).

Hay otros dos casos a considerar: puede haber líneas que son

  • presente en [matemáticas] x [/ matemáticas] y [matemáticas] y [/ matemáticas] pero ausente en [matemáticas] w [/ matemáticas], o
  • presente en [matemáticas] w [/ matemáticas] pero ausente en [matemáticas] x [/ matemáticas] y [matemáticas] y [/ matemáticas].

Algunos algoritmos de fusión de tres vías siempre etiquetarán líneas tales como conflictos de fusión. Sin embargo, Git generará o eliminará felizmente dichas líneas, respectivamente, siempre que las líneas circundantes no cambien. Este efecto se denomina fusión limpia accidental . En ocasiones es conveniente en la práctica, especialmente cuando el usuario arruinó y fusionó dos versiones diferentes del mismo parche entre sí. Pero creo que, en general, no es una buena idea ocultar tales errores y desear que este comportamiento se pueda desactivar. Intenta evitar aprovecharlo.

Si ha prestado mucha atención, es posible que haya descubierto un pequeño agujero en mi descripción anterior: dado que los commits [math] A [/ math] y [math] B [/ math] pueden contener commits de fusión, su más reciente ¡Un ancestro común podría no ser único! En general, puede haber muchos ancestros comunes más recientes [matemáticas] C_1, \ ldots, C_k [/ matemáticas]. En este caso, git merge procede de manera recursiva : primero construye la fusión [math] C = C_1 \ vee \ ldots \ vee C_k [/ math], y la usa como base de fusión para la fusión de tres vías [math] [A] + [B] – [C] [/ matemáticas]. Esta es la razón por la cual la estrategia de fusión predeterminada de Git se llama recursive .

Suponga que dos ramas se parecen al gráfico de abajo. A, B, C, D, E es la historia de la rama “maestra” y A, B, X, Y, Z es la historia de la rama “característica”.

  A B C D E
       \                  
         - X - Y - Z

El comando git merge feature primero encontrará el ancestro común de “master” (rama actual) y “feature”. Es más o menos equivalente a la git merge-base master feature . En nuestro gráfico de ejemplo, el antepasado común es B.

Si no hay conflictos entre las confirmaciones C, D, E y X, Y, Z, git creará una “confirmación de fusión”. Los compromisos de fusión tienen dos o más padres.

El nuevo gráfico se verá así.

  A - B - C - D - E -------- M
       \ /
         - X - Y - Z -------

Cada git commit se compone de un “árbol”, uno o más “padres”, nombre del autor, correo electrónico, fecha y nombre del remitente, correo electrónico, fecha.

La única diferencia entre un compromiso de fusión y un compromiso normal es el número de padres.

En el segundo gráfico, la confirmación de fusión es “M”.

Si hay un conflicto, se le pide al usuario que resuelva los conflictos y cree la confirmación de fusión manualmente. Después de solucionar los conflictos, git commit -a creará el commit de fusión. No hay sintaxis especial. Git ya sabe que el usuario está en medio de una fusión.

Para obtener más información sobre las partes internas de git, puede comenzar desde la Sección 7 del Libro de la comunidad de Git de Scott Chacon: Partes internas y plomería en http://book.git-scm.com/7_how_gi

git-merge: une dos o más historias de desarrollo juntas

Incorpora cambios de los commits nombrados (desde el momento en que sus historias divergieron de la rama actual) en la rama actual. Este comando lo usa git pull para incorporar cambios desde otro repositorio y puede usarse a mano para combinar cambios de una rama a otra.

Suponga que existe el siguiente historial y que la rama actual es ” master “:

  A --- B --- C tema
	  / /
     D --- E --- F --- G maestro

Luego, ” git merge topic ” reproducirá los cambios realizados en la rama del topic , ya que divergió del master (es decir, E ) hasta su confirmación actual ( C ) en la parte superior del master , y registrará el resultado en una nueva confirmación junto con los nombres de las dos confirmaciones principales y un mensaje de registro del usuario que describe los cambios.

  A --- B --- C tema
	  / \
     D --- E --- F --- G --- H maestro

La segunda sintaxis (” git merge --abort “) solo se puede ejecutar después de que la fusión haya generado conflictos. git merge –abort abortará el proceso de fusión e intentará reconstruir el estado previo a la fusión. Sin embargo, si hubo cambios no confirmados cuando comenzó la fusión (y especialmente si esos cambios se modificaron aún más después de que se inició la fusión), git merge –abort en algunos casos será incapaz de reconstruir los cambios originales (antes de la fusión).

Git – Documentación de git-merge