Kiedy używasz Git rebase zamiast Git Merge?

1545

Kiedy zaleca się stosowanie Git rebase vs. Git Merge?

Czy nadal muszę się łączyć po udanym rebase?

Coocoo4Cocoa
źródło
16
to jest dobre: atlassian.com/git/tutorials/merging-vs-rebasing
stackexchanger
6
Jednym z problemów osób, które lubią używać rebase, jest to, że odstrasza ich od regularnego wypychania kodu. Dlatego brak czystej historii uniemożliwia im udostępnianie kodu, co moim zdaniem jest ważniejsze.
static_rtti
9
@static_rtti: To po prostu nieprawda. Używasz błędnego przepływu opartego na rebase, jeśli nie pozwala ci to na regularne wprowadzanie zmian.
juzzlin
5
Szkoda, że ​​odpowiedzi Andrew Arnotta i Pace'a nie zostały opublikowane wcześniej, ponieważ odpowiadają na to pytanie bardziej wyczerpująco niż wcześniejsze odpowiedzi, które zgromadziły już wiele głosów.
Mark Booth

Odpowiedzi:

1135

Krótka wersja

  • Scalenie pobiera wszystkie zmiany w jednym oddziale i łączy je w innym oddziale w jednym zatwierdzeniu.
  • Rebase mówi, że chcę, aby punkt, w którym rozgałęziłem się, przeszedł do nowego punktu początkowego

Kiedy więc używasz jednego z nich?

Łączyć

  • Załóżmy, że utworzyłeś gałąź w celu opracowania jednej funkcji. Kiedy chcesz przywrócić te zmiany do mistrza, prawdopodobnie chcesz scalić (nie obchodzi cię utrzymanie wszystkich tymczasowych zatwierdzeń).

Rebase

  • Drugi scenariusz byłby taki, gdybyś zaczął programować, a następnie inny programista dokonał niezwiązanej zmiany. Prawdopodobnie chcesz ciągnąć, a następnie zmieniają bazę oprzeć swoje zmiany z bieżącej wersji z repozytorium.
Rob Di Marco
źródło
105
@Rob wspomniał o utrzymywaniu tymczasowych zatwierdzeń podczas łączenia. Wierzę, że domyślnie połączenie gałęzi B (gałęzi funkcji, nad którą pracujesz) w gałęzi M (gałąź główna) utworzy jedno zatwierdzenie w M dla każdego zatwierdzenia dokonanego w B od czasu rozejścia się dwóch. Ale jeśli scalisz przy użyciu opcji --squash, wszystkie zatwierdzenia dokonane w gałęzi B zostaną „połączone w całość” i scalone jako pojedyncze zatwierdzenie w gałęzi M, utrzymując dziennik w gałęzi master ładnie i czysto. Squashing jest prawdopodobnie tym, czego chcesz, jeśli masz wielu programistów pracujących niezależnie i łączących się z powrotem w master.
spaaarky21
19
Uważam, że założenie @ spaaarky21 dotyczące łączenia jest nieprawidłowe. Jeśli scalisz gałąź B z wzorcem M, będzie tylko jedno zatwierdzenie na M (nawet jeśli B ma wiele zatwierdzeń), niezależnie od tego, czy używasz scalenia zwykłego czy --squash. To, co zrobi --squash, to wyeliminowanie odniesienia do B jako rodzica. Dobra wizualizacja jest tutaj: syntevo.com/smartgithg/howtos.html?page=workflows.merge
jpeskin
14
@jpeskin To nie to, co widzę. Właśnie zrobiłem szybki test w celu weryfikacji. Utwórz katalog z plikiem tekstowym, initnowym repozytorium, addplikiem i commit. Kasa nowej gałęzi funkcji ( checkout -b feature.) Zmień plik tekstowy, zatwierdź i powtórz, aby były dwie nowe zmiany w gałęzi funkcji. Następnie checkout masteri merge feature. W log, widzę moje początkowe zatwierdzenie na master, a następnie dwa, które zostały połączone z funkcji. Jeśli ty merge --squash feature, funkcja jest scalona w master, ale nie jest zatwierdzona, więc jedynym nowym zatwierdzeniem w master będzie ta, którą sam wykonasz.
spaaarky21
21
@ spaaarky21 Wygląda na to, że oboje mamy rację. Gdy możliwe jest szybkie przewijanie do przodu (jak w twoim przykładzie), git domyślnie dołącza wszystkie zatwierdzenia do gałęzi funkcji B (lub, jak sugerujesz, możesz użyć --squash, aby połączyć w jeden zatwierdzenie). Ale w przypadku, gdy łączone są dwie rozbieżne gałęzie M i B, git nie uwzględni wszystkich pojedynczych zatwierdzeń z gałęzi B, jeśli zostaną połączone w M (niezależnie od tego, czy użyjesz - quash).
jpeskin
6
Dlaczego to „(nie zależy ci na zachowaniu wszystkich tymczasowych zatwierdzeń)” pomijając jeszcze tę odpowiedź? W 2009 roku nie miało to sensu i teraz nie ma sensu. Ponadto z pewnością chcesz dokonać zmiany bazy tylko wtedy, gdy inny programista wprowadzi odpowiednie zmiany , których potrzebujesz - jeśli dokonają niepowiązanych zmian, gałąź funkcji i tak powinna się łatwo łączyć bez konfliktów, a twoja historia zostałaby zachowana.
Mark Booth
372

To proste. Z rebase mówisz, aby użyć innej gałęzi jako nowej bazy do pracy.

Jeśli masz na przykład gałąź master, utworzysz gałąź, aby zaimplementować nową funkcję i powiesz, że ją nazwiesz cool-feature, oczywiście gałąź główna jest podstawą nowej funkcji.

Teraz w pewnym momencie chcesz dodać nową funkcję zaimplementowaną w masteroddziale. Możesz po prostu przełączyć masteri połączyć cool-featuregałąź:

$ git checkout master
$ git merge cool-feature

Ale w ten sposób dodawany jest nowy atrapa zatwierdzania. Jeśli chcesz uniknąć historii spaghetti, możesz bazować na :

$ git checkout cool-feature
$ git rebase master

A następnie scal je w master:

$ git checkout master
$ git merge cool-feature

Tym razem, ponieważ gałąź tematu ma te same zatwierdzenia wzorca i zatwierdzenia z nową funkcją, scalanie będzie po prostu przewijaniem do przodu.

Aldo „xoen” Giambelluca
źródło
31
but this way a new dummy commit is added, if you want to avoid spaghetti-history- jak jest źle?
ア レ ッ ク ス
6
Również flaga scalania --no-ff jest bardzo przydatna.
Aldo „xoen” Giambelluca
3
@ ア レ ッ ク ス, gdy użytkownik Sean Schofieldumieszcza to w komentarzu: „Rebase jest również fajny, ponieważ kiedy w końcu scalisz swoje rzeczy z powrotem w master (co jest trywialne, jak już opisano), masz go na szczycie historii zatwierdzeń ur. projekty, w których funkcje mogą być napisane, ale scalone kilka tygodni później, nie chcesz po prostu scalić ich w master, ponieważ są one „upychane” w master z powrotem w historii. Osobiście lubię mieć możliwość git log i zobaczyć ta ostatnia funkcja na górze. Uwaga: daty zatwierdzenia są zachowane - zmiana bazy nie zmienia tych informacji.
Adrien Be
4
Myślę, że nosi powtarzać tutaj - należy pamiętać, że wszystkie te terminy ( merge, rebase, fast-forward, itd.) Odnoszą się do konkretnych manipulacji na skierowany graf acykliczny. Stają się łatwiejsze do przemyślenia na podstawie tego modelu mentalnego.
Roy Tinker
10
@Aldo Nie ma nic „czystego” ani „uporządkowanego” w opartej na historii historii. Jest to ogólnie brudne, a IMHO okropne, ponieważ nie masz pojęcia, co się naprawdę wydarzyło. „Najczystsza” historia Gita to ta, która faktycznie miała miejsce. :)
Marnen Laibow-Koser
269

