web-dev-qa-db-fra.com

Comment fonctionne `` git merge '' en détail?

Je veux connaître un algorithme exact (ou presque) derrière "git merge". Les réponses au moins à ces sous-questions seront utiles:

  • Comment git détecte-t-il le contexte d'un changement particulier non conflictuel?
  • Comment git peut-il savoir qu'il existe un conflit dans ces lignes exactes?
  • Quelles sont les choses que git fusionne automatiquement?
  • Comment git fonctionne-t-il lorsqu'il n'y a pas de base commune pour fusionner des branches?
  • Comment git fonctionne-t-il lorsqu'il existe plusieurs bases communes pour fusionner des branches?
  • Que se passe-t-il lorsque je fusionne plusieurs branches à la fois?
  • Quelle est la différence entre les stratégies de fusion?

Mais la description de tout un algorithme sera bien meilleure.

61
abyss.7

Vous feriez mieux de chercher une description d'un algorithme de fusion à 3 voies. Une description de haut niveau ressemblerait à ceci:

  1. Trouver une base de fusion appropriée B - une version du fichier qui est un ancêtre des deux nouvelles versions (X et Y), et généralement la base la plus récente (bien qu'il y ait des cas où il devra remonter plus loin, ce qui est l'une des fonctionnalités de la fusion gits par défaut recursive)
  2. Effectuez des différences de X avec B et Y avec B.
  3. Parcourez les blocs de changement identifiés dans les deux différences. Si les deux côtés introduisent le même changement au même endroit, acceptez l'un ou l'autre; si l'un introduit un changement et que l'autre laisse cette région tranquille, introduisez le changement dans la finale; si les deux introduisent des changements dans un emplacement, mais qu'ils ne correspondent pas, marquez un conflit à résoudre manuellement.

L'algorithme complet traite cela beaucoup plus en détail et contient même une documentation (/usr/share/doc/git-doc/technical/trivial-merge.txt pour un, avec le git help XXX pages, où XXX est l'un des merge-base, merge-file, merge, merge-one-file et peut-être quelques autres). Si ce n'est pas assez profond, il y a toujours du code source ...

44
twalberg

Comment git fonctionne-t-il lorsqu'il existe plusieurs bases communes pour fusionner des branches?

Cet article a été très utile: http://codicesoftware.blogspot.com/2011/09/merge-recursive-strategy.html (voici partie 2 ).

Récursif utilise diff3 récursivement pour générer une branche virtuelle qui sera utilisée comme ancêtre.

Par exemple.:

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

Alors:

git checkout E
git merge F

