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:

Samouczek Git: git rebase

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:

  1. W obrębie gałęzi głównej wykryto błąd. Funkcja, która do tej pory działała, teraz jest uszkodzona.
  2. Programista sprawdza historię głównej gałęzi za pomocą polecenia git log, a dzięki „przejrzystej historii” jest w stanie szybko zrozumieć historię projektu.
  3. Za pomocą samego polecenia git log programista nie jest w stanie wskazać momentu wprowadzenia błędu, dlatego wykonuje polecenie git bisect.
  4. 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.

git rebase: gałąź na gałąź master

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>

To polecenie spowoduje automatyczną zmianę bazy gałęzi bieżącej na bazę , która może być dowolnego rodzaju referencją commita (np. identyfikatorem, nazwą gałęzi, tagiem lub względną referencją do wskaźnika 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>

To polecenie spowoduje zmianę bazy gałęzi bieżącej na bazę , ale w sesji interaktywnej zmiany bazy. W rezultacie nastąpi otwarcie edytora, w którym można wprowadzić (opisane poniżej) polecenia dla każdego commita do uwzględnienia w operacji zmiany bazy. Te polecenia określają sposób przekazywania poszczególnych commitów do nowej bazy. Można również zmienić kolejność na liście commitów, aby zmienić kolejność samych commitów. Po zdefiniowaniu poleceń dla każdego commita w ramach operacji zmiany bazy Git rozpocznie przetwarzanie commitów z zastosowaniem poleceń „rebase”. Polecenia edycji operacji zmiany bazy są następujące:




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.

błąd

Zatrzymuje operację zmiany bazy i wyświetla komunikaty ostrzegawcze o usuniętych commitach.

Ignoruj

Ustawienie domyślne. Ignoruje wszelkie ostrzeżenia o brakujących commitach.
  • rebase.instructionFormat: ciąg formatu polecenia git 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

Gałąź featureA staje się starą bazą — < oldbase >. Gałąź main staje się nową bazą — < newbase >, a featureB stanowi referencję, na którą będzie wskazywać wskaźnik HEAD gałęzi < newbase >. Rezultat będzie następujący:

 
                      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.

Chcesz nauczyć się obsługi systemu Git?

Wypróbuj ten interaktywny samouczek.

Zacznij już teraz