Commits aufräumen vor dem Pull Request
Wer einen Feature-Branch mit chaotischer Commit-Historie zur Review einreicht, kostet dem Team Zeit und verschenkt Vertrauen. Dieser Guide zeigt Schritt für Schritt, wie ihr mit git rebase -i Commits umordnet, zusammenfasst, umbenennt oder entfernt, Konflikte sicher löst und einen missglückten Rebase jederzeit über die reflog wieder rückgängig macht.
Inhaltsverzeichnis
- 1. Warum eine unaufgeräumte Commit-Historie vor dem Pull Request zum Problem wird
- 2. Interactive Rebase starten: git rebase -i HEAD~n und die Todo-Liste lesen
- 3. Der Befehl pick und das Umsortieren von Commits
- 4. squash versus fixup: Commits sinnvoll zusammenführen
- 5. reword: Commit-Nachrichten korrigieren, ohne den Diff zu verändern
- 6. drop zum Entfernen und edit zum Aufteilen von Commits
- 7. Konflikte während des Rebase lösen: continue, skip, abort
- 8. Reflog zur Rettung: einen missglückten Rebase rückgängig machen
- 9. Typische Stolperfallen: riskante versus sichere Praktiken
- 10. Zusammenfassung
- 11. FAQ
1. Warum eine unaufgeräumte Commit-Historie vor dem Pull Request zum Problem wird
Ein Feature-Branch mit zehn Commits wie fix, wip, noch ein Versuch und asdf ist für Reviewer eine Zumutung. Wer den Pull Request öffnet, ohne die Historie vorher aufzuräumen, zwingt Kollegen dazu, sich durch Zwischenstände zu wühlen, die nie eigenständig funktionsfähig waren und keinerlei Aussagekraft haben. Jeder Commit sollte für sich genommen kompilieren, testbar sein und eine nachvollziehbare Änderung beschreiben, sonst werden git blame und git bisect bei der nächsten Fehlersuche wertlos, weil einzelne Commits keine sinnvolle Granularität mehr besitzen.
Interactive Rebase vor dem Öffnen eines Pull Requests ist deshalb kein optionales Feintuning, sondern gelebte Praxis in praktisch jedem professionellen Git-Workflow. Wichtig ist die Grenze: Solange ein Branch nur lokal existiert oder ausschließlich euch persönlich gehört, ist das Umschreiben der Historie völlig unproblematisch. Sobald andere Entwickler denselben Branch bereits ausgecheckt haben, wird ein Rebase riskant und sollte vorher im Team abgesprochen werden, weil jeder rebasete Commit eine neue Prüfsumme bekommt und bereits vorhandene lokale Kopien in Konflikt geraten.
2. Interactive Rebase starten: git rebase -i HEAD~n und die Todo-Liste lesen
git rebase -i HEAD~n startet einen interaktiven Rebase über die letzten n Commits, gezählt vom aktuellen HEAD aus. In der Praxis ist die Alternative git rebase -i main oft komfortabler, weil sie automatisch genau die Commits erfasst, die sich vom Basis-Branch unterscheiden, ohne dass man die Anzahl der Commits vorher zählen muss. Nach dem Aufruf öffnet Git den konfigurierten Editor mit einer Todo-Liste, in der jeder Commit als eigene Zeile erscheint, chronologisch aufsteigend sortiert: Der älteste Commit steht oben, der neueste unten.
Jede Zeile beginnt standardmäßig mit dem Befehl pick, gefolgt von der abgekürzten Commit-ID und der ersten Zeile der Commit-Nachricht. Unterhalb der Todo-Liste fügt Git einen ausführlichen Kommentarblock ein, der alle verfügbaren Befehle mit Kurzform und Erklärung auflistet, dieser Block wird beim Speichern automatisch entfernt und beeinflusst den Rebase nicht. Entscheidend zu verstehen: Die Datei ist eine Liste von Anweisungen, keine Historie. Jede Änderung, ob Zeile löschen, Befehl ändern oder Reihenfolge tauschen, wird beim Schließen des Editors direkt ausgeführt.
# Inspect the last 5 commits on the feature branch
git log --oneline -5
a1b2c3d fix typo
e4f5g6h wip: cart totals
h7i8j9k add discount calculation
k1l2m3n wip
n4o5p6q feat: add loyalty discount to cart
# Start an interactive rebase covering these 5 commits
git rebase -i HEAD~5
# Or rebase interactively against the base branch instead
git rebase -i main
# The editor opens a todo list, oldest commit first
pick n4o5p6q feat: add loyalty discount to cart
pick k1l2m3n wip
pick h7i8j9k add discount calculation
pick e4f5g6h wip: cart totals
pick a1b2c3d fix typo
# Comment block below lists every available command
# p, pick <commit> = use commit
# r, reword <commit> = use commit, but edit the commit message
# e, edit <commit> = use commit, but stop for amending
# s, squash <commit> = use commit, but meld into previous commit
# f, fixup <commit> = like squash, but discard this commit's log message
# d, drop <commit> = remove commit
3. Der Befehl pick und das Umsortieren von Commits
pick ist der Standardbefehl und bedeutet schlicht: diesen Commit unverändert übernehmen. Ein Rebase, bei dem jede Zeile pick bleibt und die Reihenfolge unangetastet, verändert am Ende nichts außer den Commit-Hashes, weil jeder Commit gegen eine potenziell neue Basis neu angewendet wird. Der eigentliche Nutzen von pick zeigt sich erst in Kombination mit dem Umsortieren: Da die Zeilenreihenfolge in der Todo-Liste exakt die Anwendungsreihenfolge bestimmt, genügt es, zwei Zeilen im Editor zu vertauschen, um die Reihenfolge zweier Commits im späteren Log zu tauschen.
Beim Umsortieren ist Vorsicht geboten, wenn Commits inhaltlich voneinander abhängen. Wird ein Commit vor einen anderen gezogen, von dessen Änderungen er eigentlich abhängt, etwa weil er dieselbe Zeile im selben File modifiziert, produziert Git beim Anwenden einen Konflikt, den man dann manuell auflösen muss. Ein guter Anhaltspunkt: Commits, die unterschiedliche Dateien oder klar getrennte Funktionsbereiche betreffen, lassen sich meist gefahrlos umsortieren, während aufeinander aufbauende Änderungen am selben Codeabschnitt besser in ihrer ursprünglichen Reihenfolge bleiben.
4. squash versus fixup: Commits sinnvoll zusammenführen
Sowohl squash als auch fixup verschmelzen einen Commit mit dem direkt darüberstehenden, also dem vorherigen in der Todo-Liste. Der Unterschied liegt in der Behandlung der Commit-Nachricht: squash öffnet nach dem Zusammenführen einen Editor, in dem beide Nachrichten kombiniert vorliegen und frei bearbeitet werden können, während fixup die Nachricht des zu verschmelzenden Commits stillschweigend verwirft und nur die Nachricht des vorherigen Commits behält. squash eignet sich deshalb, wenn beide Commits jeweils relevante Information für die finale Nachricht enthalten, fixup ist die richtige Wahl für Commits wie wip oder fix typo, deren Nachricht ohnehin keinen Mehrwert bietet.
Ein produktiver Workflow kombiniert fixup mit git commit --fixup <commit>, das automatisch eine Commit-Nachricht im Format fixup! erzeugt, die auf den Zielcommit verweist. Beim anschließenden git rebase -i --autosquash sortiert Git diese Fixup-Commits automatisch direkt unter ihren Zielcommit in der Todo-Liste und markiert sie bereits mit fixup, sodass man die Reihenfolge nicht mehr manuell anpassen muss. Das spart bei größeren Feature-Branches mit vielen kleinen Korrekturcommits erheblich Zeit.
# Before: five commits, several are noise
pick n4o5p6q feat: add loyalty discount to cart
pick k1l2m3n wip
pick h7i8j9k add discount calculation
pick e4f5g6h wip: cart totals
pick a1b2c3d fix typo
# After: fold noise commits into their predecessor
pick n4o5p6q feat: add loyalty discount to cart
fixup k1l2m3n wip
squash h7i8j9k add discount calculation
fixup e4f5g6h wip: cart totals
fixup a1b2c3d fix typo
# Result after saving and closing the editor:
git log --oneline -1
d9e8f7g feat: add loyalty discount to cart
# Shortcut: mark a fixup target at commit time, then autosquash later
git commit --fixup h7i8j9k
git rebase -i --autosquash main
5. reword: Commit-Nachrichten korrigieren, ohne den Diff zu verändern
reword pausiert den Rebase-Prozess an genau diesem Commit und öffnet ausschließlich den Editor für die Commit-Nachricht, der eigentliche Diff des Commits bleibt vollständig unangetastet. Das ist der ideale Befehl, um Tippfehler in älteren Commit-Nachrichten zu korrigieren, eine Nachricht nachträglich an Conventional-Commits-Konventionen wie feat: oder fix: anzupassen oder schlicht eine zu knappe Beschreibung durch eine aussagekräftigere zu ersetzen, ohne den Code selbst noch einmal anzufassen.
Der wesentliche Unterschied zu git commit --amend: amend funktioniert ausschließlich für den allerletzten Commit auf dem aktuellen Branch, reword dagegen erlaubt das Umschreiben der Nachricht jedes beliebigen Commits in der Todo-Liste, unabhängig davon, wie viele weitere Commits danach folgen. Nach dem Schließen des Nachrichten-Editors läuft der Rebase automatisch mit dem nächsten Todo-Eintrag weiter, ohne dass ein manuelles rebase --continue nötig wäre, solange kein Konflikt auftritt.
6. drop zum Entfernen und edit zum Aufteilen von Commits
drop entfernt einen Commit vollständig aus der Historie, seine Änderungen verschwinden komplett aus dem Branch. Das Gleiche lässt sich erreichen, indem man die entsprechende Zeile einfach aus der Todo-Liste löscht, der explizite Befehl drop macht die Absicht aber deutlicher lesbar für spätere Nachvollziehbarkeit. Vorsicht ist geboten, wenn spätere Commits auf den gedroppten Commit inhaltlich aufbauen: In diesem Fall entstehen beim Anwenden der nachfolgenden Commits Konflikte, weil der erwartete Zwischenstand im Code fehlt.
edit dagegen entfernt einen Commit nicht, sondern pausiert den Rebase genau an dieser Stelle mit dem Arbeitsverzeichnis im Zustand nach Anwendung dieses Commits. Das ist der Einstiegspunkt, um einen zu großen Commit nachträglich in mehrere kleinere aufzuteilen: git reset HEAD^ macht die Änderungen wieder zu unstaged Changes, während der Commit selbst verschwindet, git add -p erlaubt danach das selektive Stagen einzelner Hunks für mehrere neue, thematisch sauber getrennte Commits. Ein abschließendes git rebase --continue setzt den Rebase mit den restlichen Todo-Einträgen fort.
# Original order (oldest first) before editing the todo list
pick n4o5p6q feat: add loyalty discount to cart
pick k1l2m3n wip
pick h7i8j9k add discount calculation
pick e4f5g6h wip: cart totals
pick a1b2c3d fix typo
pick b2c3d4e refactor: extract price helper
# Edited todo list: reordered, reworded, squashed, fixed up, dropped
pick n4o5p6q feat: add loyalty discount to cart
pick b2c3d4e refactor: extract price helper
reword h7i8j9k add discount calculation
fixup k1l2m3n wip
squash e4f5g6h wip: cart totals
drop a1b2c3d fix typo
# "edit" pauses the rebase at a chosen commit to split it
edit h7i8j9k add discount calculation
# After the rebase stops on that commit:
git reset HEAD^
git add -p
git commit -m "feat: calculate discount percentage"
git add -p
git commit -m "test: cover discount calculation edge cases"
git rebase --continue
7. Konflikte während des Rebase lösen: continue, skip, abort
Anders als bei einem einzelnen Merge kann ein Rebase mit vielen Commits an jedem einzelnen Commit einen eigenen Konflikt erzeugen, weil jeder Commit einzeln gegen die neue Basis angewendet wird. Git stoppt den Prozess beim ersten Konflikt, markiert die betroffenen Dateien mit den bekannten Konfliktmarkern <<<<<<<, ======= und >>>>>>> und meldet den betroffenen Commit-Hash. git status zeigt zuverlässig, welche Dateien betroffen sind, git diff hilft beim Verstehen, welche zwei Versionen genau aufeinandertreffen.
Nach dem manuellen Auflösen der Konfliktmarker werden die betroffenen Dateien mit git add markiert und der Rebase mit git rebase --continue fortgesetzt, Git wendet dann automatisch die restlichen Commits aus der Todo-Liste an. Ist der aktuelle Commit ohnehin überflüssig oder nicht der Mühe wert, überspringt git rebase --skip ihn komplett und verwirft seine Änderungen. Wer merkt, dass die Situation zu unübersichtlich geworden ist, kann jederzeit mit git rebase --abort den kompletten Rebase abbrechen, Git stellt dabei den exakten Zustand des Branches vor dem Rebase-Start wieder her.
# Interactive rebase stops on a commit that no longer applies cleanly
git rebase -i main
# ...
# CONFLICT (content): Merge conflict in src/Model/Discount.php
# error: could not apply h7i8j9k... add discount calculation
git status
# Unmerged paths: both modified: src/Model/Discount.php
# Open the file, resolve the <<<<<<< / ======= / >>>>>>> markers
git diff src/Model/Discount.php
# Stage the resolved file and continue the rebase
git add src/Model/Discount.php
git rebase --continue
# Skip this commit entirely instead (drops its changes)
git rebase --skip
# Or abandon the whole rebase and restore the branch as it was
git rebase --abort
8. Reflog zur Rettung: einen missglückten Rebase rückgängig machen
git rebase --abort funktioniert nur, solange ein Rebase noch aktiv läuft. Ist ein Rebase bereits vollständig durchgelaufen und stellt sich das Ergebnis erst danach als falsch heraus, etwa weil versehentlich der falsche Commit gedroppt oder eine wichtige Änderung beim Squashen verloren wurde, hilft git reflog. Das Reflog protokolliert lokal jede Bewegung von HEAD, inklusive jedes einzelnen Zwischenschritts während eines Interactive Rebase, und bleibt auch dann erhalten, wenn diese Commits in keinem Branch mehr referenziert werden.
git reflog zeigt eine chronologische Liste aller HEAD-Positionen mit Einträgen wie rebase (start) oder rebase (pick), gesucht wird typischerweise der Eintrag unmittelbar vor dem Start des Rebase. Mit git reset --hard HEAD@{n} oder direkt mit dem passenden Commit-Hash lässt sich der Branch exakt auf diesen Zustand zurücksetzen, als hätte der Rebase nie stattgefunden. Wichtig zu wissen: Reflog-Einträge sind standardmäßig 90 Tage lang gültig, bevor sie durch den Garbage Collector entfernt werden können, für die Rettung eines frischen Rebase-Fehlers ist das mehr als ausreichend Zeit.
# Something went wrong mid-rebase, HEAD is in a confusing state
git rebase --abort
# but --abort only works while a rebase is still in progress
# After a rebase already finished with the wrong result, check reflog
git reflog
a1b2c3d HEAD@{0}: rebase (finish): returned to refs/heads/feature/cart
a1b2c3d HEAD@{1}: rebase (pick): feat: add loyalty discount to cart
c4d5e6f HEAD@{2}: rebase (start): checkout main
n4o5p6q HEAD@{3}: commit: feat: add loyalty discount to cart
# n4o5p6q is the tip of the branch just before the rebase started
git reset --hard HEAD@{3}
# Or reference it directly by hash once identified
git reset --hard n4o5p6q
# Reflog entries expire eventually, default 90 days for reachable commits
git config gc.reflogExpire
9. Typische Stolperfallen: riskante versus sichere Praktiken
Der häufigste Fehler beim Interactive Rebase ist, ihn auf einem Branch auszuführen, den bereits andere Entwickler ausgecheckt haben, ohne das vorher abzusprechen. Da jeder rebasete Commit eine neue Prüfsumme erhält, geraten alle bereits existierenden lokalen Kopien in einen divergenten Zustand, der beim nächsten Pull zu verwirrenden Merge-Konflikten führt. Ähnlich riskant ist ein Force-Push mit git push --force nach dem Rebase, weil dieser Befehl kompromisslos überschreibt, selbst wenn zwischenzeitlich neue Commits von Kollegen auf dem Remote-Branch dazugekommen sind. Die folgende Übersicht stellt riskante Gewohnheiten den sicheren Alternativen gegenüber, die sich im Alltag bewährt haben.
| Situation | Riskante Praxis | Sichere Praxis |
|---|---|---|
| Push nach dem Rebase | git push --force | git push --force-with-lease |
| Ziel-Branch | Rebase auf einem geteilten Team-Branch | Rebase nur auf dem eigenen lokalen Feature-Branch |
| Bei Konflikten | Sofort --abort, ohne die Ursache zu verstehen | Konflikt gezielt lösen, dann --continue |
| Aufräumen der Historie | Alle Commits zu einem Mega-Commit squashen | Gezielt nach logischen Einheiten squashen/fixup |
| Vor dem Rebase | Ohne Kenntnis von reflog einfach loslegen | Reflog-Rettungsweg kennen, im Zweifel Branch sichern |
Wer diese Praktiken verinnerlicht, nutzt Interactive Rebase als das, was es sein soll: ein präzises Werkzeug zum Aufräumen der eigenen, noch nicht geteilten Historie, nicht als Mittel, um bereits veröffentlichte Commits nachträglich zu verändern. Im Zweifel gilt die Faustregel, dass Rebase auf lokalen Feature-Branches vor dem ersten Pull Request bedenkenlos ist, während geteilte Branches wie main oder develop niemals rebased werden sollten.
Mironsoft
Git-Workflows, Code-Reviews und CI/CD für Magento- und Hyvä-Teams
Git-Workflow und Code-Qualität professionell aufsetzen?
Wir richten für euer Magento- und Hyvä-Team klare Git-Workflows ein, von Branching-Strategie über Commit-Konventionen bis zur automatisierten Code-Review-Pipeline, damit Pull Requests schneller und sicherer gemerged werden.
Git-Workflow-Audit
Branching-Strategie, Commit-Konventionen und Review-Prozess auf den Prüfstand stellen
Team-Coaching
Interactive Rebase, saubere Commits und Konfliktlösung praxisnah vermitteln
CI/CD-Integration
Automatisierte Checks und Pipelines rund um Pull Requests aufsetzen
10. Zusammenfassung
Interactive Rebase löst ein sehr konkretes Problem: Eine Commit-Historie, die während der Entwicklung zwangsläufig chaotisch wächst, lässt sich vor dem Pull Request in eine klare, nachvollziehbare Abfolge sauberer Änderungen verwandeln. pick behält Commits unverändert, das Umsortieren ändert nur die Reihenfolge, squash und fixup fassen zusammengehörige Änderungen zusammen, reword korrigiert Nachrichten ohne den Code anzufassen, drop entfernt Überflüssiges und edit ermöglicht das Aufteilen zu großer Commits in kleinere, überprüfbare Einheiten.
Konflikte während des Rebase sind kein Grund zur Panik, solange man git status, git rebase --continue, --skip und --abort sicher beherrscht. Und selbst wenn ein Rebase komplett danebengeht, bleibt über git reflog fast immer ein Weg zurück zum ursprünglichen Zustand, solange man nicht zu lange wartet und den passenden Reflog-Eintrag findet. Wer diese Werkzeuge beherrscht, öffnet Pull Requests mit einer Historie, die Reviewer tatsächlich in wenigen Minuten verstehen, statt sich durch ein Dutzend bedeutungsloser wip-Commits zu kämpfen.
Interactive Rebase in der Praxis, das Wichtigste auf einen Blick
pick & Reihenfolge
pick übernimmt Commits unverändert, die Zeilenreihenfolge in der Todo-Liste bestimmt direkt die Reihenfolge im späteren Log.
squash vs. fixup
squash kombiniert Nachrichten manuell, fixup verwirft sie automatisch. Mit --fixup und --autosquash sortiert Git sie sogar selbst ein.
Konflikte lösen
git status zeigt betroffene Dateien, git add markiert Lösungen, git rebase --continue, --skip oder --abort steuern den weiteren Ablauf.
Reflog-Rettung
git reflog protokolliert jede HEAD-Bewegung. git reset --hard auf den passenden Eintrag macht einen missglückten Rebase rückgängig.