Aby uzupełnić moją własną odpowiedź wspomnianą przez TSamper ,

  • rebase jest często dobrym pomysłem, aby zrobić przed scaleniem, ponieważ chodzi o to, aby zintegrować w swojej gałęzi Ypracę gałęzi, Bz którą się połączysz.
    Ale ponownie, przed scaleniem, rozwiązujesz każdy konflikt w swojej gałęzi (np. „Rebase”, jak w „odtwarzaniu mojej pracy w mojej gałęzi, zaczynając od ostatniego punktu z gałęzi B).
    Jeśli zostanie to wykonane poprawnie, kolejne scalenie z gałęzi do oddział Bmoże być przewijany do przodu.

  • scalanie bezpośrednio wpływa na gałąź docelową B, co oznacza, że scalenia lepiej są trywialne, w przeciwnym razie gałąź Bmoże być długa, aby wrócić do stabilnego stanu (czas na rozwiązanie wszystkich konfliktów)


sens połączenia po rebase?

W przypadku, który opisuję, bazuję Bna moim oddziale, aby mieć możliwość odtworzenia mojej pracy z nowszego punktu B, ale pozostając w moim oddziale.
W takim przypadku nadal potrzebne jest scalenie, aby przenieść moją „odtworzoną” pracę B.

Innym scenariuszem ( opisanym na przykład w Git Ready ) jest wprowadzenie pracy bezpośrednio Bprzez rebase (który pozwala zachować wszystkie twoje miłe commity, a nawet dać ci możliwość ponownego zamówienia ich przez interaktywny rebase).
W takim przypadku (jeśli dokonujesz zmiany bazy będąc w gałęzi B) masz rację: dalsze scalanie nie jest potrzebne:

Drzewo Git domyślnie, gdy nie scaliliśmy ani nie zmieniliśmy podstawy

rebase1

otrzymujemy poprzez zmianę:

rebase3

Drugi scenariusz dotyczy: w jaki sposób przywrócić nową funkcję do poziomu głównego.

Moim celem, opisując pierwszy scenariusz rebase, jest przypomnienie wszystkim, że rebase może być również użyty jako wstępny krok do tego (że „przywróć nową funkcję do master”).
Możesz użyć rebase, aby najpierw wprowadzić master "w" gałęzi nowej funkcji: rebase odtworzy zatwierdzenia nowej funkcji z HEAD master, ale wciąż w gałęzi nowej funkcji, skutecznie przenosząc punkt początkowy gałęzi ze starego zatwierdzenia głównego HEAD-master.
To pozwala ci rozwiązać wszelkie konflikty w twojej gałęzi (w oderwaniu, jednocześnie pozwalając masterowi rozwijać się równolegle, jeśli etap rozwiązywania konfliktu trwa zbyt długo).
Następnie możesz przełączyć na master i scalić new-feature(lub zmienić bazę new-featurena, masterjeśli chcesz zachować commity wykonane w twoimnew-feature Oddział).

Więc:

  • „rebase vs. merge” można postrzegać jako dwa sposoby importowania pracy, powiedzmy, na master.
  • Ale „zmiana bazy, a następnie scalenie” może być prawidłowym przepływem pracy, który najpierw rozwiązuje konflikt w izolacji, a następnie przywraca pracę.
VonC
źródło
17
scalanie po rebase to banalne szybkie przewijanie do przodu bez konieczności rozwiązywania konfliktów.
obecalp 30.04.2009
4
@obelcap: Rzeczywiście, taki jest pomysł: bierzesz cały konflikt problemów w swoim środowisku (master bazy w nowej gałęzi), a następnie współ master, scalasz nową funkcję: 1 pico-sekundę (szybko naprzód) jeśli mistrz nie miał ewolucji
VonC
27
Rebase jest również przyjemny, ponieważ kiedy w końcu scalisz swoje rzeczy z powrotem w master (co jest trywialne, jak już opisano), masz go na szczycie swojej historii zatwierdzeń. W większych projektach, w których funkcje mogą być pisane, ale łączone kilka tygodni później, nie chcesz po prostu scalić ich z masterem, ponieważ zostają one „wypchane” z powrotem do historii master. Osobiście lubię być w stanie robić git log i widzieć tę ostatnią funkcję na górze. Uwaga: daty zatwierdzenia są zachowane - rebase nie zmienia tych informacji.
Sean Schofield,
3
@Joe: w myślach mówisz „powtórz dowolną z moich zmian (dokonanych osobno w mojej gałęzi prywatnej) na tej innej gałęzi, ale zostaw mnie w mojej gałęzi prywatnej po zakończeniu rebase”. To dobra okazja, aby posprzątać lokalną historię, unikając „popełniania błędów w punktach kontrolnych”, złamanych dwusiecznych i nieprawidłowych wyników winy. Zobacz „Przepływ pracy Git”: sandofsky.com/blog/git-workflow.html
VonC
4
@scoarescoare kluczowe jest, aby zobaczyć, jak lokalne zmiany są zgodne na szczycie najnowszej upstream gałęzi. Jeśli jeden z twoich zmian wprowadzi konflikt, natychmiast go zobaczysz. Scalenie wprowadza tylko jedno (scalone) zatwierdzenie, które może wywołać wiele konfliktów bez łatwego sposobu sprawdzenia, który z własnych lokalnych zatwierdzeń dodał wspomniany konflikt. Więc oprócz historii odkurzacza, można uzyskać bardziej precyzyjny obraz zmian Państwo wprowadzić, popełnienia przez commit (odtwarzane przez rebase), w przeciwieństwie do wszystkich zmian wprowadzonych przez głównego oddziału (wrzucano do jednego seryjnej).
VonC
229

TL; DR

W razie wątpliwości skorzystaj z funkcji scalania.

Krótka odpowiedź

Jedyne różnice między rebase a scaleniem to:

  • Wynikowa struktura drzewa historii (generalnie zauważalna tylko podczas przeglądania wykresu zatwierdzeń) jest inna (jedna będzie miała gałęzie, druga nie).
  • Scalanie generuje zazwyczaj dodatkowe zatwierdzenie (np. Węzeł w drzewie).
  • Scal i rebase będą obsługiwać konflikty inaczej. Rebase przedstawi konflikty, które zostaną popełnione w tym samym czasie, gdy scalenie zaprezentuje je wszystkie naraz.

Krótką odpowiedzią jest więc wybranie bazy lub scalenia na podstawie tego, jak ma wyglądać Twoja historia .

Długa odpowiedź

Istnieje kilka czynników, które należy wziąć pod uwagę przy wyborze operacji.

Czy gałąź, z której otrzymujesz zmiany, jest udostępniana innym programistom spoza zespołu (np. Open source, public)?

Jeśli tak, nie bazuj. Rebase niszczy gałąź, a ci programiści będą mieli zepsute / niespójne repozytoria, chyba że użyją git pull --rebase. To dobry sposób na szybkie denerwowanie innych programistów.

Jak wykwalifikowany jest twój zespół programistów?

Rebase jest destrukcyjną operacją. Oznacza to, że jeśli nie zastosujesz go poprawnie, możesz stracić zaangażowaną pracę i / lub złamać spójność repozytoriów innych programistów.

