Dlaczego tak wiele projektów woli „git rebase” niż „git merge”?

67

Jedną z zalet korzystania z DVCS jest przepływ pracy edycji-zatwierdzania-scalania (w stosunku do edycji-scalania-zatwierdzania często wymuszanej przez CVCS). Zezwalanie na rejestrowanie każdej unikalnej zmiany w repozytorium niezależnie od fuzji zapewnia, że DAG dokładnie odzwierciedla prawdziwy rodowód projektu.

Dlaczego tak wiele witryn mówi o tym, że chcą „uniknąć zatwierdzeń scalania”? Czy połączenie scalania przed zatwierdzeniem lub zmiany po scaleniu nie utrudnia izolacji regresji, przywracania wcześniejszych zmian itp.?

Wyjaśnienie: Domyślne zachowanie DVCS polega na tworzeniu zatwierdzeń scalania. Dlaczego tak wiele miejsc mówi o chęci zobaczenia historii rozwoju liniowego, która ukrywa te zobowiązania do scalania?

Jace Browning
źródło
18
Narzędzia nie zawsze działają poprawnie. A kiedy źle to zrozumieją, chłopcze, czy źle to zrozumieli.
Oded
2
@Oded czy masz na myśli automatyczne zatwierdzenia scalania (takie jak te utworzone przez git pull)? Rozumiem, dlaczego może to być problematyczne, ale moje pytanie dotyczy wszystkich zatwierdzeń scalania.
Jace Browning
możliwy duplikat Czy częste, skomplikowane konflikty scalania są oznaką problemów? „Fuzje i rebazy powinny powodować dokładnie te same konflikty, dla tych nieodłącznych konfliktów, które człowiek musi rozwiązać (tj. Dwóch programistów zmieniających ten sam wiersz kodu) ...”
gnat
Myśl, którą miałem po opublikowaniu mojej odpowiedzi, że IMO nie pasuje dokładnie jako odpowiedź: git pull --rebase == svn up(luźno-równa, nie ścisła-równa)
Izkata,
w scentralizowanym sklepie, takim jak SourceSafe, zmiany byłyby nadal rejestrowane podczas ciągłego wysiłku, ale po osiągnięciu stabilnego płaskowyżu oznaczalibyśmy wszystkie pliki na określony dzień. Następnie przynajmniej możesz skorelować błędy z wcześniejszą lub późniejszą wersją lub wersją lub z linią bazową.
Andyz Smith

Odpowiedzi:

64

Ludzie chcą uniknąć zatwierdzania scalania, ponieważ sprawia, że ​​dziennik jest ładniejszy. Poważnie. Wygląda na scentralizowane dzienniki, z którymi dorastali, i lokalnie mogą cały swój rozwój w jednym oddziale. Nie ma żadnych korzyści oprócz tych estetycznych, a oprócz tych, o których wspomniałeś, ma kilka wad, takich jak skłonność do konfliktów, aby wyciągać bezpośrednio od kolegi bez przechodzenia przez „centralny” serwer.

Karl Bielefeldt
źródło
5
Jesteś jedynym, który poprawnie zrozumiał moje pytanie. Jak mogę to wyjaśnić?
Jace Browning
12
Może sformułowanie „Jakie są korzyści z liniowej historii rozwoju?”
Karl Bielefeldt,
11
„Często robisz to, aby upewnić się, że twoje zobowiązania odnoszą się do zdalnego oddziału - być może w projekcie, do którego próbujesz się przyczynić, ale którego nie utrzymujesz. W takim przypadku wykonasz swoją pracę w oddziale, a następnie przenieś swoją pracę na origin / master, gdy będziesz gotowy przesłać łatki do głównego projektu. W ten sposób opiekun nie musi wykonywać żadnych prac integracyjnych - wystarczy szybkie przewinięcie do przodu lub czysta aplikacja. „ git-scm.com/book/en/Git-Branching-Rebasing
Andyz Smith
12
Sprawiasz, że brzmi to po prostu kosmetycznie, ale w rzeczywistości jest praktyczne: o wiele łatwiej jest zrozumieć, co dzieje się w historii liniowej, niż ze skomplikowanego DAG z przecinającymi się i łączącymi liniami.
Daniel Hershcovich
20
„Oprócz tych estetyki nie ma żadnych korzyści” - nie wiem o tym. Naprawdę trudno jest zrozumieć, co dzieje się w twojej historii, kiedy twoja historia wygląda jak płytka drukowana. (Zakładając, że wielu członków zespołu ma swoje oddziały i przepływy pracy.)
Archagon
46

