Kiedy zaleca się stosowanie Git rebase vs. Git Merge?
Czy nadal muszę się łączyć po udanym rebase?
Kiedy zaleca się stosowanie Git rebase vs. Git Merge?
Czy nadal muszę się łączyć po udanym rebase?
Odpowiedzi:
Kiedy więc używasz jednego z nich?
init
nowym repozytorium, add
plikiem 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 master
i 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.
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 master
oddziale. Możesz po prostu przełączyć master
i połączyć cool-feature
gałąź:
$ 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.
but this way a new dummy commit is added, if you want to avoid spaghetti-history
- jak jest źle?
Sean Schofield
umieszcza 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.
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.
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 Y
pracę gałęzi, B
z 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ł B
moż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łąź B
moż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ę B
na 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 B
przez 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
otrzymujemy poprzez zmianę:
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-feature
na, master
jeśli chcesz zachować commity wykonane w twoimnew-feature
Oddział).
Więc:
master
.W razie wątpliwości skorzystaj z funkcji scalania.
Jedyne różnice między rebase a scaleniem to:
Krótką odpowiedzią jest więc wybranie bazy lub scalenia na podstawie tego, jak ma wyglądać Twoja historia .
Istnieje kilka czynników, które należy wziąć pod uwagę przy wyborze operacji.
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.
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.
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.
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.
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 --rebase
wszystkich operacji ściągania).
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.
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.
Jeśli lubisz pseudonim, rm
aby rm -rf
„zaoszczędzić czas”, być może rebase jest dla Ciebie.
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.
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.
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:
Natomiast scalanie gałęzi tematów w master:
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.
git merge
obsługuje --no-ff
opcję, która zmusza ją do zatwierdzenia scalania.
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ń:
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.
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ę.
Jednak w wielu przypadkach oba oddziały posuwały się naprzód indywidualnie.
Aby dokonać integracji, Git będzie musiał utworzyć nowe zatwierdzenie, które zawiera różnice między nimi - zatwierdzenie scalania.
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.
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.
Zrobimy to w trzech krokach
git rebase branch-A // Synchronises the history with branch-A
git checkout branch-A // Change the current branch to branch-A
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”.
Następnie stosuje zatwierdzenia z gałęzi B, które chcemy zintegrować. W tym momencie obie gałęzie wyglądają dokładnie tak samo.
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ń.
Wreszcie otrzymujesz czystą gałąź gałęzi A bez niechcianych i automatycznie generowanych zatwierdzeń.
Uwaga: pochodzi z niesamowitego posta autorstwa git-tower
. Te wady o rebase
to również dobry czytać w tym samym stanowisku.
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 .
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ś .
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 merge
a git merge --no-ff
pierwszą (zwykle chcesz użyć, git merge --no-ff
ponieważ 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 rebase
jest, aby nigdy nie używać go w publicznych oddziałach.
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 merge
vs git rebase
dotyczy prawie tylko gałęzi funkcji (w poniższych przykładach --no-ff
zawsze 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 rebase
ponieważ daje to ładniejsze drzewo historii :)
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
develop
do gałęzi funkcjigit 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
git cherry-pick
Gdy potrzebujesz tylko jednego konkretnego zatwierdzenia, git cherry-pick
jest to miłe rozwiązanie ( -x
opcja 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 --rebase
zamiast 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.
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.
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.
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
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 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-only
aby 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?
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).
Kiedy używam git rebase
? Prawie nigdy, ponieważ przepisuje historię. git merge
jest prawie zawsze najlepszym wyborem, ponieważ uwzględnia to, co faktycznie wydarzyło się w twoim projekcie.