Użyj Git Rebase, aby wyczyścić historię zatwierdzania

  • Git rebase umożliwia przepisanie i uporządkowanie historii w celu utworzenia czystej i liniowej sekwencji zatwierdzeń.
  • Interaktywne rebase ułatwia usuwanie, scalanie lub zmianę kolejności zatwierdzeń, eliminując puste testy i zatwierdzenia.
  • Przepisywanie historii wymaga koordynacji działań zespołu i ostrożnego stosowania wymuszonego pushu, aby nie utracić pracy wykonanej przez innych ludzi.
  • Narzędzia takie jak git reflog i git pull --rebase pomagają pracować z rebase bezpiecznie i odwracalnie.

git rebase

Jeśli korzystasz z Gita od jakiegoś czasu, prawdopodobnie natknąłeś się na historię pełną bezużytecznych commitów, komunikatów „WIP”, szybkich testów, a nawet pustych commitów utworzonych tylko po to, by uruchomić hook lub potok. W tym momencie patrzysz na log i myślisz: „Nikt tego nie rozumie”Dobra wiadomość jest taka, że ​​nie jesteś sam: zdarza się to każdemu z nas i właśnie po to jest wsparcie. git rebase.

Ciekawostką jest to, że przy właściwym użyciu, git rebase pozwala na „wyczyszczenie” historii zatwierdzeńUczyń ją liniową, pozbawioną szumów i znacznie łatwiejszą do przeglądania. Możesz usuwać commity testowe, scalać kilka w jeden, zmieniać ich kolejność, poprawiać komunikaty commitów, a nawet usuwać zmiany zdalnie za pomocą wymuszonego pushu. Przyjrzyjmy się, na konkretnych przykładach, jak użyć rebase, aby przekształcić historię z totalnego chaosu w coś uporządkowanego i czytelnego.

Czym właściwie jest Git Rebase i dlaczego ma wpływ na historię?

Kiedy mówimy o wyprzedzaniu, mamy na myśli dosłownie zmień punkt początkowy gałęziGit pobiera zatwierdzenia z Twojej gałęzi i „reprodukuje” je jedno po drugim na innej bazie (zwykle gałęzi główny lub zaktualizowanej zdalnej gałęzi). To tak, jakbyś cofnął się w czasie i zaczął pracę od innego zatwierdzenia, ale zachował zmiany.

Wyobraź sobie, że Twój projekt ma następującą historię w gałęzi głównej: A – B – CTworzysz gałąź funkcjonalności, a następnie dokonujesz dwóch zatwierdzeń: D - E.Tymczasem w głównej części ktoś dodaje nowe zatwierdzenie FBez zmiany bazy Twoje gałęzie wyglądałyby mniej więcej tak:

A---B---C---F (main)
A---B---C---D---E (feature)

Jeśli wykonasz klasyczne scalenie gałęzi funkcji z gałęzią główną, otrzymasz dodatkowe zatwierdzenie scalenia, które często generuje zawiła historia Z kilkoma skrzyżowanymi liniami. Z kolei w przypadku rebase Git bierze commity D i E i ponownie je stosuje do F:

A---B---C---F---D'---E' (feature reescrita)

Tych D' i E' To nie są dokładnie te same zatwierdzenia co D i E: to „kopie” z nowymi identyfikatorami (haszami). Dlatego mówimy o rebase. przepisuje historięW rezultacie otrzymujemy bardziej liniowy dziennik, bez pośrednich zatwierdzeń scalania, które jedynie dodają szum.

git rebase

Dlaczego warto mieć czystą historię zatwierdzania

Uporządkowanie zapisów nie jest tylko kwestią estetyki. Czysty dziennik sprawia, że ​​codzienna praca staje się dużo łatwiejsza.Możesz na pierwszy rzut oka zobaczyć, co zostało zrobione, kiedy i dlaczego, bez konieczności przekopywania się przez puste zatwierdzenia scalania lub wyrwane z kontekstu komunikaty o „szybkich poprawkach”.