Pracowałem w zespołach, w których wszyscy programiści pochodzili z czasów, gdy firmy mogły sobie pozwolić na oddany personel zajmujący się oddziałami i fuzjami. Ci programiści niewiele wiedzą o Git i nie chcą wiedzieć dużo. W tych zespołach nie ryzykowałbym polecania zmiany bazy z jakiegokolwiek powodu.

Czy sam oddział reprezentuje przydatne informacje

Niektóre zespoły używają modelu gałęzi na funkcję, w którym każda gałąź reprezentuje cechę (lub poprawkę błędu, podfunkcję itp.) W tym modelu gałąź pomaga identyfikować zestawy powiązanych zatwierdzeń. Na przykład można szybko przywrócić funkcję, cofając scalanie tej gałęzi (uczciwie, jest to rzadka operacja). Lub różnicuj funkcję, porównując dwie gałęzie (bardziej powszechne). Rebase zniszczy gałąź, a to nie będzie proste.

Pracowałem również nad zespołami, które korzystały z modelu branża dla programistów (wszyscy tam byliśmy). W tym przypadku sama gałąź nie przekazuje żadnych dodatkowych informacji (zatwierdzenie ma już autora). Ponowne bazowanie nie zaszkodzi.

Czy z jakiegokolwiek powodu chcesz cofnąć scalenie?

Cofanie (jak przy cofaniu) rebase jest znacznie trudne i / lub niemożliwe (jeśli rebase miał konflikty) w porównaniu do cofania fuzji. Jeśli uważasz, że istnieje szansa, że ​​chcesz cofnąć, użyj scalenia.

Czy pracujesz w zespole? Jeśli tak, czy jesteś gotów przyjąć podejście „wszystko albo nic” w tej branży?

Operacje Rebase należy wyciągnąć z odpowiednim git pull --rebase. Jeśli pracujesz sam, być może pamiętasz, z którego powinieneś skorzystać we właściwym czasie. Jeśli pracujesz w zespole, koordynacja będzie bardzo trudna. Właśnie dlatego większość przepływów pracy w bazach zaleca używanie bazy w przypadku wszystkich połączeń (i git pull --rebasewszystkich operacji ściągania).

Wspólne mity

Scalanie niszczy historię (zatwierdza squash)

Zakładając, że masz następujące scalenie:

    B -- C
   /      \
  A--------D

Niektórzy twierdzą, że scalenie „niszczy” historię zatwierdzeń, ponieważ jeśli spojrzysz na dziennik tylko gałęzi master (A – D), przegapisz ważne komunikaty zatwierdzeń zawarte w B i C.

Gdyby to była prawda, nie mielibyśmy takich pytań . Zasadniczo zobaczysz B i C, chyba że wyraźnie poprosisz, aby ich nie widzieć (używając --first-parent). To jest bardzo łatwe do wypróbowania dla siebie.

Rebase pozwala na bezpieczniejsze / prostsze połączenia

Oba podejścia łączą się w różny sposób, ale nie jest jasne, czy jedno jest zawsze lepsze od drugiego i może zależeć od przepływu pracy programisty. Na przykład, jeśli programista ma tendencję do dokonywania regularnych zobowiązań (np. Może dwa razy dziennie, gdy przechodzą z pracy do domu), może być wiele zatwierdzeń dla danego oddziału. Wiele z tych zmian może nie przypominać produktu końcowego (zazwyczaj zmieniam swoje podejście raz lub dwa razy dla każdej funkcji). Gdyby ktoś inny pracował nad pokrewnym obszarem kodu i próbował bazować na moich zmianach, może to być dość żmudna operacja.

Rebase jest fajniejszy / bardziej seksowny / bardziej profesjonalny

Jeśli lubisz pseudonim, rmaby rm -rf„zaoszczędzić czas”, być może rebase jest dla Ciebie.

Moje dwa centy

Zawsze myślę, że kiedyś napotkam scenariusz, w którym Git rebase to niesamowite narzędzie, które rozwiązuje problem. Podobnie jak myślę, napotkam scenariusz, w którym Git reflog to niesamowite narzędzie, które rozwiązuje mój problem. Pracuję z Git od ponad pięciu lat. To się nie stało.

Niechlujne historie nigdy tak naprawdę nie były dla mnie problemem. Nigdy nie czytam tylko historii zmian jako ekscytującej powieści. Przez większość czasu potrzebuję historii i tak będę używać winy Git lub bisecta Git. W takim przypadku zatwierdzenie przez scalenie jest dla mnie naprawdę przydatne, ponieważ jeśli scalenie wprowadziło problem, jest to dla mnie znacząca informacja.

Aktualizacja (4/2017)

Czuję się zobowiązany wspomnieć, że osobiście złagodziłem korzystanie z bazy, chociaż moja ogólna rada nadal obowiązuje. Ostatnio dużo współpracuję z projektem Angular 2 Material . Wykorzystali rebase, aby zachować bardzo czystą historię zatwierdzeń. Pozwoliło mi to bardzo łatwo zobaczyć, co zatwierdziło naprawienie danej wady i czy to zatwierdzenie zostało uwzględnione w wydaniu. Jest to świetny przykład prawidłowego korzystania z bazy danych.

Tempo
źródło
5
Powinna być potwierdzona odpowiedź.
Mik378,
To z pewnością najlepsza odpowiedź. Zwłaszcza z wyjaśnieniem w najnowszej aktualizacji. Pomaga utrzymać czystą i przejrzystą historię git, ale bezpiecznie z niej korzystaj.
zquintana
5
Najbardziej podoba mi się ta odpowiedź. Ale: Rebase nie tworzy „czystej” historii. Tworzy bardziej liniową historię, ale to wcale nie to samo, skoro kto wie teraz wiele „brudu”, który kryje się w każdym zatwierdzeniu? Najczystsza, najczystsza historia Git to ta, która utrzymuje gałąź i integralność.
Marnen Laibow-Koser
3
„Powszechny mit, widzisz zobowiązania B i C”: Niekoniecznie !! Rzeczywiście widzisz B i C tylko wtedy, gdy scalenie było scaleniem do przodu i jest to możliwe tylko wtedy, gdy nie było konfliktów. Jeśli występują konflikty, dostajesz jeden zatwierdzenie! Jednak: Możesz najpierw scalić wzorzec z elementem i rozwiązać tam konflikty, a jeśli następnie scalisz element z elementem głównym, otrzymasz zatwierdzenia B i C oraz pojedyncze zatwierdzenie X z (pierwszego) scalenia z elementu nadrzędnego w swojej historii.
Jeremy Benks,
bardzo dobre wytłumaczenie! Musi więcej głosować!
dimmits
185

Wiele odpowiedzi tutaj mówi, że scalenie zamienia wszystkie twoje commity w jedno, dlatego sugeruję użycie rebase, aby zachować swoje commity. To jest niepoprawne. I zły pomysł, jeśli już pchnąłeś swoje zobowiązania .

Scalanie nie zaciera twoich zobowiązań. Scalanie zachowuje historię! (wystarczy spojrzeć na gitk) Rebase przepisuje historię, co jest złą rzeczą po tym, jak nacisnąłeś ją .

Użyj scalania - nie rebase za każdym razem, gdy już naciskasz.

Oto podejście Linusa (autora Gita) (teraz hostowane na moim blogu, odzyskanym przez Wayback Machine ). To naprawdę dobra lektura.

Lub możesz przeczytać moją własną wersję tego samego pomysłu poniżej.

