Saltar al contenido

¿Cómo funciona ‘git merge’ en detalles?

Solución:

Es mejor que busque una descripción de un algoritmo de fusión de 3 vías. Una descripción de alto nivel sería algo como esto:

  1. Encuentre una base de combinación adecuada B – una versión del archivo que es un antepasado de las dos nuevas versiones (X y Y), y generalmente la base más reciente (aunque hay casos en los que tendrá que retroceder más, que es una de las características de gits predeterminado recursive unir)
  2. Realizar diferencias de X con B y Y con B.
  3. Camine por los bloques de cambio identificados en las dos diferencias. Si ambos lados introducen el mismo cambio en el mismo lugar, acepte cualquiera de ellos; si uno introduce un cambio y el otro deja esa región en paz, introduzca el cambio en la final; si ambos introducen cambios en un lugar, pero no coinciden, marque un conflicto para que se resuelva manualmente.

El algoritmo completo trata esto con mucho más detalle, e incluso tiene algo de documentación (https://github.com/git/git/blob/master/Documentation/technical/trivial-merge.txt para uno, junto con el git help XXX páginas, donde XXX es una de merge-base, merge-file, merge, merge-one-file y posiblemente algunos otros). Si eso no es lo suficientemente profundo, siempre hay código fuente …

¿Cómo funciona git cuando hay varias bases comunes para fusionar ramas?

Este artículo fue muy útil: http://codicesoftware.blogspot.com/2011/09/merge-recursive-strategy.html (aquí está la parte 2).

Recursive utiliza diff3 de forma recursiva para generar una rama virtual que se utilizará como ancestro.

P.ej:

(A)----(B)----(C)-----(F)
        |      |       |
        |      |   +---+
        |      |   |
        |      +-------+
        |          |   |
        |      +---+   |
        |      |       |
        +-----(D)-----(E)

Luego:

git checkout E
git merge F

Hay 2 mejores ancestros comunes (ancestros comunes que no son ancestros de ningún otro), C y D. Git los fusiona en una nueva rama virtual V, y luego usa V como base.

(A)----(B)----(C)--------(F)
        |      |          |
        |      |      +---+
        |      |      |
        |      +----------+
        |      |      |   |
        |      +--(V) |   |
        |          |  |   |
        |      +---+  |   |
        |      |      |   |
        |      +------+   |
        |      |          |
        +-----(D)--------(E)

Supongo que Git simplemente continuaría con el si hubiera más mejores ancestros comunes, fusionando V con el siguiente.

El artículo dice que si hay un conflicto de fusión mientras se genera la rama virtual, Git simplemente deja los marcadores de conflicto donde están y continúa.

¿Qué sucede cuando fusiono varias ramas a la vez?

Como explicó @Nevik Rehnel, depende de la estrategia, está bien explicado en man git-merge MERGE STRATEGIES sección.

Solamente octopus y ours / theirs admite la fusión de varias ramas a la vez, recursive por ejemplo, no.

octopus se niega a fusionarse si hubiera conflictos, y ours es una fusión trivial por lo que no puede haber conflictos.

Esos comandos que generan una nueva confirmación tendrán más de 2 padres.

Hice uno merge -X octopus en Git 1.8.5 sin conflictos para ver cómo va.

Estado inicial:

   +--B
   |
A--+--C
   |
   +--D

Acción:

git checkout B
git merge -Xoctopus C D

Nuevo estado:

   +--B--+
   |     |
A--+--C--+--E
   |     |
   +--D--+

Como se esperaba, E tiene 3 padres.

TODO: cómo funciona exactamente octopus en las modificaciones de un solo archivo. ¿Fusiones recursivas de dos por dos de 3 vías?

¿Cómo funciona git cuando no hay una base común para fusionar ramas?

@Torek menciona que desde 2.9, la fusión falla sin --allow-unrelated-histories.

Lo probé empíricamente en Git 1.8.5:

git init
printf 'ancn' > a
git add .
git commit -m a

git checkout --orphan b
printf 'anbncn' > a
git add .
git commit -m b
git merge master

a contiene:

a
<<<<<<< ours
b
=======
>>>>>>> theirs
c

Luego:

git checkout --conflict=diff3 -- .

a contiene:

<<<<<<< ours
a
b
c
||||||| base
=======
a
c
>>>>>>> theirs

Interpretación:

  • la base esta vacia
  • cuando la base está vacía, no es posible resolver ninguna modificación en un solo archivo; solo se pueden resolver cosas como la adición de un nuevo archivo. El conflicto anterior se resolvería en una fusión de 3 vías con base ancn como una adición de una sola línea
  • I pensar que una combinación de 3 vías sin un archivo base se llama combinación de 2 vías, que es solo una diferencia

Yo también estoy interesado. No sé la respuesta, pero …

Un sistema complejo que funciona invariablemente ha evolucionado a partir de un sistema simple que funcionaba.

Creo que la fusión de git es muy sofisticada y será muy difícil de entender, pero una forma de abordar esto es a partir de sus precursores y centrarse en el corazón de su preocupación. Es decir, dados dos archivos que no tienen un ancestro común, ¿cómo resuelve git merge cómo fusionarlos y dónde están los conflictos?

Intentemos encontrar algunos precursores. De git help merge-file:

git merge-file is designed to be a minimal clone of RCS merge; that is,
       it implements all of RCS merge's functionality which is needed by
       git(1).

De wikipedia: http://en.wikipedia.org/wiki/Git_%28software%29 -> http://en.wikipedia.org/wiki/Three-way_merge#Three-way_merge -> http: //en.wikipedia .org / wiki / Diff3 -> http://www.cis.upenn.edu/~bcpierce/papers/diff3-short.pdf

Ese último enlace es un pdf de un artículo que describe el diff3 algoritmo en detalle. Aquí hay una versión del visor de pdf de Google. Tiene solo 12 páginas y el algoritmo tiene solo un par de páginas, pero un tratamiento matemático completo. Eso puede parecer un poco demasiado formal, pero si desea comprender la fusión de git, primero deberá comprender la versión más simple. No lo he comprobado todavía, pero con un nombre como diff3, probablemente también necesitará comprender diff (que usa un subsecuencia común más larga algoritmo). Sin embargo, puede haber una explicación más intuitiva de diff3 por ahí, si tienes un google …


Ahora, acabo de hacer un experimento comparando diff3 y git merge-file. Toman los mismos tres archivos de entrada versión1 versión antigua versión2 y marcar los conflictos de la misma manera, con <<<<<<< version1, =======, >>>>>>> version2 (diff3 también tiene ||||||| oldversion), mostrando su herencia común.

Usé un archivo vacío para versión antiguay archivos casi idénticos para versión 1 y versión 2 con solo una línea adicional agregada a versión 2.

Resultado: git merge-file identificó la única línea modificada como el conflicto; pero diff3 trató los dos archivos completos como un conflicto. Por lo tanto, por más sofisticada que sea diff3, la fusión de git es aún más sofisticada, incluso para el caso más simple.

Aquí están los resultados reales (utilicé la respuesta de @ twalberg para el texto). Tenga en cuenta las opciones necesarias (consulte las respectivas páginas de manual).

$ git merge-file -p fun1.txt fun0.txt fun2.txt

You might be best off looking for a description of a 3-way merge algorithm. A
high-level description would go something like this:

    Find a suitable merge base B - a version of the file that is an ancestor of
both of the new versions (X and Y), and usually the most recent such base
(although there are cases where it will have to go back further, which is one
of the features of gits default recursive merge) Perform diffs of X with B and
Y with B.  Walk through the change blocks identified in the two diffs. If both
sides introduce the same change in the same spot, accept either one; if one
introduces a change and the other leaves that region alone, introduce the
change in the final; if both introduce changes in a spot, but they don't match,
mark a conflict to be resolved manually.
<<<<<<< fun1.txt
=======
THIS IS A BIT DIFFERENT
>>>>>>> fun2.txt

The full algorithm deals with this in a lot more detail, and even has some
documentation (/usr/share/doc/git-doc/technical/trivial-merge.txt for one,
along with the git help XXX pages, where XXX is one of merge-base, merge-file,
merge, merge-one-file and possibly a few others). If that's not deep enough,
there's always source code...

$ diff3 -m fun1.txt fun0.txt fun2.txt

<<<<<<< fun1.txt
You might be best off looking for a description of a 3-way merge algorithm. A
high-level description would go something like this:

    Find a suitable merge base B - a version of the file that is an ancestor of
both of the new versions (X and Y), and usually the most recent such base
(although there are cases where it will have to go back further, which is one
of the features of gits default recursive merge) Perform diffs of X with B and
Y with B.  Walk through the change blocks identified in the two diffs. If both
sides introduce the same change in the same spot, accept either one; if one
introduces a change and the other leaves that region alone, introduce the
change in the final; if both introduce changes in a spot, but they don't match,
mark a conflict to be resolved manually.

The full algorithm deals with this in a lot more detail, and even has some
documentation (/usr/share/doc/git-doc/technical/trivial-merge.txt for one,
along with the git help XXX pages, where XXX is one of merge-base, merge-file,
merge, merge-one-file and possibly a few others). If that's not deep enough,
there's always source code...
||||||| fun0.txt
=======
You might be best off looking for a description of a 3-way merge algorithm. A
high-level description would go something like this:

    Find a suitable merge base B - a version of the file that is an ancestor of
both of the new versions (X and Y), and usually the most recent such base
(although there are cases where it will have to go back further, which is one
of the features of gits default recursive merge) Perform diffs of X with B and
Y with B.  Walk through the change blocks identified in the two diffs. If both
sides introduce the same change in the same spot, accept either one; if one
introduces a change and the other leaves that region alone, introduce the
change in the final; if both introduce changes in a spot, but they don't match,
mark a conflict to be resolved manually.
THIS IS A BIT DIFFERENT

The full algorithm deals with this in a lot more detail, and even has some
documentation (/usr/share/doc/git-doc/technical/trivial-merge.txt for one,
along with the git help XXX pages, where XXX is one of merge-base, merge-file,
merge, merge-one-file and possibly a few others). If that's not deep enough,
there's always source code...
>>>>>>> fun2.txt

Si está realmente interesado en esto, es una especie de madriguera. Para mí, parece tan profundo como las expresiones regulares, el subsecuencia común más larga algoritmo de diff, gramáticas libres de contexto o álgebra relacional. Si quieres llegar al fondo, creo que puedes, pero será necesario un estudio decidido.

¡Haz clic para puntuar esta entrada!
(Votos: 0 Promedio: 0)



Utiliza Nuestro Buscador

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *