Atomare Commits schreiben statt Monster-Commits
git
HEAD
Git · Commits · Code Review · Versionskontrolle
Atomare Commits schreiben statt Monster-Commits
git add -p, git rebase -i und die Kunst der sauberen Commit-Historie

Monster-Commits mit gemischten Refactorings, Bugfixes und neuen Features machen Code-Reviews langsam, git bisect nutzlos und einzelne Änderungen unmöglich rückgängig zu machen. Mit git add -p, git commit -p und git rebase -i lässt sich jede Änderung in klar abgegrenzte, atomare Commits aufteilen, die einzeln nachvollziehbar, testbar und im Zweifel gezielt revertierbar bleiben.

12 Min. Lesezeit git add -p · git rebase -i · git bisect Atomare Commits · Code Review · Git-Historie

1. Was einen Commit atomar macht

Ein atomarer Commit enthält genau eine logische Änderung und nichts sonst. Nicht eine Zeile, nicht eine Datei, sondern eine in sich abgeschlossene Idee: ein Bugfix, eine neue Methode, eine Umbenennung. Der Test dafür ist einfach: Lässt sich der Commit in einem Satz ohne das Wort "und" beschreiben? Wenn die Antwort "Fixt den Rabatt-Rechner und räumt nebenbei die Imports auf" lautet, ist der Commit nicht atomar, sondern ein Kompromiss aus zwei unabhängigen Absichten.

Atomar bedeutet außerdem: Der Commit lässt sich isoliert bauen und testen. Nach einem git checkout <commit> sollte das Projekt kompilieren, die Test-Suite laufen und die Anwendung sich in einem konsistenten Zustand befinden, selbst wenn spätere Commits noch fehlen. Das ist der entscheidende Unterschied zu willkürlich kleinen Commits: Atomare Commits sind so klein wie möglich, aber nicht kleiner, als es die Konsistenz des Projekts erlaubt.

2. Das Problem mit Monster-Commits

Ein Monster-Commit vermischt Refactoring, neue Features und Bugfixes in einer einzigen Änderung mit hunderten betroffenen Zeilen über dutzende Dateien. Für den Reviewer ist ein solcher Commit praktisch unlesbar: Welche Zeilenänderung ist beabsichtigt, welche ist Nebenwirkung eines automatischen Refactorings? In der Praxis führt das dazu, dass Reviewer den Diff nur noch überfliegen und pauschal genehmigen, statt jede Änderung wirklich zu verstehen. Genau dort entstehen die Bugs, die später niemand mehr zuordnen kann.

git bisect wird bei Monster-Commits nutzlos: Die Binärsuche landet zwangsläufig bei dem 800-Zeilen-Commit, der drei unabhängige Änderungen enthält, und liefert keine Antwort darauf, welche der drei die Regression verursacht hat. Auch git revert und git cherry-pick scheitern an vermischten Änderungen: Ein Revert reißt ungewollt auch das mit, was eigentlich bleiben sollte, und ein Cherry-Pick auf einen Hotfix-Branch schleppt Refactorings mit, die dort niemand haben will.

3. Gemischte Änderungen im Working Tree erkennen

Monster-Commits entstehen selten absichtlich. Typischerweise beginnt die Arbeit an einem Bugfix, und beim Lesen des Codes fällt eine unschöne Variable, eine fehlende Typisierung oder ein veralteter Kommentar auf, und wird nach dem Motto "während ich schon hier bin" gleich mit erledigt. Nach zwei Stunden Arbeit zeigt git status plötzlich zwölf geänderte Dateien, obwohl der ursprüngliche Bugfix nur zwei betrifft.

Der Moment, in dem man das erkennen sollte, ist vor dem Commit, nicht danach. git diff --stat gibt einen schnellen Überblick über die Anzahl geänderter Dateien und Zeilen. Ein genauerer Blick mit git diff zeigt, ob innerhalb einer einzelnen Datei mehrere unabhängige Hunks stecken. Die Faustregel: Sobald der Commit-Message-Entwurf ein "und" oder eine Aufzählung enthält, steckt mehr als eine logische Änderung im Working Tree, und es lohnt sich, vor dem Commit aufzuteilen statt danach zu reparieren.