Rebasing gałęzi na master:

  • zapewnia niepoprawne wyobrażenie o sposobie tworzenia zatwierdzeń
  • zanieczyszcza wzorzec za pomocą szeregu pośrednich zatwierdzeń, które mogły nie zostać dobrze przetestowane
  • może faktycznie wprowadzić przerwy kompilacyjne w tych pośrednich zatwierdzeniach z powodu zmian, które zostały wprowadzone w celu opanowania między momentem utworzenia oryginalnej gałęzi tematu a jej ponownym uruchomieniem.
  • utrudnia znalezienie dobrych miejsc do wzięcia w kasie.
  • Powoduje, że znaczniki czasu na zatwierdzeniach nie są wyrównane z ich chronologicznym porządkiem w drzewie. Zobaczysz więc, że zatwierdzenie A poprzedza zatwierdzenie B w master, ale zatwierdzenie B zostało stworzone jako pierwsze. (Co?!)
  • Powoduje więcej konfliktów, ponieważ poszczególne zatwierdzenia w gałęzi tematów mogą wiązać się z konfliktami scalania, które należy indywidualnie rozwiązać (dalej leżące w historii o tym, co wydarzyło się w każdym zatwierdzeniu).
  • jest przepisaniem historii. Jeśli gałąź, która jest ponownie wystawiana, została wypchnięta gdziekolwiek (współdzielona z kimkolwiek innym niż ty), to spieprzyłeś wszystkich, którzy mają tę gałąź, odkąd przepisałeś historię.

Natomiast scalanie gałęzi tematów w master:

  • zachowuje historię tworzenia gałęzi tematycznych, w tym wszelkie połączenia z gałęzi głównej do gałęzi tematycznej, aby zapewnić jej aktualność. Naprawdę masz dokładne pojęcie o tym, z jakim kodem pracował programista podczas tworzenia.
  • master jest gałęzią składającą się głównie z połączeń, a każdy z tych zatwierdzeń scalania jest zazwyczaj „dobrym punktem” w historii, który można bezpiecznie sprawdzić, ponieważ właśnie tam gałąź tematu była gotowa do zintegrowania.
  • wszystkie indywidualne zatwierdzenia gałęzi tematów są zachowane, w tym fakt, że były w gałęzi tematów, więc izolowanie tych zmian jest naturalne i można wiercić w razie potrzeby.
  • konflikty scalania muszą być rozwiązane tylko raz (w punkcie scalania), więc zmiany zatwierdzania pośredniego dokonane w gałęzi tematu nie muszą być rozwiązywane niezależnie.
  • można to zrobić wielokrotnie bezproblemowo. Jeśli od czasu do czasu zintegrujesz gałąź tematyczną z master, ludzie będą mogli dalej budować gałąź tematyczną i scalać ją niezależnie.
Andrew Arnott
źródło
3
Ponadto git merge ma opcję „--no-ff” (brak szybkiego przewijania do przodu), która pozwala bardzo łatwo przywrócić wszystkie zmiany wprowadzone przez pewne scalenie.
Tiago,
3
Po prostu wyjaśnij: „Odwołujesz się do sytuacji„ za każdym razem, gdy już naciskasz ”- powinno to być odważne. Link do postu Linusa jest świetny, przy okazji, wyjaśnia to.
honzajde
2
ale czy nie jest najlepszą praktyką „aktualizować” master z gałęzi tematów, zanim scalisz gałąź tematów z master przez PR (aby rozwiązać konflikty w gałęzi, a nie master)? Robimy to w ten sposób, więc większość gałęzi tematów ma jako ostatnie zatwierdzenie „scalenie wzorca gałęzi do tematu -...”, ale tutaj jest to wymienione jako „funkcja” bazowania i nikt nie wspomina o połączeniu…?
ProblemsOfSumit,
2
@AndrewArnott „Większość gałęzi tematycznych powinna być w stanie łączyć się bez konfliktów z gałęziami docelowymi”. Jak powinno to być możliwe, gdy 20 programistów pracuje nad 30 gałęziami? Podczas pracy nad twoimi będą fuzje - więc oczywiście musisz zaktualizować gałąź tematu od celu przed utworzeniem PR ... nie?
ProblemsOfSumit
3
Nie zwykle @Sumit. Git może dobrze połączyć oba kierunki, nawet jeśli dokonano zmian w jednej lub obu gałęziach. Tylko wtedy, gdy te same wiersze kodu (lub bardzo blisko) zostaną zmodyfikowane w dwóch gałęziach, dostaniesz konflikty. Jeśli zdarza się to często w dowolnym zespole, zespół powinien przemyśleć, w jaki sposób dystrybuują pracę, ponieważ rozwiązywanie konfliktów jest podatkiem i spowalnia je.
Andrew Arnott,
76

TLDR: To zależy od tego, co najważniejsze - uporządkowanej historii lub prawdziwej reprezentacji sekwencji rozwoju

Jeśli najważniejsza jest uporządkowana historia, najpierw dokonaj zmiany podstawy, a następnie scal swoje zmiany, aby było jasne, jaki dokładnie jest nowy kod. Jeśli już odepchnąłeś oddział, nie bazuj, chyba że poradzisz sobie z konsekwencjami.

Jeśli prawdziwa reprezentacja sekwencji jest najważniejsza, połączysz ją bez bazowania.

Scal oznacza: Utwórz jeden nowy zatwierdzenie, które scali moje zmiany z miejscem docelowym. Uwaga: To nowe zatwierdzenie będzie miało dwoje rodziców - najnowsze zatwierdzenie z ciągu zatwierdzeń i ostatnie zatwierdzenie z drugiego łączonego oddziału.

Rebase oznacza: Utwórz całą nową serię zatwierdzeń, używając mojego obecnego zestawu zatwierdzeń jako wskazówek. Innymi słowy, oblicz, jak wyglądałyby moje zmiany, gdybym zaczął je robić od momentu, w którym się zastanawiam. Dlatego po zmianie bazy może być konieczne ponowne przetestowanie zmian, a podczas zmiany bazy może wystąpić kilka konfliktów.

Biorąc to pod uwagę, dlaczego miałbyś bazować? Tylko po to, aby historia rozwoju była jasna. Załóżmy, że pracujesz nad funkcją X, a kiedy skończysz, scalisz zmiany. Miejsce docelowe będzie teraz miało jedno zatwierdzenie, które powiedziałoby coś w stylu „Dodano funkcję X”. Teraz zamiast scalania, jeśli dokonałeś zmiany bazy, a następnie scaliłeś, historia rozwoju miejsca docelowego zawierałaby wszystkie poszczególne zatwierdzenia w jednym logicznym przejściu. To znacznie ułatwia późniejsze sprawdzanie zmian. Wyobraź sobie, jak trudno byłoby przejrzeć historię programowania, gdyby 50 programistów cały czas łączyło różne funkcje.

To powiedziawszy, jeśli już wypchnąłeś gałąź, nad którą pracujesz w górę, nie powinieneś opierać bazy, ale scalić. W przypadku gałęzi, które nie zostały zepchnięte w górę, wypuść bazę, przetestuj i scal.

Innym razem, gdy możesz chcieć dokonać zmiany bazy, jest czas, gdy chcesz pozbyć się commits ze swojego oddziału, zanim przejdziesz dalej. Na przykład: Zatwierdzenia, które wprowadzają jakiś kod debugowania wcześnie, a inne zatwierdzają dalej to czyszczenie tego kodu. Jedynym sposobem na to jest wykonanie interaktywnej bazy:git rebase -i <branch/commit/tag>

