Czasami jest to praktycznie niemożliwe (z pewnymi wyjątkami, gdzie możesz mieć dodatkowe dane), a rozwiązania tutaj nie działają.
Git nie zachowuje historii odwołań (w tym gałęzi). Przechowuje tylko aktualną pozycję dla każdej gałęzi (głowy). Oznacza to, że z czasem możesz stracić trochę historii gałęzi. Na przykład za każdym razem, gdy rozgałęziasz się, natychmiast tracisz, która gałąź była oryginalna. Wszystko, co robi oddział, to:
git checkout branch1 # refs/branch1 -> commit1
git checkout -b branch2 # branch2 -> commit1
Możesz założyć, że pierwszy zaangażowany w to oddział. Tak bywa, ale nie zawsze tak jest. Nic nie stoi na przeszkodzie, abyś po rozpoczęciu powyższej operacji najpierw skierował się do któregoś z oddziałów. Ponadto nie można zagwarantować niezawodności znaczników czasu git. Dopiero gdy zobowiążesz się do tego, że naprawdę staną się strukturalnie gałęziami.
Podczas gdy na diagramach mamy tendencję do numerowania zatwierdzeń koncepcyjnie, git nie ma naprawdę stabilnej koncepcji sekwencji, gdy gałęzie drzewa zatwierdzenia. W takim przypadku możesz założyć, że liczby (wskazujące kolejność) są określane przez znacznik czasu (fajnie jest zobaczyć, jak interfejs użytkownika git obsługuje rzeczy, gdy ustawisz wszystkie znaczniki czasu na to samo).
Oto, czego człowiek oczekuje koncepcyjnie:
After branch:
C1 (B1)
/
-
\
C1 (B2)
After first commit:
C1 (B1)
/
-
\
C1 - C2 (B2)
Oto, co faktycznie otrzymujesz:
After branch:
- C1 (B1) (B2)
After first commit (human):
- C1 (B1)
\
C2 (B2)
After first commit (real):
- C1 (B1) - C2 (B2)
Zakładasz, że B1 jest oryginalną gałęzią, ale może w rzeczywistości być po prostu martwą gałęzią (ktoś zrobił kasę -b, ale nigdy się jej nie zobowiązał). Dopiero po zaakceptowaniu obu uzyskasz prawidłową strukturę gałęzi w git:
Either:
/ - C2 (B1)
-- C1
\ - C3 (B2)
Or:
/ - C3 (B1)
-- C1
\ - C2 (B2)
Zawsze wiesz, że C1 pojawił się przed C2 i C3, ale nigdy nie wiesz, czy C2 pojawił się przed C3, czy C3 przed C2 (ponieważ możesz na przykład ustawić czas na stacji roboczej na cokolwiek). B1 i B2 również wprowadzają w błąd, ponieważ nie wiadomo, która gałąź była pierwsza. W wielu przypadkach można bardzo dobrze i zazwyczaj dokładnie zgadnąć. To trochę jak tor wyścigowy. Wszystko jest na ogół równe samochodom, wtedy możesz założyć, że samochód, który wyprzedza okrążenie, rozpoczął okrążenie z tyłu. Mamy również konwencje, które są bardzo niezawodne, na przykład mistrz prawie zawsze reprezentuje najdłużej żyjące gałęzie, chociaż niestety widziałem przypadki, w których nawet tak nie jest.
Podany tutaj przykład to przykład zachowania historii:
Human:
- X - A - B - C - D - F (B1)
\ / \ /
G - H ----- I - J (B2)
Real:
B ----- C - D - F (B1)
/ / \ /
- X - A / \ /
\ / \ /
G - H ----- I - J (B2)
Prawdziwe tutaj jest również mylące, ponieważ my, ludzie, czytamy to od lewej do prawej, od korzenia do liścia (odnośnik). Git tego nie robi. Tam, gdzie robimy (A-> B) w naszych głowach, robi git (A <-B lub B-> A). Czyta to od ref do root. Refs mogą być gdziekolwiek, ale zwykle są to liście, przynajmniej dla aktywnych gałęzi. Ref wskazuje na zatwierdzenie i zatwierdzenie zawiera tylko znak podobny do ich rodziców / rodziców, a nie do dzieci. Gdy zatwierdzenie jest zatwierdzeniem scalania, będzie miało więcej niż jednego rodzica. Pierwszy element nadrzędny jest zawsze oryginalnym zatwierdzeniem, z którym został scalony. Pozostali rodzice są zawsze zatwierdzeniami, które zostały połączone w pierwotne zatwierdzenie.
Paths:
F->(D->(C->(B->(A->X)),(H->(G->(A->X))))),(I->(H->(G->(A->X))),(C->(B->(A->X)),(H->(G->(A->X)))))
J->(I->(H->(G->(A->X))),(C->(B->(A->X)),(H->(G->(A->X)))))
Nie jest to bardzo wydajna reprezentacja, a raczej wyrażenie wszystkich ścieżek, które git może pobrać z każdego odwołania (B1 i B2).
Pamięć wewnętrzna Gita wygląda mniej więcej tak (nie że A jako rodzic pojawia się dwukrotnie):
F->D,I | D->C | C->B,H | B->A | A->X | J->I | I->H,C | H->G | G->A
Jeśli zrzucisz surowe zatwierdzenie git, zobaczysz zero lub więcej pól nadrzędnych. Jeśli jest zero, to znaczy, że nie ma rodzica, a zatwierdzenie jest rootem (możesz mieć wiele katalogów głównych). Jeśli jest taki, oznacza to, że nie było scalenia i nie jest to zatwierdzenie główne. Jeśli jest więcej niż jeden, oznacza to, że zatwierdzenie jest wynikiem scalenia, a wszyscy rodzice po pierwszym są zobowiązaniami do scalenia.
Paths simplified:
F->(D->C),I | J->I | I->H,C | C->(B->A),H | H->(G->A) | A->X
Paths first parents only:
F->(D->(C->(B->(A->X)))) | F->D->C->B->A->X
J->(I->(H->(G->(A->X))) | J->I->H->G->A->X
Or:
F->D->C | J->I | I->H | C->B->A | H->G->A | A->X
Paths first parents only simplified:
F->D->C->B->A | J->I->->G->A | A->X
Topological:
- X - A - B - C - D - F (B1)
\
G - H - I - J (B2)
Kiedy oba trafią A, ich łańcuch będzie taki sam, wcześniej ich łańcuch będzie zupełnie inny. Pierwsze popełnienie dwóch kolejnych wspólnych zobowiązań jest wspólnym przodkiem i skąd się rozeszły. mogą występować pewne nieporozumienia między warunkami zatwierdzenia, rozgałęzienia i ref. Możesz w rzeczywistości scalić zatwierdzenie. To właśnie robi scalanie. Ref po prostu wskazuje na zatwierdzenie, a gałąź jest niczym innym jak ref w folderze .git / refs / heads, lokalizacja folderu określa, że ref jest gałąź, a nie czymś innym, jak tag.
Kiedy tracisz historię, połączenie polega na zrobieniu jednej z dwóch rzeczy w zależności od okoliczności.
Rozważać:
/ - B (B1)
- A
\ - C (B2)
W takim przypadku scalenie w dowolnym kierunku spowoduje utworzenie nowego zatwierdzenia z pierwszym nadrzędnym jako zatwierdzonym wskazanym przez bieżącą wypisaną gałąź, a drugim nadrzędnym jako zatwierdzonym na końcu gałęzi, którą scaliłeś w bieżącą gałąź. Musi utworzyć nowe zatwierdzenie, ponieważ obie gałęzie mają zmiany od czasu ich wspólnego przodka, które należy połączyć.
/ - B - D (B1)
- A /
\ --- C (B2)
W tym momencie D (B1) ma teraz oba zestawy zmian z obu gałęzi (siebie i B2). Jednak druga gałąź nie ma zmian w stosunku do B1. Jeśli scalisz zmiany z B1 do B2, aby zostały zsynchronizowane, możesz spodziewać się czegoś, co wygląda następująco (możesz zmusić git merge, aby zrobił to w ten sposób, używając --no-ff):
Expected:
/ - B - D (B1)
- A / \
\ --- C - E (B2)
Reality:
/ - B - D (B1) (B2)
- A /
\ --- C
Otrzymasz to, nawet jeśli B1 ma dodatkowe zatwierdzenia. Dopóki nie zostaną wprowadzone zmiany w B2, których B1 nie ma, oba odgałęzienia zostaną połączone. Robi szybkie przewijanie do przodu, które jest jak rebase (rebazy także jedzą lub linearyzują historię), z wyjątkiem tego, że w przeciwieństwie do rebase, ponieważ tylko jedna gałąź ma zestaw zmian, nie musi stosować zestawu zmian z jednej gałęzi na drugą względem drugiej.
From:
/ - B - D - E (B1)
- A /
\ --- C (B2)
To:
/ - B - D - E (B1) (B2)
- A /
\ --- C
Jeśli przestaniesz pracować nad B1, wszystko będzie dobrze dla zachowania historii na dłuższą metę. Tylko B1 (który może być mistrzem) będzie się zwykle rozwijał, więc lokalizacja B2 w historii B2 z powodzeniem reprezentuje punkt, w którym została scalona z B1. Właśnie tego oczekuje od ciebie git, aby rozgałęzić B z A, a następnie możesz scalić A w B tak, jak chcesz, w miarę gromadzenia się zmian, jednak po scaleniu B z powrotem w A nie oczekuje się, że będziesz pracował na B i dalej . Jeśli kontynuujesz pracę nad swoim oddziałem po szybkim przeniesieniu go z powrotem do gałęzi, nad którą pracowałeś, to za każdym razem poprzednia historia twojego B. Naprawdę tworzysz nową gałąź za każdym razem po szybkim zatwierdzeniu do źródła, a następnie do gałęzi.
0 1 2 3 4 (B1)
/-\ /-\ /-\ /-\ /
---- - - - -
\-/ \-/ \-/ \-/ \
5 6 7 8 9 (B2)
Od 1 do 3 i od 5 do 8 są gałęziami strukturalnymi, które pojawiają się, jeśli śledzisz historię dla 4 lub 9. Nie ma sposobu, aby wiedzieć, do której z tych nienazwanych i niepowiązanych gałęzi strukturalnych należą gałęzie nazwane i odniesienia jako koniec konstrukcji. Możesz założyć na tym rysunku, że 0 do 4 należy do B1, a 4 do 9 należy do B2, ale oprócz 4 i 9 nie mogłem wiedzieć, która gałąź należy do której gałęzi, po prostu narysowałem to w sposób, który daje iluzja tego. 0 może należeć do B2, a 5 może należeć do B1. W tym przypadku istnieje 16 różnych możliwości, do których nazwanych gałęzi może należeć każda gałąź strukturalna.
Istnieje wiele strategii git, które działają w ten sposób. Możesz wymusić, aby git merge nigdy nie przewijał do przodu i zawsze tworzył gałąź scalania. Strasznym sposobem na zachowanie historii gałęzi jest użycie tagów i / lub gałęzi (tagi są naprawdę zalecane) zgodnie z wybraną konwencją. Naprawdę nie poleciłbym pustego zatwierdzenia pustego w gałęzi, w której się łączysz. Bardzo powszechną konwencją jest to, aby nie łączyć się z gałęzią integracji, dopóki nie chce się naprawdę zamknąć gałęzi. Jest to praktyka, do której ludzie powinni się stosować, ponieważ w przeciwnym razie pracujesz nad punktem posiadania oddziałów. Jednak w prawdziwym świecie ideał nie zawsze jest praktyczny, co oznacza, że właściwe postępowanie nie jest wykonalne w każdej sytuacji. Jeśli co