W dwóch słowach: git bisect

Liniowa historia pozwala wskazać rzeczywiste źródło błędu.


Przykład. To jest nasz początkowy kod:

def bar(arg=None):
    pass

def foo():
    bar()

Oddział 1 dokonuje pewnych refaktoryzacji, które argnie są już ważne:

def bar():
    pass

def foo():
    bar()

Oddział 2 ma nową funkcję, z której należy korzystać arg:

def bar(arg=None):
    pass

def foo():
    bar(arg=1)

Nie będzie żadnych konfliktów scalania, jednak wprowadzono błąd. Na szczęście ten konkretny zostanie złapany na etapie kompilacji, ale nie zawsze mamy tyle szczęścia. Jeśli błąd przejawia się jako nieoczekiwane zachowanie, a nie błąd kompilacji, możemy go nie znaleźć przez tydzień lub dwa. W tym momencie git bisectna ratunek!

O kurcze. Oto, co widzi:

(master: green)
|             \_________________________
|                \                      \
(master: green)  (branch 1: green)     (branch 2: green)
|                 |                     |
|                 |                     |
(master/merge commit: green)            |
|                         ______________/
|                        /
(master/merge commit: red)
|
...days pass...
|
(master: red)

Więc kiedy wyślemy, git bisectaby znaleźć zatwierdzenie, które złamało kompilację, wskaże zatwierdzenie scalenia. Cóż, to trochę pomaga, ale zasadniczo wskazuje na pakiet zatwierdzeń, a nie na jeden. Wszyscy przodkowie są zieleni. Z drugiej strony, dzięki rebasingowi otrzymujesz liniową historię:

(master: green)
|
(master: green)
|
(all branch 1 commits: green)
|
(some branch 2 commits: green)
|
(branch 2 commit: red)
|
(remaining branch 2 commits: red)
|
...days pass...
|
(master: still red)

Teraz git bisectwskaże dokładnie zatwierdzenie funkcji, które złamało kompilację. Idealnie, komunikat zatwierdzenia wyjaśni, co było wystarczająco dobre, aby zrobić kolejny refaktor i naprawić błąd od razu.

Efekt jest spotęgowany tylko w dużych projektach, gdy opiekunowie nie napisali całego kodu, więc niekoniecznie pamiętają, dlaczego dane zatwierdzenie zostało wykonane / po co była każda gałąź. Tak więc ustalenie dokładnego zatwierdzenia (a następnie możliwość sprawdzenia zatwierdzeń wokół niego) jest wielką pomocą.


Mimo to (obecnie) nadal wolę scalanie. Przejście na gałąź wydania zapewni Ci liniową historię do wykorzystania git bisect, przy jednoczesnym zachowaniu prawdziwej historii do codziennej pracy.

Izkata
źródło
2
Dziękuję za to wyjaśnienie. Czy możesz wyjaśnić, dlaczego nadal preferujesz scalanie? Wiem, że wiele osób woli przełączyć się na bazowanie w większości sytuacji, ale nigdy nie byłem w stanie zrozumieć, dlaczego.
Philip,
@Philip Jestem trochę niezdecydowany, ponieważ używam gita tylko przez kilka miesięcy przy małych osobistych projektach, nigdy w zespole. Ale podczas śledzenia tego, co ostatnio zrobiłem, gitk --allniezwykle przydatna jest możliwość obserwowania przepływu zatwierdzeń między gałęziami (zwłaszcza, że ​​zwykle mam 3-gałęziowe gałęzie w danym momencie i przełączam się między nimi każdego dnia, w zależności od mojego nastroju w czas).
Izkata,
16
Ale scalenie jest problemem, więc chcesz zatwierdzić scalanie w bisectwyniku. Łatwo jest znaleźć poszczególne zatwierdzenia z innym blamelub innym, bisectjeśli chcesz kopać głębiej. W przypadku historii liniowej scalanie odbywa się poza księgą, więc nie można już stwierdzić, że problem polegał na złym scaleniu. Wygląda to jak idiota używający argumentu, który nie istnieje, zwłaszcza jeśli argument został usunięty kilka poprzednich zatwierdzeń. Próbowanie dowiedzieć się, dlaczego to zrobił, jest znacznie trudniejsze, gdy niszczysz historię.
Karl Bielefeldt,
1
Naprawdę nie zgadzam się z tą odpowiedzią. Rebasing czasami niszczy użyteczność bisecta. Z wyjątkiem głównych zatwierdzeń bazy, zatwierdzenia z bazy mogą nie być możliwe do uruchomienia. Przykład: rozgałęziłeś się w A i sprawiłeś, że B, C, D, ktoś popchnął E. Jesteś B i C i pracujesz, ale bazujesz na E, jesteś B i C nie można uruchomić lub można uruchomić i nie działają. Najlepszy przypadek to rezygnacja, najgorszy przypadek, gdy myślisz, że B lub C zawiera problem, podczas gdy w rzeczywistości jest to D.
Lan
4
@Izkata Dlaczego używasz bisecta? Ponieważ zdarzają się błędy / błędy. To upokarzająca rzeczywistość. Być może „przeciągnięcie myszy w lewo” nie było wcześniej na liście testów automatycznych / ręcznych i teraz zauważamy, że ta funkcja jest zepsuta, ale wiemy, że kiedyś działała.
Lan,
16