AKTUALIZACJA: Chcesz również użyć rebase, gdy używasz Git do interfejsu z systemem kontroli wersji, który nie obsługuje historii nieliniowej ( na przykład Subversion ). Podczas korzystania z mostu git-svn bardzo ważne jest, aby zmiany, które scalasz z powrotem w Subversion, były sekwencyjną listą zmian na szczycie najnowszych zmian w linii głównej. Można to zrobić tylko na dwa sposoby: (1) Ręcznie odtwórz zmiany i (2) Za pomocą polecenia rebase, które jest znacznie szybsze.

AKTUALIZACJA 2: Jednym z dodatkowych sposobów myślenia o rebase jest to, że umożliwia on rodzaj odwzorowania stylu twórczego na styl akceptowany w repozytorium, do którego się zobowiązujesz. Powiedzmy, że lubisz robić małe, małe kawałki. Masz jedno zatwierdzenie, aby naprawić literówkę, jedno zatwierdzenie, aby pozbyć się nieużywanego kodu i tak dalej. Zanim skończysz to, co musisz zrobić, masz długą serię zatwierdzeń. Powiedzmy teraz, że repozytorium, do którego się zobowiązujesz, zachęca do dużych zatwierdzeń, więc dla pracy, którą wykonujesz, można oczekiwać jednego lub dwóch zatwierdzeń. Jak wziąć ciąg zobowiązań i skompresować je do oczekiwanych? Użyłbyś interaktywnego bazy i zgniótłbyś swoje małe commity na mniejszą część. To samo dotyczy sytuacji, gdy potrzebne było odwrócenie - jeśli twój styl składał się z kilku dużych zmian, ale repozytorium wymagało długich ciągów małych zatwierdzeń. W tym celu skorzystasz również z bazy. Jeśli zamiast tego połączyłeś się, wszczepiłeś teraz swój styl zatwierdzania do głównego repozytorium. Jeśli jest wielu programistów, możesz sobie wyobrazić, jak trudno byłoby śledzić historię z kilkoma różnymi stylami zatwierdzania po pewnym czasie.

AKTUALIZACJA 3: Does one still need to merge after a successful rebase?Tak, robisz. Powodem jest to, że rebase zasadniczo polega na „przesunięciu” commits. Jak powiedziałem powyżej, te zatwierdzenia są obliczane, ale jeśli miałeś 14 zatwierdzeń od punktu rozgałęzienia, to zakładając, że nic nie pójdzie nie tak z twoją bazą, będziesz miał 14 zatwierdzeń przed (od punktu, na który się opierasz) po rebase jest gotowe. Miałeś oddział przed rebase. Będziesz miał gałąź o tej samej długości po. Nadal musisz scalić przed opublikowaniem zmian. Innymi słowy, dokonuj zmiany bazy tyle razy, ile chcesz (ponownie, tylko jeśli nie wysłałeś swoich zmian w górę). Scal dopiero po zmianie bazy.

Carl
źródło
1
Scalenie z masterem może spowodować szybkie przewijanie do przodu. W gałęzi funkcji mogą znajdować się pewne zatwierdzenia, które zawierają drobne błędy lub nawet nie kompilują. Jeśli wykonujesz tylko testy jednostkowe w gałęzi funkcji, niektóre błędy w integracji prześlizgują się przeze mnie. Przed połączeniem z master wymagane są testy integracyjne, które mogą wykazać pewne błędy. Jeśli zostaną one naprawione, funkcja może zostać zintegrowana. Ponieważ nie chcesz zatwierdzać błędnego kodu do opanowania, wydaje się, że konieczny jest rebase, aby uniemożliwić szybkie przewijanie do przodu.
mbx
1
@mbx git mergeobsługuje --no-ffopcję, która zmusza ją do zatwierdzenia scalania.
Gavin S. Yancey
63

Chociaż scalanie jest zdecydowanie najłatwiejszym i najczęstszym sposobem integracji zmian, nie jest jedynym: Rebase jest alternatywnym sposobem integracji.

Zrozumienie Scal trochę lepiej

Kiedy Git wykonuje scalenie, szuka trzech zatwierdzeń:

  • (1) Zatwierdź wspólnego przodka. Jeśli śledzisz historię dwóch gałęzi w projekcie, zawsze mają one co najmniej jeden wspólny zatwierdzenie: w tym momencie obie gałęzie miały tę samą treść, a następnie ewoluowały inaczej.
  • (2) + (3) Punkty końcowe każdej gałęzi. Celem integracji jest połączenie aktualnych stanów dwóch gałęzi. Dlatego ich ostatnie najnowsze wersje są szczególnie interesujące. Połączenie tych trzech zobowiązań spowoduje integrację, do której dążymy.

Szybkie przewijanie do przodu lub scalanie

W bardzo prostych przypadkach jedna z dwóch gałęzi nie ma żadnych nowych zatwierdzeń od czasu rozgałęzienia - jej ostatnie zatwierdzenie jest nadal wspólnym przodkiem.

Wpisz opis zdjęcia tutaj

W takim przypadku wykonanie integracji jest bardzo proste: Git może po prostu dodać wszystkie zatwierdzenia innej gałęzi na szczycie zatwierdzenia wspólnego przodka. W Git ta najprostsza forma integracji nazywana jest łączeniem „do przodu”. Oba oddziały dzielą następnie dokładnie tę samą historię.

Wpisz opis zdjęcia tutaj

Jednak w wielu przypadkach oba oddziały posuwały się naprzód indywidualnie.

Wpisz opis zdjęcia tutaj

Aby dokonać integracji, Git będzie musiał utworzyć nowe zatwierdzenie, które zawiera różnice między nimi - zatwierdzenie scalania.

Wpisz opis zdjęcia tutaj

Human Commits & Merge Commits

Zwykle zatwierdzenie jest starannie tworzone przez człowieka. Jest to znacząca jednostka, która otacza tylko powiązane zmiany i adnotuje je komentarzem.

Zatwierdzanie scalania jest nieco inne: zamiast być tworzone przez programistę, jest tworzone automatycznie przez Git. Zamiast owijać zestaw powiązanych zmian, jego celem jest połączenie dwóch gałęzi, tak jak węzeł. Jeśli chcesz później zrozumieć operację scalania, musisz spojrzeć na historię obu gałęzi i odpowiadający im wykres zatwierdzania.

Integracja z Rebase

Niektóre osoby wolą przejść bez takich automatycznych zatwierdzeń scalania. Zamiast tego chcą, aby historia projektu wyglądała tak, jakby ewoluowała w jedną, prostą linię. Nic nie wskazuje na to, że w pewnym momencie został on podzielony na wiele gałęzi.

Wpisz opis zdjęcia tutaj

Przejdźmy krok po kroku przez operację rebase. Scenariusz jest taki sam jak w poprzednich przykładach: chcemy zintegrować zmiany z gałęzi B do gałęzi A, ale teraz za pomocą rebase.

Wpisz opis zdjęcia tutaj

Zrobimy to w trzech krokach

  1. git rebase branch-A // Synchronises the history with branch-A
  2. git checkout branch-A // Change the current branch to branch-A
  3. git merge branch-B // Merge/take the changes from branch-B to branch-A

Po pierwsze, Git „cofnie” wszystkie zatwierdzenia na gałęzi A, które nastąpiły po tym, jak linie zaczęły się rozgałęziać (po zatwierdzeniu wspólnego przodka). Jednak, oczywiście, nie odrzuci ich: zamiast tego możesz traktować te zobowiązania jako „tymczasowo uratowane”.

Wpisz opis zdjęcia tutaj

