Chcę poznać dokładny algorytm (lub blisko tego) stojący za „git merge”. Pomocne będą odpowiedzi przynajmniej na te pytania podrzędne:
- W jaki sposób git wykrywa kontekst konkretnej niesprzecznej zmiany?
- W jaki sposób git dowiaduje się, że istnieje konflikt w tych dokładnych wierszach?
- Jakie rzeczy wykonuje automatyczne scalanie git?
- Jak działa git, gdy nie ma wspólnej podstawy do łączenia gałęzi?
- Jak działa git, gdy istnieje wiele wspólnych baz do łączenia gałęzi?
- Co się stanie, gdy połączę wiele oddziałów jednocześnie?
- Jaka jest różnica między strategiami scalania?
Ale opis całego algorytmu będzie znacznie lepszy.
Odpowiedzi:
Najlepiej byłoby poszukać opisu algorytmu łączenia trójstronnego. Opis wysokiego poziomu wyglądałby mniej więcej tak:
B
- wersję pliku, która jest przodkiem obu nowych wersji (X
iY
), i zazwyczaj najnowszą taką bazę (choć zdarzają się przypadki, w których będzie musiała się cofnąć dalej, czyli jedną z funkcjegit
domyślnegorecursive
scalania)X
zB
iY
oB
.Pełny algorytm zajmuje się tym bardziej szczegółowo, a nawet ma pewną dokumentację ( https://github.com/git/git/blob/master/Documentation/technical/trivial-merge.txt , wraz ze
git help XXX
stronami gdzie XXX jedenmerge-base
,merge-file
,merge
,merge-one-file
i ewentualnie kilka innych). Jeśli to nie jest wystarczająco szczegółowe, zawsze jest kod źródłowy ...źródło
Jak działa git, gdy istnieje wiele wspólnych baz do łączenia gałęzi?
Ten artykuł był bardzo pomocny: http://codicesoftware.blogspot.com/2011/09/merge-recursive-strategy.html (tutaj jest część 2 ).
Recursive używa diff3 rekurencyjnie do wygenerowania wirtualnej gałęzi, która będzie używana jako przodek.
Na przykład:
Następnie:
Istnieją 2 najlepszych wspólnych przodków (wspólnych przodków, którzy nie są przodkami żadnego innego)
C
iD
. Git łączy je w nową wirtualną gałąźV
, a następnie używaV
jako podstawy.Przypuszczam, że Git po prostu kontynuowałby działanie, gdyby było więcej najlepszych wspólnych przodków, scalając się
V
z następnym.Artykuł mówi, że jeśli wystąpi konflikt scalania podczas generowania wirtualnej gałęzi, Git po prostu pozostawia znaczniki konfliktu tam, gdzie się znajdują i kontynuuje.
Co się stanie, gdy połączę wiele oddziałów jednocześnie?
Jak wyjaśnił @Nevik Rehnel, zależy to od strategii, jest to dobrze wyjaśnione w
man git-merge
MERGE STRATEGIES
sekcji.Tylko
octopus
iours
/theirs
obsługują łączenie wielu oddziałów jednocześnie,recursive
na przykład nie.octopus
odmawia połączenia, jeśli byłyby konflikty, iours
jest to połączenie trywialne, więc nie może być konfliktów.Te polecenia generują nowe zatwierdzenie, które będzie miało więcej niż 2 rodziców.
Zrobiłem jeden
merge -X octopus
na Git 1.8.5 bez konfliktów, aby zobaczyć, jak to działa.Stan początkowy:
Akcja:
Nowy stan:
Zgodnie z oczekiwaniami
E
ma 3 rodziców.TODO: jak dokładnie octopus działa na modyfikacjach pojedynczego pliku. Rekurencyjne scalanie dwukierunkowe 3-drożne?
Jak działa git, gdy nie ma wspólnej podstawy do łączenia gałęzi?
@Torek wspomina, że od 2.9 scalanie kończy się niepowodzeniem bez
--allow-unrelated-histories
.Wypróbowałem to empirycznie na Git 1.8.5:
a
zawiera:Następnie:
a
zawiera:Interpretacja:
a\nc\n
jako dodaniem pojedynczej liniiźródło
e379fdf34fee96cd205be83ff4e71699bdc32b18
), Git odmawia teraz połączenia, jeśli nie ma bazy scalania, chyba że dodasz--allow-unrelated-histories
.--allow-unrelated-histories
można pominąć, jeśli nie ma wspólnych ścieżek plików między scalanymi gałęziami.ours
strategia łączenia, ale nie matheirs
strategii łączenia.recursive
+theirs
strategia może rozwiązać tylko dwie gałęzie. git-scm.com/docs/git-merge#_merge_strategiesJa też jestem zainteresowany. Nie znam odpowiedzi, ale ...
Myślę, że scalanie git jest wysoce wyrafinowane i będzie bardzo trudne do zrozumienia - ale jednym ze sposobów podejścia jest od jego prekursorów i skupienie się na sercu twojego zainteresowania. To znaczy, biorąc pod uwagę dwa pliki, które nie mają wspólnego przodka, w jaki sposób git merge sprawdza, jak je scalić i gdzie występują konflikty?
Spróbujmy znaleźć jakieś prekursory. Od
git help merge-file
:Z Wikipedii: 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
Ten ostatni link to plik PDF z artykułem szczegółowo opisującym
diff3
algorytm. Oto wersja przeglądarki PDF Google . Ma tylko 12 stron, a algorytm to tylko kilka stron - ale w pełni matematyczne podejście. Może się to wydawać zbyt formalne, ale jeśli chcesz zrozumieć scalanie git, musisz najpierw zrozumieć prostszą wersję. Nie sprawdzałem jeszcze, ale z nazwą taką jakdiff3
prawdopodobnie będziesz musiał również zrozumieć diff (który używa najdłuższego wspólnego algorytmu podciągów). Jednak może istnieć bardziej intuicyjne wyjaśnieniediff3
, jeśli masz Google ...Teraz właśnie przeprowadziłem eksperyment porównujący
diff3
igit merge-file
. Biorą te same trzy pliki wejściowe version1 OldVersion Version2 i konflikty Należy zaznaczyć sposób same, z<<<<<<< version1
,=======
,>>>>>>> version2
(diff3
również||||||| oldversion
), pokazując ich wspólne dziedzictwo.Użyłem pustego pliku dla starej wersji i prawie identycznych plików dla wersji1 i wersji2 z tylko jedną dodatkową linią dodaną do wersji2 .
Wynik:
git merge-file
zidentyfikowano pojedynczą zmienioną linię jako konflikt; alediff3
potraktował całe dwa pliki jako konflikt. Zatem, tak wyrafinowany jak diff3, scalanie gita jest jeszcze bardziej wyrafinowane, nawet w tym najprostszym przypadku.Oto rzeczywiste wyniki (użyłem odpowiedzi @ twalberg do tekstu). Zwróć uwagę na potrzebne opcje (zobacz odpowiednie strony podręcznika).
$ git merge-file -p fun1.txt fun0.txt fun2.txt
$ diff3 -m fun1.txt fun0.txt fun2.txt
Jeśli naprawdę cię to interesuje, to trochę królicza nora. Wydaje mi się, że jest tak głęboka, jak wyrażenia regularne, najdłuższy wspólny algorytm podciągów diff, gramatyka bezkontekstowa czy algebra relacyjna. Jeśli chcesz dotrzeć do sedna sprawy, myślę, że możesz, ale wymaga to pewnych zdecydowanych badań.
źródło
Oto oryginalna realizacja
http://git.kaarsemaker.net/git/blob/857f26d2f41e16170e48076758d974820af685ff/git-merge-recursive.py
Zasadniczo tworzysz listę wspólnych przodków dla dwóch zatwierdzeń, a następnie łączysz je rekurencyjnie, albo szybko przesyłając do przodu, albo tworząc wirtualne zatwierdzenia, które są używane na podstawie trójstronnego scalania plików.
źródło
Jeśli ta sama linia uległa zmianie po obu stronach scalania, jest to konflikt; jeśli nie, zmiana z jednej strony (jeśli istnieje) jest akceptowana.
Zmiany, które nie są sprzeczne (patrz wyżej)
Zgodnie z definicją bazy scalającej Git , istnieje tylko jedna (najnowszy wspólny przodek).
To zależy od strategii łączenia (tylko
octopus
iours
/theirs
strategie obsługują łączenie więcej niż dwóch gałęzi).Jest to wyjaśnione na stronie
git merge
podręcznika .źródło
git-merge-recursive
istnieje?git-merge-recursive
powinno być (nie ma strony podręcznika, a Google nic nie daje). Więcej informacji na ten temat można znaleźć na stronach podręcznikagit merge
igit merge-base
.git-merge
Strona mężczyzna igit-merge-base
strony man, że podkreślić omówienia wielu wspólnych przodków i rekurencyjną seryjnej. Czuję, że twoja odpowiedź jest niekompletna bez dyskusji na ten temat.