Gdy gałęzie są scalane poprzez ciągłe scalanie z gałęzi głównej, powstaje wiele zatwierdzeń, takich jak „Scal gałąź „główną” z funkcją-x”, Nie dostarczają prawdziwych informacji o zmianach w kodzieKomplikuje to takie zadania, jak:

  • Znajdź commit, w którym wprowadzono błąd, używając narzędzia do porównywania plikówponieważ historia jest pełna nieistotnych połączeń.
  • Przejrzyj żądania ściągnięciaponieważ trzeba przeskakiwać między zbędnymi zatwierdzeniami, aby zobaczyć, co faktycznie się zmieniło.
  • Zrozumienie ewolucji funkcjiszczególnie jeśli zostało opracowane na przestrzeni wielu małych testów.

Git rebase to idealne narzędzie do „wygładzenia” całego tego szumu przed udostępnieniem gałęzi reszcie zespołu. To tak, jakbyś prał swoją historię w pralce.Ważne zmiany przechowujesz w formie uporządkowanej i z jasnymi komunikatami.

Kluczowe różnice między poleceniami git merge i git rebase

Aby w pełni zrozumieć, co robisz, używając rebase, warto porównać jego zachowanie z zachowaniem scalanie gitOba służą integracji zmian, ale na różne sposoby i z ważnymi konsekwencjami dla całego zapisu.

z łączyćGit tworzy nowe zatwierdzenie scalenia, które ma dwóch nadrzędnych: wierzchołek Twojej gałęzi i wierzchołek gałęzi, z którą scalasz. Wynikowa historia zachowuje dokładnie oryginalną sekwencję zatwierdzeńAle może skończyć się to wieloma równoległymi gałęziami i połączeniami.

z przebazowaćZamiast tworzyć zatwierdzenie scalające, Git pobiera zatwierdzenia i stosuje je jedno po drugim do nowej bazy danych. Generuje to nowe zatwierdzenia z nowymi haszaminawet jeśli zmiany kodu są takie same.

W praktyce:

  • Łączyć Historia jest przedstawiana dokładnie tak, jak się wydarzyła, ze wszystkimi powiązaniami i połączeniami.
  • Zmień bazę Generuje liniową i czystą historię, jakby wszystko działo się w linii prostej.

Wybór nie polega na tym, że „jeden jest lepszy od drugiego”, ale która sytuacja jest bardziej odpowiednia?Aby połączyć już współdzielone i zamknięte gałęzie, scalanie jest zazwyczaj bezpieczniejsze. Aby zachować porządek w lokalnych gałęziach roboczych lub przed otwarciem żądania ściągnięcia, należy użyć polecenia rebase.

scalanie vs. rebase

Przypadki, w których rebase się sprawdza: aktualizacja i polerowanie gałęzi

Istnieją dwa scenariusze, w których większość programistów używa rebase codziennie: utrzymuj swoją gałąź roboczą na bieżąco z główną y wyczyść zatwierdzenia przed ich udostępnieniemPrzyjrzyjmy się im bliżej.

Zaktualizuj gałąź funkcji o najnowsze zmiany z gałęzi głównej

Pracujesz nad nową funkcją w swojej gałęzi, ale w tym czasie Twoi współpracownicy wciąż zatwierdzają zmiany głównyJeśli chcesz zintegrować zmiany, jedną z opcji jest:

git checkout tu-rama-feature
git merge main

Działa to, ale przy każdej synchronizacji generowane jest zatwierdzenie scalenia, co z czasem może powodować problemy. historia pełna powtarzających się połączeńJeśli jednak to zrobisz:

git checkout tu-rama-feature
git rebase main

Git przeniesie Twoje commity na ostatni stan gałęzi main, tak jakbyś uruchomił gałąź po tych zmianach. Otrzymasz liniowa historia, bez pośrednich zatwierdzeń scalaniaa recenzja będzie jaśniejsza.

