git rebase
Ten dokument zawiera szczegółowe omówienie polecenia git rebase
. O poleceniu „rebase” wspomniano już na stronach dotyczących konfiguracji repozytorium i przepisywania historii. Ta strona zawiera bardziej dogłębny opis konfiguracji i wykonywania polecenia git rebase
. Omówione zostaną również typowe przypadki jego użycia i związane z nimi pułapki.
Polecenie „rebase” jest jednym z dwóch narzędzi Git przeznaczonych specjalnie do integracji zmian z jednej gałęzi w drugiej. Drugim narzędziem integracji zmian jest polecenie git merge
. Scalanie zawsze oznacza posunięcie rejestru zmian naprzód. Polecenie „rebase” zapewnia natomiast zaawansowane funkcje przepisywania historii. Szczegółowe porównanie poleceń „merge” i „rebase” można znaleźć w naszym przewodniku dotyczącym porównania operacji scalania i zmiany bazy. Polecenie „rebase” obejmuje 2 podstawowe tryby: ręczny i interaktywny. Poszczególne tryby polecenia „rebase” zostaną omówione szczegółowo w dalszej części.
Jak działa polecenie „git rebase”?
Zmiana bazy (rebasing) jest procesem przenoszenia lub łączenia sekwencji commitów do postaci nowego commita bazowego. Najlepiej się sprawdza i najłatwiej go zwizualizować w kontekście przepływu pracy związanego z tworzeniem gałęzi funkcji. Ogólny proces można przedstawić w następujący sposób:
Z punktu widzenia zawartości operacja zmiany bazy polega na zmianie bazy gałęzi z jednego commita na drugi tak, aby gałąź sprawiała wrażenie utworzonej z poziomu innego commita. Na poziomie wewnętrznym Git uzyskuje taki rezultat poprzez utworzenie nowych commitów oraz zastosowanie ich do wskazanej bazy. Trzeba jednak pamiętać, że choć gałąź wygląda tak samo, składa się ona z zupełnie nowych commitów.
Korzystanie
Z operacji zmiany bazy korzysta się zazwyczaj po to, aby zachować liniową historię projektu. Przykładowo rozważmy sytuację, w której od momentu rozpoczęcia pracy nad gałęzią funkcji dokonano postępów w obrębie gałęzi głównej. Chcesz zatem uwzględnić najnowsze aktualizacje gałęzi głównej w swojej gałęzi funkcji, ale jednocześnie zachować przejrzystość historii gałęzi tak, aby wyglądało na to, że prace były prowadzone na najnowszej gałęzi głównej. Dzięki temu później można bez problemu scalić gałąź funkcji z powrotem z gałęzią główną. Dlaczego zależy nam na zachowaniu „przejrzystej historii”? Korzyści płynące z posiadania przejrzystej historii stają się namacalne podczas wykonywania operacji Git w celu zbadania wprowadzenia regresji. Odwołajmy się więc do bardziej praktycznego scenariusza:
- W obrębie gałęzi głównej wykryto błąd. Funkcja, która do tej pory działała, teraz jest uszkodzona.
- Programista sprawdza historię głównej gałęzi za pomocą polecenia
git log
, a dzięki „przejrzystej historii” jest w stanie szybko zrozumieć historię projektu. - Za pomocą samego polecenia
git log
programista nie jest w stanie wskazać momentu wprowadzenia błędu, dlatego wykonuje poleceniegit bisect
. - Historia Git jest przejrzysta, dlatego polecenie
git bisect
pozwala zawęzić zbiór commitów do porównania przy szukaniu regresji. Programista szybko wyszukuje commit, w którym wprowadzono błąd, i może podjąć odpowiednie działania.
Więcej informacji na temat poleceń git log oraz git bisect można znaleźć na stronach dotyczących użycia każdego z nich.
Są dostępne dwie opcje integracji funkcji z gałęzią główną: bezpośrednie scalenie lub scalenie poprzedzone zmianą bazy. Pierwsza opcja powoduje scalanie trójstronne i utworzenie commita scalenia, a druga umożliwia scalenie z przewijaniem i uzyskanie idealnie liniowej historii. Poniższy diagram pokazuje, w jaki sposób zmiana bazy zastosowana do gałęzi głównej ułatwia scalenie z przewijaniem.
Zmiana bazy jest typowym sposobem integracji zmian z gałęzi nadrzędnej z lokalnym repozytorium. Ściągnięcie zmian nadrzędnych za pomocą polecenia „git merge” powoduje powstanie zbędnego commita scalenia za każdym razem, gdy chcesz sprawdzić postępy projektu. Natomiast zmiana bazy przypomina bardziej podejście w stylu „Chcę oprzeć moje zmiany na tym, co zrobili dotychczas wszyscy inni”.
Nie zmieniaj bazy historii publicznej
Jak już wspominaliśmy w sekcji dotyczącej przepisywania historii, nie należy stosować polecenia „rebase” do commitów wypchniętych do repozytorium publicznego. Spowodowałoby to zastąpienie starych commitów nowymi, co wyglądałoby, jakby część historii Twojego projektu nagle zniknęła.
Polecenie „git rebase” w trybie standardowym i interaktywnym
Polecenie „git rebase” jest interaktywne, gdy zostanie zastosowany argument -- i
. Litera „i” oznacza tutaj słowo „interaktywny”. Jeśli nie użyje się żadnych argumentów, polecenie zostanie wykonane w trybie standardowym. W obydwu przypadkach załóżmy, że została utworzona odrębną gałąź funkcji.
# Create a feature branch based off of main
git checkout -b feature_branch main
# Edit files
git commit -a -m "Adds new feature"
Polecenie „git rebase” w trybie standardowym spowoduje automatyczne zastosowanie commitów z bieżącej gałęzi roboczej do końcówki przekazywanej gałęzi.
git rebase <base>
This automatically rebases the current branch onto <base>
, which can be any kind of commit reference (for example an ID, a branch name, a tag, or a relative reference to HEAD
).
Wykonanie polecenia git rebase
z flagą -i
rozpoczyna interaktywną sesję zmiany bazy. Zamiast przenoszenia wszystkich commitów na ślepo do nowej bazy, interaktywna zmiana bazy umożliwia zmodyfikowanie poszczególnych commitów w trakcie procesu. Pozwala to oczyścić historię poprzez usunięcie, rozdzielenie i zmodyfikowanie istniejących serii commitów. To niczym polecenie git commit --amend
na sterydach.
git rebase --interactive <base>
This rebases the current branch onto <base>
but uses an interactive rebasing session. This opens an editor where you can enter commands (described below) for each commit to be rebased. These commands determine how individual commits will be transferred to the new base. You can also reorder the commit listing to change the order of the commits themselves. Once you've specified commands for each commit in the rebase, Git will begin playing back commits applying the rebase commands. The rebasing edit commands are as follows:
pick 2231360 some old commit
pick ee2adc2 Adds new feature
# Rebase 2cf755d..ee2adc2 onto 2cf755d (9 commands)
#
# Commands:
# p, pick = use commit
# r, reword = use commit, but edit the commit message
# e, edit = use commit, but stop for amending
# s, squash = use commit, but meld into previous commit
# f, fixup = like "squash", but discard this commit's log message
# x, exec = run command (the rest of the line) using shell
# d, drop = remove commit
Dodatkowe polecenia „rebase”
Jak już opisano szczegółowo na stronie przepisywania historii, za pomocą polecenia zmiany bazy można zmieniać starsze commity lub wiele commitów jednocześnie, zatwierdzone pliki oraz wiele komunikatów. Choć są to właśnie najbardziej typowe zastosowania, polecenie git rebase
ma również opcje dodatkowe, które mogą się okazać przydatne w bardziej złożonych przypadkach.
- Polecenie
git rebase -- d
oznacza, że w trakcie wykonywania commit zostanie pominięty w końcowym bloku połączonych commitów. - Polecenie
git rebase -- p
umożliwia pozostawienie commita bez zmian. Zawartość ani komunikat takiego commita nie zostaną zmodyfikowane, a commit nadal będzie widoczny w historii gałęzi jako pojedynczy commit. - Polecenie
git rebase -- x
w trakcie wykonywania stosuje skrypt powłoki wiersza polecenia do każdego oznaczonego commita. Przydatnym przykładem jego zastosowania może być uruchomienie pakietu testowego bazy kodu na konkretnych commitach, co ułatwia rozpoznanie regresji w trakcie zmiany bazy.
Podsumowanie
Interaktywna zmiana bazy zapewnia pełną kontrolę nad tym, jak wygląda historia projektu. Dzięki temu programiści zyskują dużą swobodę, ponieważ mogą zatwierdzać historię bez obaw o porządek i skoncentrować się na pisaniu kodu, a następnie cofnąć się i wyczyścić historię po fakcie.
Większość programistów chętnie wykorzystuje interaktywną zmianę bazy do dopracowania gałęzi funkcji przed scaleniem jej z główną bazą kodu. Dzięki temu mogą połączyć za pomocą polecenia „squash” nieistotne commity, usunąć przestarzałe i upewnić się, że gałąź jest w porządku pod każdym innym względem, zanim włączą ją do „oficjalnej” historii projektu. Dla wszystkich innych użytkowników będzie wyglądało, jakby cała funkcja została opracowana w pojedynczej serii dobrze zaplanowanych commitów.
Prawdziwą moc interaktywnej zmiany bazy widać w historii wynikowej gałęzi głównej. Dla każdej innej osoby będziesz sprawiać wrażenie wybitnego programisty, który zaimplementował nową funkcję z idealną liczbą commitów już za pierwszym razem. W ten właśnie sposób interaktywna zmiana bazy pozwala prowadzić przejrzystą i sensowną historię projektu.
Opcje konfiguracji
Za pomocą polecenia git config
można ustawić kilka właściwości zmiany bazy. Te opcje pozwalają modyfikować wygląd wyniku i działanie polecenia git rebase
.
rebase.stat
: wartość logiczna domyślnie ustawiona na fałsz. Ta opcja włącza wyświetlanie wizualnej zawartości diffstat, która pokazuje zmiany wprowadzone od ostatniego wykonania operacji zmiany bazy.
rebase.autoSquash:
wartość logiczna, która pozwala przełączać sposób działania opcji--autosquash
.
rebase.missingCommitsCheck:
dla tej opcji można ustawić wiele wartości, które pozwalają zmienić sposób działania polecenia „rebase” w odniesieniu do brakujących commitów.
warn | Wyświetla ostrzeżenie o usuniętych commitach w trybie interaktywnym. |
| Zatrzymuje operację zmiany bazy i wyświetla komunikaty ostrzegawcze o usuniętych commitach. |
| Ustawienie domyślne. Ignoruje wszelkie ostrzeżenia o brakujących commitach. |
rebase.instructionFormat:
ciąg formatu poleceniagit log
, który zostanie użyty do sformatowania widoku operacji zmiany bazy w trybie interaktywnym.
Zaawansowane zastosowanie operacji zmiany bazy
Do polecenia git rebase
można przekazać argument wiersza polecenia --onto
. W trybie --onto
polecenie „git rebase” zostaje rozwinięte w następujący sposób:
git rebase --onto <newbase> <oldbase>
Opcja --onto
umożliwia skorzystanie z bardziej zaawansowanej formy polecenia „rebase” pozwalającej na przekazywanie określonych referencji jako końcówek, które będą stanowiły podstawę operacji zmiany bazy.
Załóżmy na przykład, że mamy repozytorium zawierające następujące gałęzie:
o---o---o---o---o main
\
o---o---o---o---o featureA
\
o---o---o featureB
Gałąź featureB bazuje na gałęzi featureA. Uzmysławiamy sobie jednak, że gałąź featureB nie jest zależna od żadnej ze zmian w gałęzi featureA i można ją wyprowadzić z gałęzi głównej.
git rebase --onto main featureA featureB
featureA is the <oldbase>
. main
becomes the <newbase>
and featureB is reference for what HEAD
of the <newbase>
will point to. The results are then:
o---o---o featureB
/
o---o---o---o---o main
\
o---o---o---o---o featureA
Niebezpieczeństwa związane z operacją zmiany bazy
Jednym z problemów, które należy wziąć pod uwagę podczas pracy z poleceniem „git rebase”, jest to, że w związanym z nim przepływie pracy może zwiększyć się liczba konfliktów scalania. Dochodzi do tego w przypadku istnienia długotrwałej gałęzi odchodzącej od gałęzi głównej. Gdy w końcu zechcesz połączyć ją z gałęzią główną za pomocą operacji zmiany bazy, może ona zawierać wiele nowych commitów, z którymi zmiany wprowadzone w Twojej gałęzi mogą być sprzeczne. Można temu łatwo zaradzić poprzez częste łączenie gałęzi z gałęzią główną za pomocą polecenia „rebase” oraz częstsze wykonywanie commitów. W razie wystąpienia konfliktów można również kontynuować lub zresetować operację zmiany bazy, przekazując do polecenia git rebase
argumenty wiersza polecenia --continue
lub --abort
.
Istotniejszym problemem związanym z operacją zmiany bazy jest utrata commitów w wyniku interaktywnego przepisania historii. Uruchomienie operacji zmiany bazy w trybie interaktywnym i wykonanie podpoleceń, takich jak „squash” lub „drop”, spowoduje usunięcie commitów z bezpośredniego dziennika Twojej gałęzi. Na pierwszy rzut oka może się wydawać, że commity zniknęły na zawsze. Można je jednak przywrócić oraz cofnąć całą operację zmiany bazy, korzystając z polecenia git reflog
. Więcej informacji na temat wyszukiwania zagubionych commitów za pomocą polecenia git reflog
można znaleźć na stronie dokumentacji dotyczącej polecenia „git reflog”.
Samo polecenie „git rebase” nie stwarza poważnego zagrożenia. Faktyczne niebezpieczeństwo pojawia się podczas wykonywania operacji zmiany bazy w trybie interaktywnym w celu przepisania historii oraz wymuszonego wypchania wyników do gałęzi zdalnej współdzielonej z innymi użytkownikami. Należy unikać takiego postępowania ze względu na możliwość nadpisania pracy innych użytkowników zdalnych, gdy wykonają operację ściągania.
Odzyskiwanie po wykonaniu operacji zmiany bazy w gałęzi nadrzędnej
Jeśli inny użytkownik wykonał operację zmiany bazy i wymusił wypchnięcie do gałęzi, do której dodajesz commity, użycie polecenia git pull
spowoduje zastąpienie wszelkich commitów wychodzących od tej poprzedniej gałęzi wymuszoną końcówką. Na szczęście polecenie git reflog
pozwala pobrać dziennik reflog gałęzi zdalnej. W dzienniku reflog gałęzi zdalnej możesz znaleźć referencję sprzed wykonania operacji zmiany bazy. Następnie możesz wykonać operację zmiany bazy gałęzi względem zdalnej referencji, używając opcji --onto
w sposób opisany powyżej w sekcji Zaawansowane zastosowanie operacji zmiany bazy.
Podsumowanie
W tym artykule opisano użycie polecenia git rebase
. Omówiliśmy podstawowe i zaawansowane przypadki użycia oraz bardziej zaawansowane przykłady. Najważniejsze poruszone zagadnienia:
- Polecenie „git rebase” w trybie standardowym i interaktywnym
- Opcje konfiguracji polecenia „git rebase”
- Opcja „--onto” w poleceniu „git rebase”
- Utrata commitów po użyciu polecenia „git rebase”
Przyjrzeliśmy się również użyciu polecenia git rebase
w połączeniu z innymi narzędziami, takimi jak git reflog
, git fetch
i git push
. Więcej informacji na ich temat można znaleźć na odpowiednich poświęconych im stronach.