4. git add -p / git add -i im Detail: Hunks interaktiv stagen

git add -p (Kurzform für --patch) durchläuft jeden Hunk, also jeden zusammenhängenden Änderungsblock, einzeln und fragt, ob er in die Staging-Area soll. Die wichtigsten Tasten: y stagt den Hunk, n überspringt ihn, s versucht, den Hunk in kleinere Teile zu splitten, e öffnet den Editor für manuelle Anpassung, q beendet die Session sofort. So lässt sich aus einem Working Tree mit gemischten Änderungen gezielt nur der Teil stagen, der zum aktuellen Commit gehört; der Rest bleibt unstaged für den nächsten Commit.

git add -i öffnet stattdessen ein interaktives Menü mit Optionen wie status, update, patch und diff. Der Menüpunkt patch entspricht git add -p, bietet aber zusätzlich die Möglichkeit, vorher gezielt Dateien auszuwählen, für die der Patch-Modus laufen soll. Das ist praktisch, wenn nur zwei von zehn geänderten Dateien den aktuellen Commit betreffen.


$ git add -p
diff --git a/src/Discount/PercentageCalculator.php b/src/Discount/PercentageCalculator.php
@@ -12,7 +12,7 @@ class PercentageCalculator
-        return $price * $percentage;
+        return round($price * $percentage / 100, 2);
Stage this hunk [y,n,q,a,d,s,e,?]? y

diff --git a/src/Discount/PercentageCalculator.php b/src/Discount/PercentageCalculator.php
@@ -30,6 +30,10 @@ class PercentageCalculator
+    // TODO: remove after Q3 migration
+    private function legacyFallback(): float
+    {
+        return 0.0;
+    }
Stage this hunk [y,n,q,a,d,s,e,?]? n

5. Hunks weiter teilen mit s und manuell editieren mit e

Manchmal enthält ein einzelner Hunk immer noch zwei unabhängige Änderungen, etwa weil beide dieselbe oder eine benachbarte Zeile betreffen. Die Taste s versucht dann, den Hunk in kleinere, unabhängig stagbare Teile zu zerlegen. Das gelingt jedoch nicht immer: Git kann einen Hunk nur splitten, wenn zwischen den Änderungen unveränderte Kontextzeilen liegen. Bei zwei Änderungen in direkt aufeinanderfolgenden Zeilen bricht s mit der Meldung ab, dass der Hunk nicht weiter geteilt werden kann.

Für genau diesen Fall gibt es e. Der Editor öffnet den Hunk als Patch-Text, in dem Zeilen mit + oder - markiert sind. Um eine Zeile aus dem zu stagenden Patch auszuschließen, wird eine +-Zeile einfach gelöscht, eine --Zeile hingegen in eine Kontextzeile ohne Präfix umgewandelt. Git prüft anschließend, ob der editierte Patch konsistent anwendbar ist. Bei Syntaxfehlern im editierten Patch bricht der Vorgang ohne Änderung ab, sodass nichts kaputtgehen kann.


# Manual edit mode (e): only keep the null-check, drop the log line
# Original hunk shown by git add -p -e:
@@ -45,6 +45,8 @@ class OrderValidator
     if ($order === null) {
         throw new InvalidArgumentException('Order must not be null');
     }
