Twierdzenie, dlaczego scalanie jest lepsze w DVCS niż w Subversion, było w dużej mierze oparte na tym, jak rozgałęzianie i scalanie działało w Subversion jakiś czas temu. Subversion w wersji wcześniejszej niż 1.5.0 nie przechowywało żadnych informacji o tym, kiedy gałęzie zostały scalone, dlatego kiedy chciałeś scalić, musisz określić, który zakres wersji musiał zostać scalony.
Dlaczego więc połączenia Subversion są do bani ?
Zastanów się nad tym przykładem:
1 2 4 6 8
trunk o-->o-->o---->o---->o
\
\ 3 5 7
b1 +->o---->o---->o
Gdy chcemy scalić zmiany b1 z bagażnikiem, wydamy następujące polecenie, stojąc na folderze, który został wyewidencjonowany:
svn merge -r 2:7 {link to branch b1}
… Który spróbuje scalić zmiany z b1
lokalnego katalogu roboczego. Następnie zatwierdzasz zmiany po rozwiązaniu wszelkich konfliktów i przetestowaniu wyniku. Po zatwierdzeniu drzewo wersji będzie wyglądać następująco:
1 2 4 6 8 9
trunk o-->o-->o---->o---->o-->o "the merge commit is at r9"
\
\ 3 5 7
b1 +->o---->o---->o
Jednak ten sposób określania zakresów wersji szybko wymyka się spod kontroli, gdy drzewo wersji rośnie, ponieważ subversion nie ma żadnych metadanych dotyczących tego, kiedy i jakie wersje zostały scalone. Zastanów się, co stanie się później:
12 14
trunk …-->o-------->o
"Okay, so when did we merge last time?"
13 15
b1 …----->o-------->o
Jest to w dużej mierze problem związany z projektem repozytorium, który ma Subversion, aby utworzyć gałąź, musisz utworzyć nowy katalog wirtualny w repozytorium, który będzie zawierał kopię pnia, ale nie przechowuje żadnych informacji dotyczących tego, kiedy i co wszystko z powrotem się połączyło. Czasami doprowadzi to do nieprzyjemnych konfliktów scalania. Jeszcze gorsze jest to, że Subversion domyślnie używał łączenia dwukierunkowego, co ma pewne paraliżujące ograniczenia w automatycznym łączeniu, gdy dwie głowy oddziałów nie są porównywane z ich wspólnym przodkiem.
Aby złagodzić to Subversion przechowuje teraz metadane do rozgałęzienia i scalenia. To rozwiązałoby wszystkie problemy, prawda?
A tak przy okazji, Subversion wciąż jest do bani…
W scentralizowanym systemie, takim jak subversion, wirtualne katalogi są do kitu. Dlaczego? Ponieważ każdy ma dostęp do ich przeglądania… nawet te eksperymentalne. Rozgałęzianie jest dobre, jeśli chcesz eksperymentować, ale nie chcesz widzieć eksperymentów wszystkich i ich ciotek . To poważny hałas poznawczy. Im więcej gałęzi dodasz, tym więcej badziewiasz zobaczysz.
Im więcej oddziałów publicznych znajduje się w repozytorium, tym trudniej będzie śledzić wszystkie różne oddziały. Pytanie, które zadajesz, brzmi: czy gałąź jest wciąż w fazie rozwoju, czy naprawdę jest martwa, co trudno powiedzieć w scentralizowanym systemie kontroli wersji.
Z tego, co widziałem, przez większość czasu organizacja i tak domyślnie korzysta z jednego dużego oddziału. Szkoda, bo to z kolei trudno będzie śledzić testowanie i wydawanie wersji, a wszystko, co dobre, pochodzi z rozgałęziania.
Dlaczego więc DVCS, takie jak Git, Mercurial i Bazaar, są lepsze od Subversion przy rozgałęzianiu i scalaniu?
Istnieje bardzo prosty powód: rozgałęzienie to koncepcja pierwszej klasy . Z założenia nie ma wirtualnych katalogów , a rozgałęzienia są twardymi obiektami w DVCS, które muszą być takie, aby działać po prostu z synchronizacją repozytoriów (tj. Push i pull ).
Pierwszą rzeczą, którą robisz podczas pracy z DVCS, jest klonowanie repozytoriów (git clone
, hg clone
i bzr branch
). Klonowanie jest koncepcyjnie tym samym, co tworzenie gałęzi kontroli wersji. Niektórzy nazywają to rozwidleniem lub rozgałęzieniem (chociaż ten drugi często jest również używany w odniesieniu do kolokacji rozgałęzień), ale to po prostu to samo. Każdy użytkownik prowadzi własne repozytorium, co oznacza, że masz rozgałęzienie dla poszczególnych użytkowników .
Struktura wersji nie jest drzewem , lecz grafem . Mówiąc dokładniej, ukierunkowany wykres acykliczny (DAG, co oznacza wykres, który nie ma żadnych cykli). Naprawdę nie musisz zagłębiać się w specyfikację DAG, inaczej niż każde zatwierdzenie ma jedno lub więcej odwołań nadrzędnych (na podstawie których zatwierdzenie było oparte). Dlatego poniższe wykresy pokażą strzałki między wersjami w odwrotnej kolejności z tego powodu.
Byłby to bardzo prosty przykład łączenia; Wyobraź sobie centralne repozytorium o nazwie origin
i użytkownika, Alice, klonującego repozytorium na jej maszynę.
a… b… c…
origin o<---o<---o
^master
|
| clone
v
a… b… c…
alice o<---o<---o
^master
^origin/master
Podczas klonowania każda kopia jest kopiowana do Alicji dokładnie tak, jak była (co jest potwierdzane przez jednoznacznie identyfikowalne hash-id) i oznacza, gdzie znajdują się gałęzie źródła.
Alice następnie pracuje nad swoim repozytorium, angażując się we własne repozytorium i decyduje się na wprowadzenie zmian:
a… b… c…
origin o<---o<---o
^ master
"what'll happen after a push?"
a… b… c… d… e…
alice o<---o<---o<---o<---o
^master
^origin/master
Rozwiązanie jest raczej proste, jedyne, co origin
repozytorium musi zrobić, to wziąć wszystkie nowe wersje i przenieść swoją gałąź do najnowszej wersji (która git nazywa „przewijaniem do przodu”):
a… b… c… d… e…
origin o<---o<---o<---o<---o
^ master
a… b… c… d… e…
alice o<---o<---o<---o<---o
^master
^origin/master
Przypadek użycia, który zilustrowałem powyżej, nawet nie musi niczego scalać . Tak naprawdę problem nie dotyczy scalania algorytmów, ponieważ algorytm scalania trójstronnego jest prawie taki sam między wszystkimi systemami kontroli wersji. Problem dotyczy bardziej struktury niż czegokolwiek innego .
A co powiesz na przykład, który ma prawdziwe połączenie?
Trzeba przyznać, że powyższy przykład jest bardzo prostym przypadkiem użycia, więc zróbmy znacznie bardziej pokręcony, aczkolwiek bardziej powszechny. Pamiętasz, że origin
zaczęło się od trzech wersji? Cóż, facet, który to zrobił, nazwał go Bob , pracował sam i dokonał zmian w swoim repozytorium:
a… b… c… f…
bob o<---o<---o<---o
^ master
^ origin/master
"can Bob push his changes?"
a… b… c… d… e…
origin o<---o<---o<---o<---o
^ master
Teraz Bob nie może wypchnąć swoich zmian bezpośrednio do origin
repozytorium. System wykrywa to poprzez sprawdzenie, czy wersje Boba bezpośrednio pochodzą od wersji origin
, co w tym przypadku nie. Każda próba wypchnięcia spowoduje, że system powie coś w stylu „ Uh… Obawiam się, że nie mogę pozwolić ci to zrobić, Bob ”.
Więc Bob musi włączyć się, a następnie scalić zmiany (z git pull
; lub hg pull
i merge
; lub bzr merge
). Jest to proces dwuetapowy. Najpierw Bob musi pobrać nowe wersje, które skopiują je z origin
repozytorium. Widzimy teraz, że wykres jest rozbieżny:
v master
a… b… c… f…
bob o<---o<---o<---o
^
| d… e…
+----o<---o
^ origin/master
a… b… c… d… e…
origin o<---o<---o<---o<---o
^ master
Drugim krokiem procesu ściągania jest połączenie rozbieżnych wskazówek i zatwierdzenie wyniku:
v master
a… b… c… f… 1…
bob o<---o<---o<---o<-------o
^ |
| d… e… |
+----o<---o<--+
^ origin/master
Mamy nadzieję, że scalanie nie spowoduje konfliktów (jeśli ich przewidujesz, możesz wykonać dwa kroki ręcznie w git przy pomocy fetch
i merge
). Później należy ponownie wprowadzić te zmiany origin
, co spowoduje szybkie scalenie do przodu, ponieważ zatwierdzenie scalania jest bezpośrednim potomkiem najnowszego w origin
repozytorium:
v origin/master
v master
a… b… c… f… 1…
bob o<---o<---o<---o<-------o
^ |
| d… e… |
+----o<---o<--+
v master
a… b… c… f… 1…
origin o<---o<---o<---o<-------o
^ |
| d… e… |
+----o<---o<--+
Istnieje inna opcja scalenia w git i hg, zwana rebase , która przeniesie zmiany Boba do najnowszych zmian. Ponieważ nie chcę, aby ta odpowiedź była bardziej szczegółowa, pozwolę ci zamiast tego przeczytać na ten temat dokumenty git , mercurial lub bazar .
W ramach ćwiczenia dla czytelnika spróbuj dowiedzieć się, jak to będzie działać z innym zaangażowanym użytkownikiem. Robi się to podobnie jak powyższy przykład z Bobem. Scalanie między repozytoriami jest łatwiejsze niż mogłoby się wydawać, ponieważ wszystkie zmiany / zatwierdzenia są jednoznacznie identyfikowalne.
Istnieje również kwestia wysyłania łat między poszczególnymi programistami, co było ogromnym problemem w Subversion, który jest łagodzony w git, hg i bzr dzięki unikalnie identyfikowalnym wersjom. Gdy ktoś połączy swoje zmiany (tj. Wykona zatwierdzenie scalenia) i wyśle je wszystkim pozostałym w zespole do konsumpcji poprzez wypchnięcie do centralnego repozytorium lub wysłanie łatek, wówczas nie muszą się martwić o scalenie, ponieważ to już się stało . Martin Fowler nazywa ten sposób pracy rozwiązłą integracją .
Ponieważ struktura różni się od Subversion, dzięki zastosowaniu DAG, umożliwia rozgałęzianie i scalanie w łatwiejszy sposób nie tylko dla systemu, ale także dla użytkownika.