Il existe 2 meilleurs ancêtres communs (des ancêtres communs qui ne sont les ancêtres d'aucun autre), C et D. Git les fusionne dans une nouvelle branche virtuelle V, puis utilise V comme base.

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

Je suppose que Git continuerait avec s'il y avait plus de meilleurs ancêtres communs, en fusionnant V avec le suivant.

L'article indique que s'il y a un conflit de fusion lors de la génération de la branche virtuelle, Git laisse simplement les marqueurs de conflit où ils sont et continue.

Que se passe-t-il lorsque je fusionne plusieurs branches à la fois?

Comme l'a expliqué @Nevik Rehnel, cela dépend de la stratégie, il est bien expliqué sur man git-mergeMERGE STRATEGIES section.

Seuls octopus et ours/theirs prennent en charge la fusion de plusieurs branches à la fois, recursive par exemple ne le fait pas.

octopus refuse de fusionner s'il y avait des conflits, et ours est une fusion triviale donc il ne peut pas y avoir de conflits.

Ces commandes génèrent un nouveau commit aura plus de 2 parents.

J'ai fait un merge -X octopus sur Git 1.8.5 sans conflits pour voir comment ça se passe.

Etat initial:

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

Action:

git checkout B
git merge -Xoctopus C D

Nouvel état:

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

Comme prévu, E a 3 parents.

TODO: comment fonctionne exactement la pieuvre sur les modifications d'un seul fichier. Fusion récursive à deux voies à trois voies?

Comment git fonctionne-t-il lorsqu'il n'y a pas de base commune pour fusionner les branches?

@Torek mentionne que depuis 2.9, la fusion échoue sans --allow-unrelated-histories.

Je l'ai essayé empiriquement sur Git 1.8.5:

git init
printf 'a\nc\n' > a
git add .
git commit -m a

git checkout --Orphan b
printf 'a\nb\nc\n' > a
git add .
git commit -m b
git merge master

a contient:

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

Alors:

git checkout --conflict=diff3 -- .

a contient:

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

Interprétation:

  • la base est vide
  • lorsque la base est vide, il n'est pas possible de résoudre toute modification sur un seul fichier; seules les choses comme l'ajout de nouveaux fichiers peuvent être résolues. Le conflit ci-dessus serait résolu lors d'une fusion à 3 voies avec la base a\nc\n comme ajout d'une seule ligne
  • I pensez qu'une fusion à 3 voies sans fichier de base est appelée une fusion à 2 voies, qui est juste un diff

Ça m'intéresse aussi. Je ne connais pas la réponse, mais ...

Un système complexe qui fonctionne se révèle invariablement avoir évolué à partir d'un système simple qui fonctionnait

Je pense que la fusion de git est très sophistiquée et sera très difficile à comprendre - mais une façon d'aborder cela est de ses précurseurs et de se concentrer sur le cœur de votre préoccupation. Autrement dit, étant donné deux fichiers qui n'ont pas d'ancêtre commun, comment la fusion git fonctionne-t-elle pour les fusionner et où se trouvent les conflits?

Essayons de trouver des précurseurs. 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/Diff -> http://www.cis.upenn.edu/~bcpierce/papers/ diff3-short.pdf

Ce dernier lien est un pdf d'un article décrivant le diff3 algorithme en détail. Voici une version google pdf-viewer . Il ne fait que 12 pages et l'algorithme ne compte que quelques pages - mais un traitement mathématique complet. Cela peut sembler un peu trop formel, mais si vous voulez comprendre la fusion de git, vous devrez d'abord comprendre la version plus simple. Je n'ai pas encore vérifié, mais avec un nom comme diff3, vous aurez probablement aussi besoin de comprendre diff (qui utilise un algorithme la plus longue sous-séquence commune ). Cependant, il peut y avoir une explication plus intuitive de diff3 là-bas, si vous avez un google ...


Maintenant, je viens de faire une expérience comparant diff3 et git merge-file. Ils prennent les trois mêmes fichiers d'entrée version1 oldversion version2 et marquent les conflits de la même manière, avec <<<<<<< version1, =======, >>>>>>> version2 (diff3 a aussi ||||||| oldversion), montrant leur héritage commun.

J'ai utilisé un fichier vide pour oldversion, et des fichiers presque identiques pour version1 et version2 avec une seule ligne supplémentaire ajoutée à version2.

Résultat: git merge-file a identifié la ligne modifiée unique comme étant le conflit; mais diff3 a traité les deux fichiers entiers comme un conflit. Ainsi, aussi sophistiqué que diff3 soit, la fusion de git est encore plus sophistiquée, même pour ce cas le plus simple.

Voici les résultats réels (j'ai utilisé la réponse de @ twalberg pour le texte). Notez les options nécessaires (voir les pages de manuel respectives).

$ 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 cela vous intéresse vraiment, c'est un peu un trou de lapin. Pour moi, il semble aussi profond que les expressions régulières, l'algorithme la plus longue sous-séquence commune de diff, de grammaires sans contexte ou d'algèbre relationnelle. Si vous voulez aller au fond des choses, je pense que vous le pouvez, mais il faudra une étude déterminée.

6
13ren

Voici l'implémentation d'origine

http://git.kaarsemaker.net/git/blob/857f26d2f41e16170e48076758d974820af685ff/git-merge-recursive.py

Fondamentalement, vous créez une liste d'ancêtres communs pour deux validations, puis les fusionnez récursivement, soit en les faisant suivre rapidement, soit en créant des validations virtuelles qui sont utilisées pour la base d'une fusion à trois sur les fichiers.

2
aamontal

Comment git détecte-t-il le contexte d'un changement particulier non conflictuel?
Comment git découvre-t-il un conflit dans ces lignes exactes?

Si la même ligne a changé des deux côtés de la fusion, c'est un conflit; si ce n'est pas le cas, le changement d'un côté (s'il existe) est accepté.

Quelles sont les choses que git fusionne automatiquement?

Modifications qui n'entrent pas en conflit (voir ci-dessus)

Comment git fonctionne-t-il lorsqu'il existe plusieurs bases communes pour fusionner des branches?

Par la définition d'un Git merge-base , il n'y en a qu'un (le dernier ancêtre commun).

Que se passe-t-il lorsque je fusionne plusieurs branches à la fois?

Cela dépend de la stratégie de fusion (seules les stratégies octopus et ours/theirs prennent en charge la fusion de plus de deux branches).

Quelle est la différence entre les stratégies de fusion?

Ceci est expliqué dans le git merge page de manuel .

1
Nevik Rehnel