Następnie stosuje zatwierdzenia z gałęzi B, które chcemy zintegrować. W tym momencie obie gałęzie wyglądają dokładnie tak samo.

Wpisz opis zdjęcia tutaj

W ostatnim kroku nowe zatwierdzenia w oddziale A są teraz ponownie stosowane - ale na nowej pozycji, oprócz zintegrowanych zatwierdzeń z oddziału B (są ponownie oparte).

Wynik wygląda na to, że rozwój nastąpił w linii prostej. Zamiast zatwierdzenia scalania, które zawiera wszystkie połączone zmiany, zachowano oryginalną strukturę zatwierdzeń.

Wpisz opis zdjęcia tutaj

Wreszcie otrzymujesz czystą gałąź gałęzi A bez niechcianych i automatycznie generowanych zatwierdzeń.

Uwaga: pochodzi z niesamowitego posta autorstwa git-tower. Te wady o rebaseto również dobry czytać w tym samym stanowisku.

Abdullah Khan
źródło
+1 za bardzo fajne diagramy. Zawsze chciałem móc zilustrować przykład git flow w podobny sposób, bez powodzenia.
Mikayil Abdullayev
60

Przed scaleniem / rebase:

A <- B <- C    [master]
^
 \
  D <- E       [branch]

Po git merge master:

A <- B <- C
^         ^
 \         \
  D <- E <- F

Po git rebase master:

A <- B <- C <- D' <- E'

(A, B, C, D, E i F są zatwierdzeniami)

Ten przykład i znacznie lepiej zilustrowane informacje o Git można znaleźć w samouczku Git The Basics .

facet szczotka
źródło
30

Zdanie to otrzymuje:

Ogólnie rzecz biorąc, sposobem na uzyskanie jak najlepszego z obu światów jest bazowanie na lokalnych zmianach, które wprowadziłeś, ale jeszcze ich nie udostępniłeś, zanim prześlesz je w celu wyczyszczenia swojej historii, ale nigdy nie bazuj na niczym, co gdzieś wypchnąłeś .

Źródło: 3.6 Git Branching - Rebasing, Rebase vs. Merge

Joaquin Sargiotto
źródło
25

Ta odpowiedź dotyczy głównie Git Flow . Tabele zostały wygenerowane za pomocą ładnego generatora tabel ASCII , a drzewa historii za pomocą tego cudownego polecenia ( aliasu jako git lg):

git log --graph --abbrev-commit --decorate --date=format:'%Y-%m-%d %H:%M:%S' --format=format:'%C(bold blue)%h%C(reset) - %C(bold cyan)%ad%C(reset) %C(bold green)(%ar)%C(reset)%C(bold yellow)%d%C(reset)%n''          %C(white)%s%C(reset) %C(dim white)- %an%C(reset)'

Tabele są w odwrotnej kolejności chronologicznej, aby były bardziej spójne z drzewami historii. Zobacz także różnicę między pierwszą git mergea git merge --no-ffpierwszą (zwykle chcesz użyć, git merge --no-ffponieważ przybliża to twoją historię do rzeczywistości):

git merge

Polecenia:

Time          Branch "develop"             Branch "features/foo"
------- ------------------------------ -------------------------------
15:04   git merge features/foo
15:03                                  git commit -m "Third commit"
15:02                                  git commit -m "Second commit"
15:01   git checkout -b features/foo
15:00   git commit -m "First commit"

Wynik:

* 142a74a - YYYY-MM-DD 15:03:00 (XX minutes ago) (HEAD -> develop, features/foo)
|           Third commit - Christophe
* 00d848c - YYYY-MM-DD 15:02:00 (XX minutes ago)
|           Second commit - Christophe
* 298e9c5 - YYYY-MM-DD 15:00:00 (XX minutes ago)
            First commit - Christophe

git merge --no-ff

Polecenia:

Time           Branch "develop"              Branch "features/foo"
------- -------------------------------- -------------------------------
15:04   git merge --no-ff features/foo
15:03                                    git commit -m "Third commit"
15:02                                    git commit -m "Second commit"
15:01   git checkout -b features/foo
15:00   git commit -m "First commit"

Wynik:

*   1140d8c - YYYY-MM-DD 15:04:00 (XX minutes ago) (HEAD -> develop)
|\            Merge branch 'features/foo' - Christophe
| * 69f4a7a - YYYY-MM-DD 15:03:00 (XX minutes ago) (features/foo)
| |           Third commit - Christophe
| * 2973183 - YYYY-MM-DD 15:02:00 (XX minutes ago)
|/            Second commit - Christophe
* c173472 - YYYY-MM-DD 15:00:00 (XX minutes ago)
            First commit - Christophe

git merge vs git rebase

Pierwszy punkt: zawsze łącz funkcje w celu opracowania, nigdy nie bazuj w oparciu o funkcje . Jest to konsekwencja Złotej Reguły Rebasing :

Złotą zasadą git rebasejest, aby nigdy nie używać go w publicznych oddziałach.

Innymi słowy :

Nigdy nie bazuj na czymkolwiek, co gdzieś wypchnąłeś.

Osobiście dodałbym: chyba że jest to gałąź funkcji ORAZ ty i twój zespół jesteście świadomi konsekwencji .

Zatem pytanie git mergevs git rebasedotyczy prawie tylko gałęzi funkcji (w poniższych przykładach --no-ffzawsze było używane podczas scalania). Zauważ, że ponieważ nie jestem pewien, czy istnieje jedno lepsze rozwiązanie ( istnieje debata ), przedstawię tylko, jak zachowują się oba polecenia. W moim przypadku wolę używać, git rebaseponieważ daje to ładniejsze drzewo historii :)

Między gałęziami obiektów

git merge

Polecenia:

Time           Branch "develop"              Branch "features/foo"           Branch "features/bar"
------- -------------------------------- ------------------------------- --------------------------------
15:10   git merge --no-ff features/bar
15:09   git merge --no-ff features/foo
15:08                                                                    git commit -m "Sixth commit"
15:07                                                                    git merge --no-ff features/foo
15:06                                                                    git commit -m "Fifth commit"
15:05                                                                    git commit -m "Fourth commit"
15:04                                    git commit -m "Third commit"
15:03                                    git commit -m "Second commit"
15:02   git checkout -b features/bar
15:01   git checkout -b features/foo
15:00   git commit -m "First commit"

Wynik:

*   c0a3b89 - YYYY-MM-DD 15:10:00 (XX minutes ago) (HEAD -> develop)
|\            Merge branch 'features/bar' - Christophe
| * 37e933e - YYYY-MM-DD 15:08:00 (XX minutes ago) (features/bar)
| |           Sixth commit - Christophe
| *   eb5e657 - YYYY-MM-DD 15:07:00 (XX minutes ago)
| |\            Merge branch 'features/foo' into features/bar - Christophe
| * | 2e4086f - YYYY-MM-DD 15:06:00 (XX minutes ago)
| | |           Fifth commit - Christophe
| * | 31e3a60 - YYYY-MM-DD 15:05:00 (XX minutes ago)
| | |           Fourth commit - Christophe
* | |   98b439f - YYYY-MM-DD 15:09:00 (XX minutes ago)
|\ \ \            Merge branch 'features/foo' - Christophe
| |/ /
|/| /
| |/
| * 6579c9c - YYYY-MM-DD 15:04:00 (XX minutes ago) (features/foo)
| |           Third commit - Christophe
| * 3f41d96 - YYYY-MM-DD 15:03:00 (XX minutes ago)
|/            Second commit - Christophe
* 14edc68 - YYYY-MM-DD 15:00:00 (XX minutes ago)
            First commit - Christophe