Krótko mówiąc, ponieważ łączenie jest często innym miejscem, w którym coś może się nie udać, i musi tylko raz pójść nie tak, aby ludzie bardzo bali się poradzić sobie z tym ponownie (raz ugryziony dwukrotnie, jeśli chcesz).

Załóżmy, że pracujemy nad nowym ekranem zarządzania kontem, i okazuje się, że w przepływie pracy nowego konta wykryto błąd. OK, bierzemy dwie oddzielne ścieżki - kończysz zarządzanie kontem, a ja naprawiam błąd w nowych kontach. Ponieważ oboje mamy do czynienia z kontami, pracowaliśmy z bardzo podobnym kodem - być może musieliśmy nawet dostosować te same fragmenty kodu.

W tej chwili mamy dwie różne, ale w pełni działające wersje oprogramowania. Obaj przeprowadziliśmy zatwierdzenie naszych zmian, oboje sumiennie przetestowaliśmy nasz kod i niezależnie jesteśmy bardzo pewni, że wykonaliśmy świetną robotę. Co teraz?

Czas połączyć się, ale ... cholera, co się teraz stanie? Możemy równie dobrze przejść od dwóch działających zestawów oprogramowania do jednego, zunifikowanego, okropnie zepsutego fragmentu nowo błędnego oprogramowania, w którym zarządzanie kontem nie działa, a nowe konta są zepsute, a ja nawet nie wiem, czy nadal występuje stary błąd .

Być może oprogramowanie było inteligentne i powiedziało, że wystąpił konflikt, i nalegaliśmy, abyśmy udzielili mu wskazówek. Cóż, cholera - siadam, aby to zrobić i widzę, że dodałeś złożony kod, którego od razu nie rozumiem. Myślę, że jest to sprzeczne ze zmianami, które wprowadziłem ... Pytam, a kiedy masz minutę, sprawdzasz i widzisz mój kod, którego nie rozumiesz. Jedno lub oboje z nas musi poświęcić trochę czasu, aby usiąść, odpowiednio przygotować połączenie i być może ponownie przetestować całą sprawę, aby upewnić się, że jej nie złamaliśmy.

Tymczasem 8 innych facetów popełnia kod, tak jak są sadystami, zrobiłem kilka drobnych poprawek i przesłałem je, zanim zdałem sobie sprawę, że mamy konflikt scalania, a mężczyzna z pewnością wydaje się, że to dobry moment na przerwę, a może ty są wolne po południu lub utknęły na spotkaniu lub czymkolwiek. Może powinienem wziąć urlop. Lub zmień kariery.

Aby uciec przed tym koszmarem, niektórzy ludzie bardzo boją się zaangażowania (co nowego, amiright?). Naturalnie ryzykujemy awersję w takich scenariuszach - chyba że uważamy, że jesteśmy do dupy i i tak to spieprzymy, w takim przypadku ludzie zaczną działać z lekkomyślnym porzuceniem. westchnienie

Więc proszę bardzo. Tak, nowoczesne systemy zostały zaprojektowane w celu złagodzenia tego bólu, i powinien on być w stanie łatwo wycofywać się i bazować, obniżać i freebase i hanglide i tak dalej.