Wyczyść swoje zatwierdzenia przed otwarciem żądania ściągnięcia

Bardzo często podczas opracowywania funkcji pojawiają się zatwierdzenia w rodzaju „poprawka literówki”, „testowanie potoku”, „więcej zmian w logowaniu” itd. To jest w porządku, gdy pracujesz, ale To nie jest ten rodzaj historii, który chcesz pokazać. kiedy otwierasz PR.

Interaktywne rebase pozwala zreorganizować i pogrupować te zatwierdzenia w coś bardziej logicznego. Na przykład zamiast tego:

- WIP: añadir login
- Más cambios login
- Corregir tests login
- Arreglar typo variable

Możesz skończyć z pojedynczym, dobrze opisanym zatwierdzeniem:

- Añadir funcionalidad completa de login de usuario con tests

Dzięki takiemu „wypolerowaniu” historii recenzenci mogą ją łatwiej zrozumieć. co wnosi każde zatwierdzenie i ułatwia rozwiązywanie problemów w przyszłości.

Interaktywne przebazowanie w celu przepisania historii

Podstawowa wersja rebase po prostu przenosi commity do innej bazy. Ale klejnotem w koronie jest interaktywna nadbaza, który otwiera edytor z listą ostatnich zatwierdzeń, dzięki czemu możesz zdecydować, co zrobić z każdym z nich.

Typowym sposobem na rozpoczęcie tego jest określenie, ile commitów wstecz chcesz „dotknąć” z bieżącego HEAD-a. Na przykład:

git rebase -i HEAD~6

To polecenie mówi Gitowi: „weź ostatnie 6 commitów z tej gałęzi i przygotuj dla mnie interaktywną bazę zmian”. Git otworzy domyślny edytor (Vim, Nano, VS Code itp.) z następującą zawartością:

pick 7ed9c6e update version
pick ecb7ef3 empty commit 1
pick a323615 empty commit 2
pick 2c3d41d empty commit 3
pick d53c00f empty commit 4
pick 22dcc79 empty commit 5

# Przebazuj 549dd76..22dcc79 na 549dd76 (6 poleceń)
#
# Polecenia:
# p, wybierz = użyj commita
# r, przeformułowanie = Użyj commit, ale edytuj wiadomość
# e, edytuj = Użyj commit i stop, aby zmodyfikować
# s, squash = scal z poprzednim zatwierdzeniem
# f, naprawa = jak squash, ale odrzucający wiadomość
# x, exec = wykonaj polecenie powłoki
# b, break = zatrzymaj rebase tutaj
# d, upuść = usuń zatwierdzenie
#…

Zwróć uwagę na jeden ważny szczegół: Kolejność w tym pliku jest odwrotna do tej, którą widzisz w dzienniku gitW pliku pierwsze zatwierdzenie (7ed9c6e) jest najstarsze z sześciu, a ostatnie (22dcc79) jest najnowsze. Jest to kluczowe, aby uniknąć pomyłek podczas usuwania lub zmiany kolejności zatwierdzeń.

Usuń testy lub puste zatwierdzenia z lokalnej historii

Załóżmy, że w celu przetestowania haka Git dokonywałeś pustych zatwierdzeń z opcją –zezwól-na-puste, Por ejemplo:

git commit -m "commit with no changes" --allow-empty

Ta opcja umożliwia utworzenie zatwierdzenia nawet wtedy, gdy w plikach nie dokonano żadnych zmian, co jest przydatne podczas testowania, ale Zaśmieca historię zatwierdzeniami, które nie mają żadnej realnej wartości.Wyobraź sobie, że Twój dziennik wygląda tak:

git log --pretty=oneline --abbrev-commit

I otrzymujesz:

22dcc79 (HEAD -> main, origin/main, origin/HEAD) empty commit 5
d53c00f empty commit 4
2c3d41d empty commit 3
a323615 empty commit 2
ecb7ef3 empty commit 1
7ed9c6e update version