git rebase

Polecenia:

Time           Branch "develop"              Branch "features/foo"           Branch "features/bar"
------- -------------------------------- ------------------------------- -------------------------------
15:10   git merge --no-ff features/bar
15:09   git merge --no-ff features/foo
15:08                                                                    git commit -m "Sixth commit"
15:07                                                                    git rebase features/foo
15:06                                                                    git commit -m "Fifth commit"
15:05                                                                    git commit -m "Fourth commit"
15:04                                    git commit -m "Third commit"
15:03                                    git commit -m "Second commit"
15:02   git checkout -b features/bar
15:01   git checkout -b features/foo
15:00   git commit -m "First commit"

Wynik:

*   7a99663 - YYYY-MM-DD 15:10:00 (XX minutes ago) (HEAD -> develop)
|\            Merge branch 'features/bar' - Christophe
| * 708347a - YYYY-MM-DD 15:08:00 (XX minutes ago) (features/bar)
| |           Sixth commit - Christophe
| * 949ae73 - YYYY-MM-DD 15:06:00 (XX minutes ago)
| |           Fifth commit - Christophe
| * 108b4c7 - YYYY-MM-DD 15:05:00 (XX minutes ago)
| |           Fourth commit - Christophe
* |   189de99 - YYYY-MM-DD 15:09:00 (XX minutes ago)
|\ \            Merge branch 'features/foo' - Christophe
| |/
| * 26835a0 - YYYY-MM-DD 15:04:00 (XX minutes ago) (features/foo)
| |           Third commit - Christophe
| * a61dd08 - YYYY-MM-DD 15:03:00 (XX minutes ago)
|/            Second commit - Christophe
* ae6f5fc - YYYY-MM-DD 15:00:00 (XX minutes ago)
            First commit - Christophe

Od developdo gałęzi funkcji

git merge

Polecenia:

Time           Branch "develop"              Branch "features/foo"           Branch "features/bar"
------- -------------------------------- ------------------------------- -------------------------------
15:10   git merge --no-ff features/bar
15:09                                                                    git commit -m "Sixth commit"
15:08                                                                    git merge --no-ff develop
15:07   git merge --no-ff features/foo
15:06                                                                    git commit -m "Fifth commit"
15:05                                                                    git commit -m "Fourth commit"
15:04                                    git commit -m "Third commit"
15:03                                    git commit -m "Second commit"
15:02   git checkout -b features/bar
15:01   git checkout -b features/foo
15:00   git commit -m "First commit"

Wynik:

*   9e6311a - YYYY-MM-DD 15:10:00 (XX minutes ago) (HEAD -> develop)
|\            Merge branch 'features/bar' - Christophe
| * 3ce9128 - YYYY-MM-DD 15:09:00 (XX minutes ago) (features/bar)
| |           Sixth commit - Christophe
| *   d0cd244 - YYYY-MM-DD 15:08:00 (XX minutes ago)
| |\            Merge branch 'develop' into features/bar - Christophe
| |/
|/|
* |   5bd5f70 - YYYY-MM-DD 15:07:00 (XX minutes ago)
|\ \            Merge branch 'features/foo' - Christophe
| * | 4ef3853 - YYYY-MM-DD 15:04:00 (XX minutes ago) (features/foo)
| | |           Third commit - Christophe
| * | 3227253 - YYYY-MM-DD 15:03:00 (XX minutes ago)
|/ /            Second commit - Christophe
| * b5543a2 - YYYY-MM-DD 15:06:00 (XX minutes ago)
| |           Fifth commit - Christophe
| * 5e84b79 - YYYY-MM-DD 15:05:00 (XX minutes ago)
|/            Fourth commit - Christophe
* 2da6d8d - YYYY-MM-DD 15:00:00 (XX minutes ago)
            First commit - Christophe

git rebase

Polecenia:

Time           Branch "develop"              Branch "features/foo"           Branch "features/bar"
------- -------------------------------- ------------------------------- -------------------------------
15:10   git merge --no-ff features/bar
15:09                                                                    git commit -m "Sixth commit"
15:08                                                                    git rebase develop
15:07   git merge --no-ff features/foo
15:06                                                                    git commit -m "Fifth commit"
15:05                                                                    git commit -m "Fourth commit"
15:04                                    git commit -m "Third commit"
15:03                                    git commit -m "Second commit"
15:02   git checkout -b features/bar
15:01   git checkout -b features/foo
15:00   git commit -m "First commit"

Wynik:

*   b0f6752 - YYYY-MM-DD 15:10:00 (XX minutes ago) (HEAD -> develop)
|\            Merge branch 'features/bar' - Christophe
| * 621ad5b - YYYY-MM-DD 15:09:00 (XX minutes ago) (features/bar)
| |           Sixth commit - Christophe
| * 9cb1a16 - YYYY-MM-DD 15:06:00 (XX minutes ago)
| |           Fifth commit - Christophe
| * b8ddd19 - YYYY-MM-DD 15:05:00 (XX minutes ago)
|/            Fourth commit - Christophe
*   856433e - YYYY-MM-DD 15:07:00 (XX minutes ago)
|\            Merge branch 'features/foo' - Christophe
| * 694ac81 - YYYY-MM-DD 15:04:00 (XX minutes ago) (features/foo)
| |           Third commit - Christophe
| * 5fd94d3 - YYYY-MM-DD 15:03:00 (XX minutes ago)
|/            Second commit - Christophe
* d01d589 - YYYY-MM-DD 15:00:00 (XX minutes ago)
            First commit - Christophe

Notatki dodatkowe

git cherry-pick

Gdy potrzebujesz tylko jednego konkretnego zatwierdzenia, git cherry-pickjest to miłe rozwiązanie ( -xopcja dołącza wiersz z tekstem „ (wybranie z zatwierdzenia ...) ” do oryginalnej treści komunikatu zatwierdzenia, więc zwykle dobrym pomysłem jest jego użycie - git log <commit_sha1>aby zobaczyć to):

Polecenia:

Time           Branch "develop"              Branch "features/foo"                Branch "features/bar"
------- -------------------------------- ------------------------------- -----------------------------------------
15:10   git merge --no-ff features/bar
15:09   git merge --no-ff features/foo
15:08                                                                    git commit -m "Sixth commit"
15:07                                                                    git cherry-pick -x <second_commit_sha1>
15:06                                                                    git commit -m "Fifth commit"
15:05                                                                    git commit -m "Fourth commit"
15:04                                    git commit -m "Third commit"
15:03                                    git commit -m "Second commit"
15:02   git checkout -b features/bar
15:01   git checkout -b features/foo
15:00   git commit -m "First commit"

Wynik:

*   50839cd - YYYY-MM-DD 15:10:00 (XX minutes ago) (HEAD -> develop)
|\            Merge branch 'features/bar' - Christophe
| * 0cda99f - YYYY-MM-DD 15:08:00 (XX minutes ago) (features/bar)
| |           Sixth commit - Christophe
| * f7d6c47 - YYYY-MM-DD 15:03:00 (XX minutes ago)
| |           Second commit - Christophe
| * dd7d05a - YYYY-MM-DD 15:06:00 (XX minutes ago)
| |           Fifth commit - Christophe
| * d0d759b - YYYY-MM-DD 15:05:00 (XX minutes ago)
| |           Fourth commit - Christophe
* |   1a397c5 - YYYY-MM-DD 15:09:00 (XX minutes ago)
|\ \            Merge branch 'features/foo' - Christophe
| |/
|/|
| * 0600a72 - YYYY-MM-DD 15:04:00 (XX minutes ago) (features/foo)
| |           Third commit - Christophe
| * f4c127a - YYYY-MM-DD 15:03:00 (XX minutes ago)
|/            Second commit - Christophe
* 0cf894c - YYYY-MM-DD 15:00:00 (XX minutes ago)
            First commit - Christophe