Ale to wszystko więcej pracy, a my po prostu chcemy nacisnąć przycisk na kuchence mikrofalowej i zrobić 4-daniowy posiłek, zanim zdążymy znaleźć widelec, i wszystko to wydaje się bardzo niespełnione - kod jest pracą, jest produktywny, jest znaczący, ale z wdziękiem obsługa scalania po prostu się nie liczy.

Programiści z reguły muszą rozwinąć świetną pamięć roboczą, a następnie mają tendencję do natychmiastowego zapominania o wszystkich śmieciach i nazwach zmiennych oraz określaniu zakresu, gdy tylko zakończą problem, i radzą sobie z konfliktem scalania (lub, co gorsza, źle wykonane połączenie) jest zaproszeniem do przypomnienia o twojej śmiertelności.

BrianH
źródło
5
Niesamowicie sformułowane, proszę pana!
Southpaw Hare
10
Odpowiada to, dlaczego ludzie boją się scalenia, a nie dlatego, że zatwierdzanie łączenia jest uważane przez wielu za złe.
Jay
23
Problem z tą odpowiedzią polega na tym, że nadal dotyczy to bazy . W końcu rebase nadal dokonuje scalania, w którym zmieniłeś wiersz kodu, który już został zmieniony. Myślę, że jest to problem ze sposobem, w jaki pytanie zostało zadane.
deworde
1
Głosowałem za odpowiedzią, ponieważ ponieważ jest ona nadal wymowna, nie ma sensu, imho.
Eugene
5
Wydaje się, że wcale nie uderza to w rebase vs. merge-merge.
Andyz Smith,
5

Rebasing zapewnia ruchomy punkt rozgałęzienia, który upraszcza proces wypychania zmian z powrotem do linii bazowej. Pozwala to traktować długo działającą gałąź jak lokalną zmianę. Bez zmiany zasad gałęzie gromadzą zmiany od linii bazowej, które zostaną uwzględnione w połączonych zmianach z powrotem do linii bazowej.

Scalenie pozostawia linię bazową w oryginalnym punkcie rozgałęzienia. Jeśli połączysz kilka tygodni zmian z linii, z której się rozgałęziłeś, masz teraz wiele zmian w punkcie rozgałęzienia, z których wiele będzie w linii podstawowej. Utrudnia to identyfikację zmian w oddziale. Przesunięcie zmian z powrotem do linii bazowej może powodować konflikty niezwiązane z wprowadzonymi zmianami. W przypadku konfliktów możliwe jest wprowadzanie niespójnych zmian. Trwające fuzje wymagają zarządzania, a zmiany są stosunkowo łatwe.

Rebase przenosi punkt rozgałęzienia do najnowszej wersji na linii bazowej. Wszelkie napotkane konflikty będą dotyczyły wyłącznie Twoich zmian. Wprowadzanie zmian jest znacznie prostsze. Konflikty są rozwiązywane w lokalnym oddziale poprzez wykonanie dodatkowej bazy. W przypadku sprzecznych wypychań ostatni, który wypchnie ich zmianę, będzie musiał rozwiązać problem ze swoją zmianą.

BillThor
źródło
1
więc wszystko, co robi, to obniża nowe zmiany do poziomu, który uważa się za „niewłaściwą rzecz”, ponieważ są jedyną zmianą . Jeśli wszystkie stare zmiany zostaną uwzględnione podczas scalania, wszystkie zmiany będą na równi, jeśli chodzi o to, czy są one dzisiaj „właściwą rzeczą”.
Andyz Smith
@AndyzSmith Nie uważam nowych zmian za niewłaściwe . Próbując ustalić, co należy zmienić, należy je popchnąć, mają rację . Po dokonaniu zmiany bazy możesz zignorować stare zmiany, ponieważ są one częścią twojej linii bazowej.
BillThor,
racja, więc jeśli nie jesteś pewien, jaki jest twój punkt odniesienia, tj. czy to, co już zmieniłeś, jest gotowe do scalenia, jest „właściwą rzeczą”, to nie bazuj.
Andyz Smith
A jeśli ktoś popchnie cię do zmiany i zepsuje kompilację, ponieważ ich prototyp funkcji nie pasuje do istniejącego prototypu, od razu wiesz, z powodu bazy, że nowy facet jest w błędzie. nie musicie zgadywać, że może nowy facet ma odpowiedni prototyp, a poprzednio nie zmienione, niezastosowane zmiany zestawu zmian są tymi z niewłaściwym prototypem.
Andyz Smith
4