W tym scenariuszu chcesz zachować tylko przydatną zatwierdzoną „wersję aktualizacji” i usunąć wszystkie pozostałe. puste zatwierdzenie To były tylko testy. Aby to zrobić, należy uruchomić interaktywną rebase dla ostatnich 6 zatwierdzeń:

git rebase -i HEAD~6

Edytor otwiera się z 6 zatwierdzeniami. Twoim celem jest usunięcie wierszy odpowiadających pustym zatwierdzeniom. Oznacza to, że zostawiasz tylko coś takiego:

pick 7ed9c6e update version

# Przebazuj 549dd76..22dcc79 na 549dd76 (6 poleceń)
# … reszta komentarzy …

Jak wskazano w sekcji pomocy samego pliku, Jeśli usuniesz wiersz, zatwierdzenie zniknie z historii. W nowej, przepisanej wersji, podczas zapisywania i zamykania edytora, Git odtworzy tylko zatwierdzenie „aktualizowanej wersji” i odrzuci pozostałe.

Wgrywanie nowej historii do Origin: użycie wymuszonego pushu

Do tej pory wszystkie zmiany rebase zostały wprowadzone w lokalnej kopii repozytorium. Jeśli chcesz, aby to czyszczenie zostało uwzględnione również w zdalnym repozytorium (np. pochodzenie/główne), będziesz musiał nadpisać zdalną historię nową.

Robi się to za pomocą wymuszone pchnięciePowszechnym sposobem wskazania tego jest użycie znaku „+” przed nazwą gałęzi podczas wypychania:

git push origin +main

Ten znak plus informuje Git, że Zignoruj ​​rozbieżność historii i nadpisz gałąź zdalną z Twoją przepisaną wersją. Jeśli po prostu próbowałeś zrobić:

git push origin main

Git ostrzeże Cię, że Twoja lokalna historia nie jest bezpośrednim potomkiem historii zdalnej (ponieważ zatwierdzenia zostały nadpisane poleceniem rebase) i odrzuci przesłanie zmian, aby uniknąć utraty danych.

Ważne jest, aby zrozumieć, że po tym wymuszonym pchnięciu, Usunięte zatwierdzenia nie będą już dostępne z poziomu origin/mainMogą one nadal znajdować się w reflogach lub lokalnych kopiach od innych współpracowników, ale ze względów praktycznych wyczyściłeś historię zdalną.

Poważne ostrzeżenie: przepisywanie współdzielonych gałęzi nie jest zabawą

Przepisywanie historii brzmi świetnie, jeśli chodzi o uporządkowanie wszystkiego, ale ma kluczowe konsekwencje: podczas tworzenia nowych zatwierdzeń z nowymi haszami, Zniechęcasz każdego, kto już wcześniej opierał swoją pracę na starych zatwierdzeniach.Dlatego właśnie istnieje słynna złota zasada:

Nie zmieniaj baz gałęzi, które są już współdzielone i aktywnie używane przez innych.

W praktyce oznacza to, że można bezpiecznie używać rebase w przypadku:

  • Lokalne oddziały funkcji których jeszcze nie opublikowałeś lub z których korzystasz tylko Ty.
  • Ostatnie zobowiązania te, o których wiesz, że na razie nikt inny nie jest ich zwolennikiem.

I to jest naprawdę zły pomysł, aby to zrobić w przypadku:

  • główny, rozwijający się lub jakikolwiek „oficjalny” oddział ze zbioru, który służy jako baza dla kilku osób.
  • Wspólne gałęzie robocze gdy zatwierdzenia dokonuje więcej niż jeden programista.

Jeśli przepiszesz historię współdzielonej gałęzi, a następnie wykonasz wymuszone wypchnięcie, inni członkowie zespołu to odkryją Jego gałęzie wskazują na zatwierdzenia, które nie istnieją już w zdalnym środowisku.Aby naprawić bałagan, będą musieli wykonać bardziej zaawansowane operacje (takie jak ponowne bazowanie na nowej historii lub zresetowanie jej.).