+    $this->logger->debug('Validating order ' . $order->getId());
+    // Remove the debug line below by deleting the '+' line entirely
+    if (!$order->getItems()) {

# After editing: the debug-log '+' line is deleted,
# only the null-check hunk remains staged

6. git commit -p als Abkürzung: eine saubere Commit-Sequenz bauen

git commit -p kombiniert git add -p und git commit in einem Schritt: Git fragt Hunk für Hunk ab, öffnet danach direkt den Editor für die Commit-Message der gerade zusammengestellten Änderung und committet nur diesen Teil. Der Rest bleibt unstaged und unverändert im Working Tree für den nächsten Durchlauf. Für das Aufteilen eines gemischten Working Trees in mehrere atomare Commits ist das oft schneller als add -p gefolgt von einem separaten commit.

Die Praxis: git commit -p so oft wiederholen, bis git status einen sauberen Working Tree zeigt. Jeder Durchlauf bekommt eine eigene, präzise Commit-Message. Zur Kontrolle danach git log --oneline und git show <commit> für jeden einzelnen Commit. Wenn jeder Commit in einem Satz beschreibbar ist und für sich baut, war die Aufteilung erfolgreich.


# Working tree has three unrelated changes mixed together
$ git status --short
 M src/Discount/PercentageCalculator.php
 M src/Order/OrderValidator.php
 M composer.json

# Build three atomic commits directly, one git commit -p per concern
$ git commit -p
# ... select only the rounding-fix hunk, then write the message:
# "fix: round percentage discount to 2 decimals"

$ git commit -p
# ... select only the null-check hunk:
# "fix: reject null order in OrderValidator"

$ git add composer.json && git commit -m "chore: bump phpunit to 10.5"

$ git log --oneline -3
a1b2c3d chore: bump phpunit to 10.5
e4f5g6h fix: reject null order in OrderValidator
i7j8k9l fix: round percentage discount to 2 decimals

7. Nachträglich aufteilen: git reset -p und git rebase -i

Wenn der Monster-Commit bereits existiert, ist er noch nicht verloren, solange er nicht in einen geteilten Branch gepusht wurde. git rebase -i HEAD~3 öffnet die letzten drei Commits zur Bearbeitung; der betroffene Commit wird von pick auf edit gesetzt. Sobald der Rebase dort anhält, setzt git reset HEAD^ den Commit zurück, ohne die Dateiänderungen zu verwerfen. Alles landet unstaged im Working Tree, bereit für git add -p und mehrere neue, atomare Commits.

git reset -p funktioniert ähnlich, aber direkter: Es entstagt Änderungen hunkweise aus der Staging-Area, ohne den gesamten Commit zurückzusetzen. Das ist nützlich, wenn zu viel auf einmal gestaged wurde, bevor committet wurde. Wichtig bei beiden Verfahren: Das Umschreiben von Historie mit rebase ist nur unproblematisch, solange die betroffenen Commits noch nicht gepusht oder von anderen Team-Mitgliedern ausgecheckt wurden. Nach dem Push nur mit --force-with-lease und expliziter Absprache im Team.


$ git rebase -i HEAD~3
# editor shows:
pick i7j8k9l fix: round percentage discount to 2 decimals
edit e4f5g6h fix: reject null order + bump phpunit in one commit
pick a1b2c3d docs: update changelog

# Rebase stops at the "edit" commit
$ git reset HEAD^
# changes are now unstaged, commit is undone, files intact

$ git add -p
# stage only the null-check hunk, then:
$ git commit -m "fix: reject null order in OrderValidator"

$ git add composer.json
$ git commit -m "chore: bump phpunit to 10.5"

$ git rebase --continue

8. Der Payoff: git bisect, git revert und git cherry-pick

Mit einer atomaren Historie liefert git bisect ein exaktes Ergebnis. Die Binärsuche halbiert bei jedem Schritt den Commit-Bereich, und wenn jeder Commit genau eine logische Änderung enthält, zeigt git bisect am Ende exakt die eine Zeile, die die Regression verursacht hat, und nicht einen 800-Zeilen-Commit, in dem noch drei weitere unabhängige Änderungen gesucht werden müssten. Mit git bisect run lässt sich dieser Prozess sogar vollständig automatisieren, solange ein Testskript den fehlerhaften Zustand zuverlässig erkennt.

Auch git revert und git cherry-pick profitieren direkt: Ein Revert eines atomaren Commits hebt genau eine Änderung sauber auf, ohne dass ungewollt Refactorings oder unabhängige Fixes mit verschwinden. Ein Cherry-Pick auf einen Hotfix- oder Release-Branch lässt sich gezielt auf den einen Fix beschränken, ohne dass Merge-Konflikte durch mitgeschlepptes, dort unerwünschtes Refactoring entstehen. Genau das ist der praktische Wert atomarer Commits: Jede dieser drei Operationen wird von einer Ratearbeit zu einer verlässlichen, mechanischen Operation.


$ git bisect start
$ git bisect bad HEAD
$ git bisect good v2.4.1

# Automate the search with a script that exits non-zero on failure
$ git bisect run vendor/bin/phpunit --filter=DiscountCalculatorTest

# ... bisect narrows down through the atomic commit history ...
i7j8k9l is the first bad commit
commit i7j8k9l
    fix: round percentage discount to 2 decimals

# Revert exactly this one concern, nothing else
$ git revert i7j8k9l

# Or cherry-pick just this fix onto the release branch
$ git cherry-pick i7j8k9l

9. Team-Workflow, Konventionen und Vergleich

Atomare Commits entfalten ihren vollen Wert erst, wenn ein Team sie konsequent einhält. Eine gemeinsame Konvention für Commit-Messages, etwa nach dem Schema von Conventional Commits mit Präfixen wie fix:, feat: oder chore:, macht die Absicht jedes Commits sofort erkennbar, auch ohne den Diff zu öffnen. In Pull Requests lohnt sich außerdem die Entscheidung, ob per Merge-Commit, Rebase oder Squash gemergt wird: Squash-Merges verstecken die atomare Struktur im Ziel-Branch, bewahren sie aber innerhalb des Pull Requests für die Review-Phase, was für viele Teams ein guter Kompromiss ist.

Die folgende Tabelle stellt typische Monster-Commit-Muster den entsprechenden atomaren Alternativen gegenüber.

Situation Monster-Commit-Muster Atomares Muster Vorteil
Refactoring während Bugfix Ein Commit mit Umbenennung und Fix git add -p in zwei Commits trennen Review und Revert bleiben unabhängig
Commit-Message "various fixes and cleanup" Ein Commit pro logischer Änderung Historie ist ohne Diff lesbar
Fehlersuche git bisect landet auf 800-Zeilen-Commit git bisect landet exakt auf der Zeile Keine manuelle Nachsuche nötig
Änderung zurücknehmen git revert reißt ungewollte Änderungen mit git revert hebt genau eine Änderung auf Kein Kollateralschaden bei Reverts
Hotfix auf Release-Branch cherry-pick schleppt Refactoring mit cherry-pick überträgt nur den einen Fix Wenige bis keine Merge-Konflikte

In der Praxis lohnt sich ein kurzer CI-Check, der Commit-Messages gegen das Team-Format validiert, etwa mit commitlint, damit die Konvention nicht nur auf dem Papier existiert, sondern bei jedem Push automatisch durchgesetzt wird.

Mironsoft

Git-Workflows, Code-Review-Prozesse und CI/CD für Magento-Teams

Saubere Commit-Historie im Team etablieren?

Wir helfen Magento- und PHP-Teams dabei, Git-Workflows zu professionalisieren: von Commit-Konventionen über Code-Review-Guidelines bis zur CI/CD-Pipeline, die bei jedem Pull Request automatisch prüft.

Git-Workflow-Audit

Analyse eurer Commit-Historie, Branching-Strategie und Review-Prozesse

Team-Coaching

Praxisnahes Training zu git add -p, rebase -i und atomaren Commits

CI/CD-Integration

Automatisierte Checks für Commit-Konventionen in der Pipeline

10. Zusammenfassung

Atomare Commits statt Monster-Commits lösen ein wiederkehrendes Problem: Code-Reviews, die niemand mehr wirklich liest, git bisect, das keine brauchbare Antwort liefert, und Reverts, die mehr kaputt machen als sie reparieren. git add -p und git add -i teilen den Working Tree hunkweise in klar abgegrenzte Commits auf, git commit -p verkürzt den Weg dorthin. Wo der Monster-Commit schon existiert, holen git reset -p und git rebase -i die Kontrolle nachträglich zurück, solange die Historie noch nicht gepusht wurde.

Der Aufwand für saubere, atomare Commits zahlt sich nicht beim Schreiben aus, sondern bei jedem späteren Blick in die Historie: beim Review, bei der Fehlersuche mit bisect, beim Revert eines einzelnen Fixes und beim Cherry-Pick auf einen Release-Branch. Teams, die Commit-Konventionen und Review-Guidelines konsequent durchsetzen, verwandeln die Git-Historie von einem chronologischen Nebenprodukt in ein durchsuchbares, verlässliches Werkzeug.

Atomare Commits statt Monster-Commits, das Wichtigste auf einen Blick

Ein Commit, ein Zweck

git add -p und git commit -p stagen und committen nur die Hunks, die zusammengehören.

Nachträglich reparierbar

git reset -p und git rebase -i teilen bereits bestehende Commits, solange sie noch lokal sind.

Bisect, Revert, Cherry-Pick

Atomare Commits machen alle drei Operationen zu mechanischen, verlässlichen Schritten statt Ratearbeit.

Team-Konvention

Klare Commit-Message-Formate und CI-Checks setzen atomare Commits im ganzen Team durch.

11. FAQ: Atomare Commits schreiben statt Monster-Commits

1Was ist ein atomarer Commit genau?
Ein atomarer Commit enthält genau eine logische Änderung, die sich in einem Satz ohne "und" beschreiben lässt. Nach dem Checkout sollte das Projekt kompilieren und die Test-Suite laufen.
2Wie groß darf ein atomarer Commit sein?
Keine feste Zeilenzahl. Entscheidend ist eine einzelne, konsistente und testbare Absicht, das kann eine Zeile oder mehrere hundert Zeilen umfassen.
3Wie nutze ich git add -p richtig?
git add -p durchläuft jeden Hunk einzeln und fragt per Tastendruck, ob er gestaged werden soll, so bleibt nur der zum Commit gehörende Teil im Staging.
4Was bedeuten die Tasten y, n, s, e, q?
y stagt den Hunk, n überspringt ihn, s teilt ihn weiter, e öffnet den manuellen Editor, q beendet die Session sofort.
5Was mache ich, wenn s nicht weiter teilen kann?
Taste e öffnet den manuellen Bearbeitungsmodus: unerwünschte '+'-Zeilen löschen, '-'-Zeilen in Kontextzeilen umwandeln, um sie auszuschließen.
6Wie teile ich einen bestehenden Monster-Commit auf?
git rebase -i HEAD~n auf edit setzen, git reset HEAD^ zurücksetzen, mit git add -p mehrere atomare Commits bauen, dann git rebase --continue.
7git reset -p vs. git rebase -i zum Aufteilen?
reset -p entstagt vor dem Commit hunkweise. rebase -i wird gebraucht, wenn der Commit schon existiert und erst rückgängig gemacht werden muss.
8Warum funktioniert git bisect mit Monster-Commits schlecht?
bisect zeigt am Ende einen einzelnen Commit als Ursache. Enthält der mehrere unabhängige Änderungen, muss die eigentliche Ursache darin noch manuell gesucht werden.
9Wie hilft eine atomare Historie bei revert/cherry-pick?
Ein Revert oder Cherry-Pick betrifft dann genau eine Änderung. Bei Monster-Commits reißen beide Operationen ungewollt weitere Änderungen mit oder erzeugen Merge-Konflikte.
10Amenden oder immer neue Commits erstellen?
Solange lokal und ungepusht: amend oder rebase -i sind unproblematisch. Nach dem Push nur mit expliziter Team-Absprache und --force-with-lease.