git rebase: Funktionsweise, interaktiver Modus und Commits squashen
git rebase verschiebt die Commits des aktuellen Branches auf eine neue Basis: Git nimmt Ihre Commits vorübergehend ab, setzt den Branch ans Ende des angegebenen Branches und wendet sie als neue Commits erneut an. So entsteht eine lineare Historie ohne Merge-Commits, und mit git rebase -i lassen sich Commits zusammenfassen (squash), umbenennen oder löschen; umschreiben sollten Sie so nur Commits, die noch nicht für andere veröffentlicht sind.
Was macht git rebase?
git rebase „verpflanzt“ Ihren Branch an eine andere Stelle der Historie. Angenommen, Sie haben feature vor einer Woche von main abgezweigt, und main ist seitdem weitergewandert. Nach dem Rebase sieht Ihr Branch so aus, als hätten Sie gerade erst angefangen – vom neuesten Commit auf main aus.
git switch feature
git fetch origin
git rebase origin/main
Unter der Haube passiert das in drei Schritten. Git findet den gemeinsamen Vorfahren der Branches und sichert nacheinander die Änderungen jedes Ihrer Commits danach, verschiebt den Branch auf origin/main und wendet die gesicherten Änderungen einzeln erneut an. Es entstehen neue Commits mit demselben Inhalt und denselben Nachrichten, aber mit anderen Hashes.
Vorher: A---B---C---F---G main
\
D---E feature
Nachher: A---B---C---F---G main
\
D'---E' feature
Die Striche bei D' und E' sind keine Dekoration. Es sind andere Objekte mit anderen Hashes und einem anderen Elternteil. Daraus folgt die wichtigste Rebase-Regel, um die es gleich geht.
Die goldene Regel: Rebasen Sie nichts, was schon veröffentlicht ist
Rebase schreibt die Historie um. Solange nur Sie die Commits haben, ist das sicher. Haben Kollegen den Branch aber schon geholt, bleiben bei ihnen nach dem Rebase die alten D und E, bei Ihnen die neuen D' und E' – beim nächsten pull verdoppelt sich die Historie, und Konflikte müssen erneut gelöst werden.
Praxisregel: Rebase verwendet man für eigene lokale Branches und persönliche Feature-Branches, an denen nur eine Person arbeitet. Gemeinsame Branches – main, develop, Release-Branches – werden niemals rebased.
Branch nach einem Rebase pushen
Lag der Branch schon auf dem Server, wird ein normales git push abgelehnt: Die lokale Historie setzt die entfernte nicht mehr fort. Sie brauchen einen erzwungenen Push, und zwar in der sicheren Variante:
git push --force-with-lease
Anders als --force verweigert --force-with-lease das Überschreiben, wenn auf dem Server Commits aufgetaucht sind, die Sie nicht gesehen haben. So löschen Sie nicht versehentlich fremde Arbeit.
Konflikte beim Rebase lösen
Da die Commits einzeln angewendet werden, kann bei jedem von ihnen ein Konflikt auftreten – auch mehrmals hintereinander. Git hält an und meldet, welcher Commit sich nicht anwenden ließ.
$ git rebase origin/main
Auto-merging src/api.js
CONFLICT (content): Merge conflict in src/api.js
error: could not apply 4e1f9a2... Request-Cache hinzufügen
hint: Resolve all conflicts manually, mark them as resolved with
hint: "git add/rm <conflicted_files>", then run "git rebase --continue".
Vorgehen:
- Öffnen Sie die Datei mit dem Konflikt, wählen Sie die richtige Variante und entfernen Sie die Marker
<<<<<<<,=======,>>>>>>>. - Markieren Sie die Datei als gelöst:
git add src/api.js. Ein Commit ist nicht nötig. - Machen Sie weiter:
git rebase --continue. Git wendet den nächsten Commit an.
Die übrigen Befehle zur Steuerung des Vorgangs:
| Befehl | Was er macht |
|---|---|
git rebase --continue | Nach dem Lösen des Konflikts fortfahren |
git rebase --skip | Aktuellen Commit komplett überspringen (seine Änderungen gehen verloren) |
git rebase --abort | Rebase abbrechen und den Branch in den Ausgangszustand zurückversetzen |
Achten Sie auf die Verwechslungsgefahr bei „ours“ und „theirs“. Während eines Rebase ist HEAD die Basis, auf die Sie den Branch verschieben, und die „eingehenden“ Änderungen sind Ihre eigenen Commits. Deshalb nimmt git checkout --ours beim Rebase die Version aus main und --theirs Ihre eigene. Bei merge ist es genau umgekehrt.
Wiederkehrende Konflikte: rerere
Wenn Sie einen langlebigen Branch regelmäßig rebasen und immer wieder denselben Konflikt lösen, aktivieren Sie git config --global rerere.enabled true. Git merkt sich, wie Sie den Konflikt gelöst haben, und wendet die Lösung beim nächsten Mal selbst an.
Interaktives Rebase: Historie vor der Veröffentlichung aufräumen
Das Wertvollste an rebase ist der interaktive Modus. Damit bringen Sie Ihre Commits in Ordnung, bevor Sie sie ins Review geben: „fix“, „fix typo“, „noch ein fix“ zu einem sinnvollen Commit zusammenfassen, Nachrichten umschreiben, die Reihenfolge ändern oder Überflüssiges entfernen.
git rebase -i HEAD~4 # die letzten 4 Commits
git rebase -i origin/main # alle Commits des Branches, die nicht in main sind
Git öffnet einen Editor mit der Liste der Commits – von alt nach neu (anders als bei git log):
pick 1a2b3c4 Login-Formular hinzufügen
pick 5d6e7f8 fix
pick 9a0b1c2 E-Mail-Validierung
pick 3d4e5f6 Tippfehler in Validierung
# 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 keep only the previous commit's log message
# d, drop = remove commit
Sie ändern das Wort am Zeilenanfang, speichern und schließen den Editor – Git arbeitet den Plan von oben nach unten ab.
| Befehl | Wirkung |
|---|---|
pick | Commit unverändert übernehmen |
reword | Änderungen behalten, aber die Nachricht umschreiben |
edit | Am Commit anhalten, um seinen Inhalt zu ändern (git commit --amend) oder ihn in mehrere aufzuteilen |
squash | In den vorherigen Commit einschmelzen, Nachrichten zusammenführen und bearbeiten |
fixup | In den vorherigen Commit einschmelzen, die Nachricht dieses Commits verwerfen |
drop | Commit samt Änderungen löschen |
Das Umsortieren der Zeilen ändert die Reihenfolge der Commits, das Löschen einer Zeile entspricht drop.
Git squash: mehrere Commits zu einem zusammenfassen
Einen eigenen Befehl git squash gibt es in Git nicht. Commits „squashen“ können Sie auf drei Wegen, und das interaktive Rebase ist der flexibelste davon.
Weg 1: Interaktives Rebase
Für das Beispiel oben machen wir aus vier Commits zwei saubere:
pick 1a2b3c4 Login-Formular hinzufügen
fixup 5d6e7f8 fix
pick 9a0b1c2 E-Mail-Validierung
fixup 3d4e5f6 Tippfehler in Validierung
Der erste Commit einer Gruppe muss pick bleiben: squash und fixup schmelzen einen Commit in den vorherigen ein, und der allererste hat keinen Vorgänger.
Weg 2: fixup-Commits und autosquash
Wenn Sie schon wissen, welchen Commit Sie korrigieren, legen Sie die Korrektur gleich passend markiert an:
git commit --fixup=1a2b3c4
git rebase -i --autosquash origin/main
Git setzt den fixup-Commit selbst unter den richtigen und markiert ihn als fixup – Sie müssen den Plan nur noch speichern. Als Standardverhalten aktivieren Sie das mit git config --global rebase.autoSquash true.
Weg 3: den ganzen Branch zu einem Commit zusammenfassen
git reset --soft $(git merge-base HEAD origin/main)
git commit -m "Login per E-Mail mit Validierung"
reset --soft setzt den Branch-Zeiger auf den Abzweigpunkt zurück, lässt aber alle Änderungen im Index – Sie müssen nur noch einen Commit machen. Weitere Möglichkeiten: git merge --squash beim Integrieren in den Haupt-Branch oder der Button „Squash and merge“ im Pull Request.
Nützliche Rebase-Varianten
git pull --rebase
Ein normales pull merged den Remote-Branch in Ihren und erzeugt einen Merge-Commit, wenn Sie parallel zu Kollegen committet haben. git pull --rebase setzt stattdessen Ihre lokalen Commits auf die neuen Remote-Commits. Als Standard: git config --global pull.rebase true. Das ist genau die sichere Zone für rebase – Ihre lokalen Commits hat noch niemand gesehen.
--onto: einen Teil des Branches verschieben
Sie haben feature-b von feature-a abgezweigt und wollen jetzt, dass feature-b direkt von main ausgeht, ohne die Commits von feature-a:
git rebase --onto main feature-a feature-b
Gelesen wird das so: Nimm die Commits von feature-b, die nicht in feature-a sind, und verpflanze sie auf main.
--exec: jeden Commit prüfen
git rebase -i --exec "npm test" origin/main
Nach jedem angewendeten Commit laufen die Tests; schlagen sie fehl, hält das Rebase beim problematischen Commit an. Praktisch, wenn jeder Commit der Historie bauen soll.
--update-refs: Stapel abhängiger Branches
Wenn Sie eine Kette voneinander abhängiger Branches pflegen, verschiebt die Option --update-refs (Git 2.38+) beim Rebase des obersten Branches auch die dazwischenliegenden.
Wie macht man ein Rebase rückgängig?
Während es läuft – git rebase --abort. Ist das Rebase schon abgeschlossen und das Ergebnis gefällt Ihnen nicht, sind die alten Commits nicht verschwunden: Das Referenz-Log kennt sie noch.
$ git reflog
e5f6a7b HEAD@{0}: rebase (finish): returning to refs/heads/feature
e5f6a7b HEAD@{1}: rebase (pick): E-Mail-Validierung
...
c3d4e5f HEAD@{5}: commit: Tippfehler in Validierung
$ git reset --hard HEAD@{5}
Suchen Sie in git reflog den Eintrag vor Beginn des Rebase und setzen Sie den Branch darauf zurück. Direkt nach dem Rebase funktioniert auch die Abkürzung git reset --hard ORIG_HEAD. Das Reflog wird lokal gespeichert und hält Einträge standardmäßig etwa 90 Tage, nach einem missglückten Rebase ist Panik also unnötig.
Typische Fehler
- Gemeinsamen Branch rebased und force-gepusht – bei den Kollegen läuft die Historie auseinander. Lösung: sich abstimmen, den Branch aus dem Reflog desjenigen wiederherstellen, bei dem er korrekt ist, und Force-Push auf geschützte Branches serverseitig verbieten.
--forcestatt--force-with-leaseverwendet und dabei einen Commit überschrieben, den ein Kollege gerade in denselben Branch gepusht hat.- Squash in der ersten Zeile des interaktiven Plans – Git antwortet mit dem Fehler „cannot 'squash' without a previous commit“.
- Rebase mit unsauberem Arbeitsverzeichnis gestartet. Git verweigert den Start. Committen Sie die Änderungen, legen Sie sie mit
git stashbeiseite oder verwenden Siegit rebase --autostash. - Konflikt gelöst und
git commitausgeführt stattgit rebase --continue. Das ergibt einen überflüssigen Commit; nach--continuelässt er sich mit einem weiteren interaktiven Rebase entfernen.
Fazit
git rebase verschiebt Ihre Commits auf eine neue Basis und erzeugt sie neu, deshalb wird die Historie linear und sauber. Der interaktive Modus macht aus Entwurfs-Commits eine ordentliche Historie: squash und fixup fassen Korrekturen zusammen, reword korrigiert Nachrichten, drop entfernt Überflüssiges. Die wichtigste Regel ist nur eine – rebasen Sie ausschließlich, was Sie noch nicht für andere veröffentlicht haben, und pushen Sie Ihren Remote-Branch nach einem Rebase mit --force-with-lease. Die Arbeit mit rebase und das Aufräumen der Commit-Historie werden im Git-Kurs praktisch geübt.
Häufige Fragen
Verändert git rebase den Inhalt des Codes?
Ohne Konflikte ist der Endstand der Dateien derselbe wie bei einem merge, aber die Commits selbst werden mit neuen Hashes und neuen Eltern neu erzeugt. Haben Sie bei einem Konflikt anders entschieden, unterscheiden sich die Zwischen-Commits von den ursprünglichen.
Wie rebased man bis zum allerersten Commit des Repositorys?
Mit git rebase -i --root: Dann landen alle Commits im Plan, auch der Root-Commit. Das braucht man selten, etwa um die Nachricht des allerersten Commits zu ändern.
Bleiben Autor und Datum der Commits beim Rebase erhalten?
Autor und Autorendatum bleiben erhalten, Committer und Commit-Datum werden auf die aktuellen Werte gesetzt. Mit --committer-date-is-author-date bleibt das Commit-Datum gleich dem Autorendatum.
Kann man Merge-Commits rebasen?
Standardmäßig begradigt rebase die Historie und verwirft Merge-Commits. Um die Merge-Struktur zu erhalten, verwenden Sie git rebase --rebase-merges.
Woran erkennt man, dass gerade ein Rebase läuft?
git status zeigt die Zeile interactive rebase in progress oder You are currently rebasing und nennt die verfügbaren Befehle. Viele Shells und IDEs zeigen das außerdem im Prompt an.
Dieses Thema wird im Kurs „Git vom Anfänger zum versierten Entwickler“ ausführlich mit Praxis behandelt.
Zum Kurs