Pchanie siłą: lepsze z pasem bezpieczeństwa

W wielu przypadkach po rebase konieczne będzie wymuszenie push. Klasyczna opcja to:

git push --force origin tu-rama

Jednak ten wariant nadpisuje wszystko na komputerze zdalnym bez pytania. Aby uniknąć przypadkowego nadpisania pracy innych osób, Git oferuje znacznie lepsze rozwiązanie: –force-with-lease.

Kiedy to robisz:

git push --force-with-lease origin tu-rama

Git najpierw sprawdza, czy Pilot nadal jest w takim stanie, w jakim go widziałeś. Kiedy wykonałeś ostatnią operację pull lub fetch. Jeśli wykryje, że ktoś od tego czasu wypchnął nowe commity do tej gałęzi, push zostanie odrzucony, a nie wymuszony, co zapobiegnie nadpisaniu zmian wprowadzonych przez innych użytkowników.

Pomysł polega na połączeniu operacji rebase i wymuszonego push, zawsze z zachowaniem zimnej krwi: tylko w oddziałach, które kontrolujesz i stosując, kiedy tylko jest to możliwe, zabezpieczenie –force-with-lease jako dodatkową warstwę zabezpieczeń.

Co zrobić, gdy rebase się nie powiedzie: reflog na ratunek

Każdemu z nas się to zdarzyło: rozpoczynasz interaktywną zmianę bazy, przypadkowo coś usuwasz lub zmieniasz, nieprawidłowo rozwiązujesz konflikt i nagle Wygląda na to, że straciłeś część swojej pracyZanim wpadniesz w panikę, pamiętaj, że Git ma asa w rękawie: git reflog.

Reflog to lokalny zapis wszystkich zmian w HEAD: nowych commitów, resetów, rebaz, scaleń… Dzięki niemu możesz zlokalizować, gdzie znajdowała się Twoja gałąź, zanim ją zepsułeś, i powrócić do tego punktu za pomocą twardego resetu.

Typowy przepływ wyglądałby następująco:

git reflog

Tam zobaczysz listę wpisów zawierającą coś takiego:

abc1234 HEAD@{0}: rebase terminado
def5678 HEAD@{1}: checkout: moving from main to main
...

Określasz zatwierdzenie, w którym byłeś przed rozpoczęciem ponownego bazowania (na przykład, def5678) i wracasz do niego z:

git reset --hard def5678

W ten sposób Całkowicie cofasz nakładkę i wracasz do poprzedniego stanu. Możesz również przerwać trwający proces rebase (jeśli jesteś w trakcie i go nie ukończyłeś) za pomocą:

git rebase --abort

To polecenie pozostawia gałąź w stanie, w jakim była tuż przed rozpoczęciem bieżącego ponownego bazowania. Jest to idealne rozwiązanie, gdy pojawią się niepożądane konflikty lub gdy uznasz, że kontynuacja nie jest dobrym momentem.

Git pull kontra git pull –rebase: małe różnice, duży wpływ

Innym punktem, który często budzi wątpliwości, jest różnica pomiędzy git pull normalne i git pull --rebaseOba narzędzia aktualizują lokalną gałąź ze zdalnej, ale robią to w różny sposób.

Kiedy biegasz:

git pull

Git składa się z dwóch kroków: pobierz git aby wprowadzić zmiany zdalnie, a następnie scalanie git aby połączyć je z bieżącą gałęzią. Może to spowodować utworzenie dodatkowego zatwierdzenia scalenia, jeśli masz zatwierdzenia lokalne nad gałęzią zdalną, co ponownie historia z niepotrzebnymi połączeniami.

Jeśli jednak uruchomisz:

git pull --rebase

Git wykonuje pobieranie, a następnie Replikuj lokalne zatwierdzenia w zaktualizowanej wersji zdalnej.Wynik jest podobny do tego, który uzyskałeś, gdybyś zrobił git fetch następnie git rebase origin/maini utrzymuje czystszą, bardziej liniową historię.

