Rebase — to operacja w Git, która przenosi commity z jednej gałęzi na wierzchołek drugiej, tworząc liniową historię bez zbędnych merge-commitów. W przeciwieństwie do scalania, rebase przepisuje historię: każdy przeniesiony commit otrzymuje nowy hash, ponieważ zmienia się jego rodzic. Według dokumentacji Git (2026), rebase stosuje się do synchronizacji gałęzi feature z aktualnym stanem main przed utworzeniem pull request. Polecenie git rebase to jedno z podstawowych narzędzi do utrzymania czystej historii w projektach korzystających z Git Flow.
Najważniejsze
Rebase — to polecenie Git, które przebazuje bieżącą gałąź na wskazaną: bierze wszystkie commity bieżącej gałęzi, tymczasowo je zapisuje, przesuwa wskaźnik gałęzi na docelowy commit i sekwencyjnie aplikuje zapisane commity na jego wierzchołek. Rezultatem jest historia wyglądająca tak, jakby programista pracował bezpośrednio od ostatniego commita gałęzi docelowej.
Podstawowa składnia: git rebase main — będąc w gałęzi feature, to polecenie przenosi wszystkie commity feature na wierzchołek main. Git używa strategii three-way merge dla każdego commita osobno. Jeśli commit A jest już obecny w gałęzi docelowej (określany po hash), Git automatycznie go pomija, co pozwala uniknąć duplikowania zmian.
Rebase obsługuje również tryb onto do przenoszenia części commitów: git rebase --onto target start end — ta forma pozwala wydobyć zakres commitów z jednej gałęzi i zastosować je na wierzchołku drugiej. Na przykład git rebase --onto main feature~3 feature przeniesie ostatnie trzy commity gałęzi feature na wierzchołek main.
# Przełącz na gałąź feature
git checkout feature
# Przebazuj feature na main
git rebase main
# Po udanym rebazie — historia jest liniowa
git log --oneline --graph
# Przenieś ostatnie 3 commity do main
git rebase --onto main HEAD~3 HEAD
Rebase i merge rozwiązują to samo zadanie — łączenie zmian z różnych gałęzi — ale robią to zasadniczo różnymi sposobami. Merge zachowuje pełną historię scaliń, tworząc merge-commit z dwoma rodzicami. Rebase przepisuje historię, czyniąc ją liniową. Wybór między nimi zależy od workflow zespołu i zasad pracy z repozytorium.
Główna różnica — jak rejestrowany jest fakt scalenia. Merge zachowuje: „w tym momencie połączyliśmy feature w main” — to informatywne dla historii projektu, ale zaśmieca log przy częstych scaleniach. Rebase pokazuje: „commity feature zostały wykonane sekwencyjnie od ostatniego stanu main” — to czyste, ale ukrywa fakt, że prace były prowadzone równolegle.
Druga różnica — obsługa konfliktów. Przy merge konflikty rozwiązuje się raz, a rozwiązanie jest zapisywane w merge-commit. Przy rebase konflikty mogą wystąpić dla każdego przenoszonego commita i każdy wymaga osobnego rozwiązania. Jest to bardziej czasochłonne, ale pozwala dokładniej kontrolować, które zmiany trafiają do wersji końcowej.
| Kryterium | Rebase | Merge |
|---|---|---|
| Historia | Liniowa, bez merge-commitów | Nieliniowa, z merge-commitami |
| Hashe commitów | Są nadpisywane (nowe) | Oryginalne są zachowane |
| Konflikty | Dla każdego commita osobno | Raz w merge-commit |
| Publiczne gałęzie | Zabroniony | Dozwolony |
| Polecenie anulowania | git rebase --abort | git merge --abort |
Interaktywny rebase (git rebase -i) — to tryb, w którym Git otwiera edytor z listą commitów i dostępnymi działaniami dla każdego z nich. Programista może przepisać historię przed wysłaniem do zdalnego repozytorium. To podstawowe narzędzie do utrzymania czystości commitów w gałęzi feature.
Dostępne polecenia w trybie interaktywnym: pick (zostaw commit bez zmian), reword (zmień wiadomość commita), edit (zatrzymaj się w celu wprowadzenia zmian), squash (połącz z poprzednim commitem, zachowując obie wiadomości), fixup (połącz, odrzucając wiadomość), drop (usuń commit). Każde polecenie wpisuje się przed hashem commita w otwartym edytorze.
Squash i fixup — najczęściej używane polecenia do łączenia commitów. Jeśli programista zrobił 5 małych commitów z poprawkami w trakcie pracy, squash połączy je w jeden logiczny commit z sensowną wiadomością. Fixup jest przydatny do poprawiania literówek: zmiany trafiają do poprzedniego commita bez zapisywania własnej wiadomości.
# Otwórz edytor dla ostatnich 4 commitów
git rebase -i HEAD~4
# Edytor pokaże:
pick a1b2c3d Add authentication
pick e4f5g6h Fix typo in login
squash i6j7k8l Additional login fixes
pick m9n0o1p Add auth tests
# Po zapisaniu — Git wykonuje rebase
# i otwiera edytor dla scalonej wiadomości commita
# Auto-squash bez otwierania edytora
git rebase -i HEAD~4 --autosquash
Flaga --autosquash automatycznie ustawia fixup/squash dla commitów, których wiadomości zaczynają się od fixup! lub squash!. Przyspiesza to pracę, jeśli programista z góry oznacza commity do późniejszego połączenia. Flaga --committer-date-is-author-date zachowuje oryginalną datę commita przy przebazowaniu — przydatne do zachowania chronologii w historii.
Konflikty przy rebase pojawiają się, gdy Git nie może automatycznie zastosować przenoszonego commita z powodu sprzeczności ze zmianami w gałęzi docelowej. W przeciwieństwie do merge, gdzie konflikt rozwiązuje się raz, przy rebase każdy commit może wywołać konflikt i trzeba go rozwiązywać sekwencyjnie dla każdego commita od najstarszego do najnowszego.
Gdy pojawia się konflikt, Git wstrzymuje rebase i informuje, który commit spowodował problem. Programista otwiera konfliktowy plik (Git oznacza konfliktowe fragmenty znacznikami <<<<<<<, =======, >>>>>>>), edytuje go, dodaje do indeksu (git add) i kontynuuje rebase poleceniem git rebase --continue. Jeśli nie znaleziono rozwiązania — git rebase --abort całkowicie anuluje przebazowanie.
Wskazówka: przy wielokrotnych konfliktach skuteczniejsze jest użycie git mergetool, który otwiera wizualny edytor do rozwiązywania sprzeczności. Można również pominąć problematyczny commit (git rebase --skip), ale usunie to jego zmiany z końcowej historii, co rzadko bywa właściwym rozwiązaniem.
# Rozpocznij rebase z konfliktem
git rebase main
# Auto-merging file.txt
# KONFLIKT (treść): Konflikt scalania w file.txt
# Sprawdź status
git status
# oba zmodyfikowane: file.txt
# Edytuj skonfliktowane sekcje → git add → kontynuuj
git add file.txt
git rebase --continue
# W razie wątpliwości — przerwij
git rebase --abort
Złota zasada rebase: nigdy nie przebazować commitów, które zostały już wysłane do zdalnego repozytorium i są dostępne dla innych programistów. Ponieważ rebase nadpisuje hashe commitów, u kolegów pojawią się konflikty przy próbie synchronizacji — ich lokalna historia będzie się rozchodzić z nadpisaną zdalną.
Sytuacja, w której rebase jest kategorycznie zabroniony: jeśli ktoś już utworzył gałąź na podstawie twoich commitów (na przykład twój kolega zrobił feature od twojego feature), zmiana historii zepsuje jego pracę. W takich przypadkach należy użyć merge. Nie zaleca się również robienia rebase tuż przed deadlinem — błąd przy rozwiązywaniu konfliktów może zająć więcej czasu niż oczekiwano i zablokować wydanie.
Wyjątek: jeśli gałąź jest używana tylko przez jednego programistę (osobista gałąź feature, nieopublikowana lub opublikowana w trybie draft), rebase przed pushem to standardowa praktyka. Po publikacji i rozpoczęciu pracy zespołowej — tylko merge. GitHub i GitLab domyślnie oferują squash merge jako kompromis: łączy commity w jeden, ale nie przepisuje historii gałęzi docelowej.
We współczesnych zespołach najczęściej stosuje się workflow zorientowany na rebase w połączeniu z GitHub Flow. Proces wygląda tak: programista tworzy gałąź feature od main, pracuje w niej, okresowo synchronizuje się przez git rebase main, a przed utworzeniem pull request wykonuje interaktywny rebase w celu oczyszczenia historii.
Po utworzeniu PR (jeśli trzeba pobrać nowe zmiany z main) używa się git pull --rebase main zamiast zwykłego git pull. Pozwala to wciągnąć zmiany bez tworzenia zbędnego merge-commita. Git pull z flagą --rebase jest równoważny git fetch + git rebase — Git najpierw pobiera nowe commity, a następnie przebazuje lokalne zmiany na ich wierzchołek.
Git pozwala ustawić rebase jako domyślne zachowanie dla pull: git config --global pull.rebase true. Po tej konfiguracji git pull zawsze wykonuje rebase zamiast merge. Jeśli potrzebny jest zwykły pull — używa się git pull --no-rebase. Wiele zespołów włącza również autostash: git config --global rebase.autoStash true — to automatycznie chowa niezatwierdzone zmiany przed rebase i przywraca je po.
Często zadawane pytania
Przebazować (zrobić rebase) — oznacza wykonać git rebase: przenieść commity bieżącej gałęzi na wierzchołek drugiej. W rezultacie historia staje się liniowa, każdy commit otrzymuje nowy hash, a merge-commity nie są tworzone. Polecenie jest używane do synchronizacji gałęzi bez zbędnych punktów scalenia w logu.
Merge tworzy merge-commit z dwoma rodzicami, zachowując równoległą historię i oryginalne hashe. Rebase nadpisuje historię — commity otrzymują nowe hashe, a historia staje się liniowa. Merge jest bezpieczniejszy dla publicznych gałęzi, rebase daje czystszy log.
Polecenie git rebase -i HEAD~N otwiera edytor z N ostatnimi commitami. Dla każdego commita można wybrać działanie: pick (zostaw), reword (zmień nazwę), edit (zmień), squash (połącz z poprzednim), fixup (połącz bez wiadomości), drop (usuń). Po zapisaniu Git stosuje wybrane zmiany.
Rebase nadpisuje hashe commitów, co czyni historię niekompatybilną z kopiami tych samych commitów u innych programistów. Jeśli kolega już otrzymał twoje commity przez git pull, a ty później je przebazowałeś, jego git push zostanie odrzucony, a git pull utworzy duplikujące się commity i konflikty.
Przed zakończeniem — git rebase --abort anuluje całkowicie. Po zakończeniu można przywrócić poprzedni stan przez git reflog — znaleźć hash commita sprzed rebase i wykonać git reset --hard do niego. Reflog przechowuje historię przemieszczeń HEAD przez domyślnie 30 dni.
Podsumowanie
Opracujemy aplikację mobilną pod klucz
IT Sectr tworzy aplikacje na iOS i Androida dla startupów i firm od 2017 roku. Doradzimy Ci i zaproponujemy najlepsze rozwiązanie.
Przeczytaj również