git pull --rebase

Nie jestem pewien, czy potrafię to wyjaśnić lepiej niż Derek Gourlay ... Zasadniczo używaj git pull --rebasezamiast git pull:) W artykule brakuje jednak tego, że możesz włączyć to domyślnie :

git config --global pull.rebase true

git rerere

Ponownie, ładnie wyjaśnione tutaj . Mówiąc prościej, jeśli włączysz tę opcję, nie będziesz musiał więcej rozwiązywać tego samego konfliktu wiele razy.

sp00m
źródło
15

Książka Pro Git ma naprawdę dobre wyjaśnienie na stronie rebasingu .

Zasadniczo scalenie zajmie dwa commity i połączy je.

Podstawa trafi do wspólnego przodka na dwóch i stopniowo zastosuje zmiany jeden na drugim. To zapewnia „czystszą” i bardziej liniową historię.

Ale kiedy dokonujesz zmiany bazy, porzucasz poprzednie zatwierdzenia i tworzysz nowe. Dlatego nigdy nie powinieneś bazować na repozytorium, które jest publiczne. Inne osoby pracujące w repozytorium będą cię nienawidzić.

Tylko z tego powodu prawie wyłącznie łączę się. 99% przypadków moje oddziały nie różnią się tak bardzo, więc jeśli występują konflikty, to tylko w jednym lub dwóch miejscach.

Xero
źródło
1
Fuzje nie łączą zatwierdzeń - to byłoby przepisywanie historii. Rebase to robi.
kellyfj
4

Git rebase służy do uczynienia ścieżek rozgałęzień w historii bardziej przejrzystymi i liniowymi w strukturze repozytorium.

Służy również do zachowania prywatności utworzonych przez Ciebie gałęzi, ponieważ po zmianie bazy danych i wysłaniu zmian na serwer, jeśli usuniesz swoją gałąź, nie będzie żadnych dowodów na gałąź, nad którą pracowałeś. Więc twój oddział jest teraz Twoim lokalnym zmartwieniem.

Po wykonaniu rebase pozbywamy się również dodatkowego zatwierdzenia, które kiedyś sprawdzaliśmy, czy wykonujemy normalne scalanie.

I tak, nadal trzeba dokonać scalenia po udanym rebase, ponieważ polecenie rebase po prostu umieszcza twoją pracę na gałęzi, o której wspomniałeś podczas rebase, powiedzmy master, i dokonuje pierwszego zatwierdzenia twojej gałęzi jako bezpośredniego potomka gałęzi master . Oznacza to, że możemy teraz wykonać szybkie scalanie do przodu, aby wprowadzić zmiany z tej gałęzi do gałęzi głównej.

cvibha
źródło
4

Jeśli jesteś tylko jednym programistą, możesz użyć rebase zamiast scalania, aby mieć czystą historię

wprowadź opis zdjęcia tutaj

yoAlex5
źródło
3

Kilka praktycznych przykładów, nieco związanych z rozwojem na dużą skalę, gdzie Gerrit jest używany do przeglądu i integracji dostaw:

Scalam, kiedy podnoszę gałąź gałęzi funkcji do nowego zdalnego sterownika. Daje to minimalną pracę nad ulepszeniem i łatwo jest śledzić historię rozwoju funkcji, na przykład w gitk .

git fetch
git checkout origin/my_feature
git merge origin/master
git commit
git push origin HEAD:refs/for/my_feature

Scalam, gdy przygotowuję zatwierdzenie dostawy.

git fetch
git checkout origin/master
git merge --squash origin/my_feature
git commit
git push origin HEAD:refs/for/master

Dokonuję zmiany bazy, gdy moje potwierdzenie dostarczenia kończy się niepowodzeniem z jakiegokolwiek powodu, i muszę zaktualizować ją w kierunku nowego zdalnego sterownika.

git fetch
git fetch <gerrit link>
git checkout FETCH_HEAD
git rebase origin/master
git push origin HEAD:refs/for/master
Martin G.
źródło
3

Wiele razy wyjaśniono, co to jest baza i co to jest scalenie, ale kiedy należy z niej korzystać?

Kiedy należy korzystać z rebase?

  • gdy nie pchnąłeś gałęzi / nikt inny nad nim nie pracuje
  • chcesz pełną historię
  • chcesz uniknąć wszystkich automatycznie generowanych komunikatów zatwierdzania „scalone ..”

Gdy Git rebase zmienia historię. Dlatego nie powinieneś go używać, gdy ktoś pracuje nad tym samym oddziałem / jeśli go wypchnąłeś. Ale jeśli masz oddział lokalny, możesz wykonać scalanie wzorca bazowego przed scaleniem swojego oddziału z powrotem w wzorcu, aby zachować czystszą historię. W ten sposób po scaleniu w gałęzi master nie będzie widać, że używałeś gałęzi w gałęzi master - historia jest „czystsza”, ponieważ nie masz automatycznie wygenerowanego „scalenia…”, ale nadal masz pełna historia w gałęzi master bez autogeneracji „scalonych…” zobowiązań.

Upewnij się jednak, że używasz, git merge feature-branch --ff-onlyaby upewnić się, że nie ma konfliktów tworzących pojedyncze zatwierdzenie podczas scalania funkcji z powrotem do głównej.Jest to interesujące, jeśli korzystasz z gałęzi funkcji dla każdego zadania, nad którym pracujesz, gdy przeglądasz historię gałęzi funkcji, ale nie zatwierdzasz „scalonego”.

Drugi scenariusz byłby taki, jeśli rozgałęzisz się z gałęzi i chcesz wiedzieć, co zmieniło się w głównej gałęzi. Rebase daje ci informacje, ponieważ obejmuje każde zatwierdzenie.

Kiedy należy użyć scalania?

  • kiedy pchnąłeś gałąź / inni też nad nią pracują
  • nie potrzebujesz pełnej historii
  • po prostu połączenie jest dla Ciebie wystarczające

Jeśli nie potrzebujesz lub nie chcesz mieć całej historii gałęzi funkcji w gałęzi głównej lub jeśli inni pracują nad tą samą gałęzią / przepuściłeś ją. Jeśli nadal chcesz mieć historię, po prostu połącz wzorzec z gałęzią funkcji przed scaleniem gałęzi funkcji do wzorca. Spowoduje to szybkie przewijanie do przodu, w którym masz historię gałęzi funkcji w swoim wzorcu (w tym zatwierdzenie scalania, które było w gałęzi cech, ponieważ scaliłeś z nią wzorzec).

Jeremy Benks
źródło
-4

Kiedy używam git rebase? Prawie nigdy, ponieważ przepisuje historię. git mergejest prawie zawsze najlepszym wyborem, ponieważ uwzględnia to, co faktycznie wydarzyło się w twoim projekcie.

Marnen Laibow-Koser
źródło
1
@benjaminhull Dzięki! - chyba że mam nadzieję, że moja odpowiedź jest oparta na faktach. Opinia IMHO ma niewielkie miejsce w tego rodzaju sprawach: faktem jest , że utrata faktycznej historii utrudnia później życie.
Marnen Laibow-Koser
1
Zgodzić się. Scalanie nigdy nie doprowadzi do zepsutej historii itp. (Kiedy zmienisz swoje wypchnięte zobowiązania)
surfrider