Dlatego wiele zespołów konfiguruje Gita tak, aby domyślnie używał funkcji rebase podczas pobierania danych. To prosty sposób na unikaj automatycznych zatwierdzeń scalania które nie wnoszą wiele.

Usuwanie plików lub obrazów z historii: ogólna koncepcja

Czasami problemem nie jest brzydki commit, ale plik, który nigdy nie powinien trafić do repozytorium: błędny obraz, nieprawidłowe dane uwierzytelniające, duże pliki itp. Kuszące jest przejście do GitHub i poszukanie ikony kosza, ale jeśli wydaje się ona wyłączona i wyświetla komunikaty takie jak „musisz być na gałęzi”, to dlatego, że GitHub nie pozwala na usunięcie historii w ten sposób..

Prawidłowa metoda zazwyczaj polega na użyciu przepisywania historii z komputera lokalnego (z interaktywnym przebazowaniem lub narzędziami takimi jak repozytorium filtrów git) a następnie wykonaj wymuszoną operację push. W GitHub Desktop możesz również zarządzać gałęziami i zatwierdzeniami, ale operacja całkowicie usunąć plik ze wszystkich zatwierdzeń Polega to na przepisaniu historii w sposób podobny do tego, jaki widzimy tutaj.

Krótko mówiąc, jeśli prześlesz niewłaściwy obraz lub poufny plik i chcesz, aby „zniknął” z historii, samo usunięcie go w ostatnim zatwierdzeniu nie wystarczy: Zmiany, w których została wprowadzona, muszą zostać przepisane. a następnie wymusić pchnięcie. To delikatna operacja i powinna być przeprowadzona spokojnie i we współpracy z zespołem.

Praktyczny schemat wyprzedzania bez uszkadzania pojazdu

Najlepszym sposobem na opanowanie wyprzedzania jest ćwiczenie w kontrolowanym środowisku, bez obawy o zakłócenie pracy innych. Prosty schemat działania wyglądałby następująco:

  1. Utwórz nową gałąź z gałęzi głównej za pomocą git checkout -b my-branch-tests.
  2. Wprowadź kilka małych zmian (mogą to być drobne zmiany lub pliki testowe).
  3. Symuluj główny postęp, dokonując nowych zatwierdzeń (lub prosząc kogoś innego o zrobienie tego).
  4. Wróć do gałęzi testowej i uruchom git rebase main aby zobaczyć, jak zmieniane są Twoje zatwierdzenia.
  5. Prueba un git rebase -i HEAD~3 aby połączyć, zmienić nazwę lub usunąć dowolne z ostatnich zatwierdzeń.

Dzięki temu ćwiczeniu zobaczysz jak Zmień historię przed i poJak Git reaguje na konflikty i jak w razie potrzeby można powrócić do poprzednich stanów za pomocą reflog. Im więcej praktyki zdobędziesz lokalnie, tym pewniej będziesz stosować te techniki w rzeczywistych gałęziach projektu.

Opanowanie rebase w Gicie pozwala przekształcić chaotyczną historię, pełną pustych commitów, zbędnych scaleń i bezsensownych komunikatów, w czytelną sekwencję istotnych zmian. Używając interaktywnego rebase, usuwając commity testowe, grupując rozproszone prace, liniowo aktualizując gałęzie oraz wykorzystując narzędzia takie jak reflog i wymuszone pushe z opcją `--force-with-lease`, możesz utrzymać swoje repozytorium w znacznie lepszym i bardziej profesjonalnym stanie, o ile pamiętasz o stosowaniu tych technik tylko do gałęzi, nad którymi masz kontrolę, i powiadamiasz zespół przed nadpisaniem wspólnej historii.

Historia wersji w Dysku: jak z niej korzystać i najlepsze praktyki
Podobne artykuł:
Historia wersji w Dysku: zaawansowane wykorzystanie i najlepsze praktyki

Dodaj jako preferowane źródło