Je veux connaître un algorithme exact (ou presque) derrière "git merge". Les réponses au moins à ces sous-questions seront utiles:
Mais la description de tout un algorithme sera bien meilleure.
Vous feriez mieux de chercher une description d'un algorithme de fusion à 3 voies. Une description de haut niveau ressemblerait à ceci:
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 git
s par défaut recursive
)X
avec B
et Y
avec B
.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 ...
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-merge
MERGE 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:
a\nc\n
comme ajout d'une seule ligneÇ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.
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.
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 .