Zautomatyzowane narzędzia stają się coraz lepsze w zapewnianiu, że scalający się kod będzie się kompilował i działał, unikając w ten sposób konfliktów składniowych , ale nie mogą zagwarantować braku logicznych konfliktów, które mogą zostać wprowadzone przez scalenia. Tak więc „udane” scalenie daje poczucie fałszywej pewności, podczas gdy w rzeczywistości nic nie gwarantuje, a ty musisz powtórzyć wszystkie testy.

Prawdziwy problem z rozgałęzianiem i scalaniem, tak jak to widzę, polega na tym, że przysłania przysłowiową puszkę. Pozwala powiedzieć „Po prostu będę pracować w moim własnym małym świecie” przez tydzień i rozwiążę wszelkie problemy, które pojawią się później. Ale naprawianie błędów jest zawsze szybsze / tańsze, gdy są świeże. Zanim wszystkie gałęzie kodu zaczną się scalać, możesz już zapomnieć o niuansach rzeczy, które zostały wykonane.

Weź oba wyżej wymienione problemy razem, a możesz znaleźć się w sytuacji, w której łatwiej i łatwiej jest sprawić, aby wszyscy pracowali z tego samego tułowia i stale rozwiązywać konflikty, gdy pojawiają się, nawet jeśli spowalniają aktywny rozwój.

MrFox
źródło
4
Odpowiada to, dlaczego ludzie boją się scalenia, a nie dlatego, że zatwierdzanie łączenia jest uważane przez wielu za złe.
Jay
Więc, ten bagażnik, do którego się odwołujesz, czy to obejmuje rebase? Wyjaśnij proszę.
Andyz Smith
@AndyzSmith „trunk” to termin svn na odpowiednik gałęzi „master”
gita
@Izkata, więc ta odpowiedź opowiada się za niestosowaniem scalania lub rebase?
Andyz Smith
@AndyzSmith Tak, też tak to czytam. I nie zgadzam się z tym; po pracy w zespole z 7 innymi programistami w sugerowany sposób, nie sugeruję tego. Powinniśmy byli używać gałęzi funkcji bardziej niż my, ale większość z nich boi się scalenia (jak BrianDHall w swojej odpowiedzi).
Izkata
0

Dodatkowym istotnym punktem jest to, że: poprzez zmianę wersji mogę łatwo wybrać lub przywrócić funkcję w mojej gałęzi wydania.

Bruno Schäpper
źródło
1
Czy to jest skomplikowane?
Akavall,
Jasne :-) W git flow regularnie tworzymy nowe gałęzie wydań. Jeśli po utworzeniu błąd zostanie naprawiony w gałęzi programistycznej, git cherry-pick -x <commit>zastosujemy go do gałęzi wydania. Lub, możemy chcieć cofnąć coś w tej wersji, używając git revert <commit>. Jeśli <commit>jest to scalenie, staje się owłosione. Jest to możliwe, o ile wiem, ale niełatwe. Kiedy używasz rebase, każde „scalenie” jest jednym gigantycznym, ale regularnym zatwierdzeniem, łatwo cherr-do pobrania i odwracalne.
Bruno Schäpper,
0

W żadnej z odpowiedzi nie powiedziano trzech rzeczy:

  • Różnicowanie wewnątrz gałęzi:

    • Różnicowanie między dowolną parą zatwierdzeń w gałęzi staje się niezwykle trudne, gdy występują zatwierdzenia scalania.
  • Scalanie jednego elementu na raz:

    • Kiedy rozwiązujesz różnicę między dwiema gałęziami, scalanie zwykle odbywa się naraz, a wszelkie konflikty scalające, które pozostały Ci do zrobienia, bez kontekstu, za który konkretny zatwierdzenie były odpowiedzialne za konflikt. Jeśli zmienisz bazę, baza zatrzyma się w momencie wystąpienia konfliktu i pozwoli ci rozwiązać go w tym kontekście.
  • Czyszczenie przed wypchnięciem:

    • Jeśli popełniłeś błąd, który później musisz naprawić, jeśli nie wypchnąłeś interaktywnego rebase, pozwoli ci połączyć / podzielić / zmienić / przenieść zatwierdzenia. Chociaż możesz to zrobić, jeśli się połączyłeś, staje się to bardzo trudne, jeśli chcesz połączyć / podzielić / zmienić / przesunąć się przez granicę scalania.
Catskul
źródło