Führen Sie git reset --hard auf einem Commit aus, den Ihr Team schon per Pull geholt hat, und Ihre Historie weicht ab sofort von der der anderen ab. Führen Sie git revert auf einen Tippfehler aus, den Sie noch nicht gepusht haben, und Sie haben dem Log einen überflüssigen Commit hinzugefügt. Git hat vier Befehle zum Rückgängigmachen, restore, reset, commit --amend und revert, und sie sind nicht austauschbar. Wählen Sie den falschen, passiert entweder gar nichts, oder es passiert deutlich mehr, als Sie wollten.

Entscheidend ist, wo sich die Änderung gerade befindet: im Working Tree, in der Staging Area, in einem Commit, den nur Sie haben, oder in einem Commit, den Sie schon gepusht haben.

Wo sich eine Änderung in Git befindet: Working Tree, Index, History

Jeder Befehl zum Rückgängigmachen wirkt auf eine oder mehrere von drei Ebenen:

  • Working Tree: die Dateien auf der Festplatte, so wie Sie sie gerade bearbeiten.
  • Index (die Staging Area): der Schnappschuss, den Sie mit git add aufgebaut haben und der auf den nächsten Commit wartet.
  • History: die bereits aufgezeichneten Commits, erreichbar von HEAD aus.

Eine Änderung beginnt im Working Tree, wandert mit git add in den Index und mit git commit in die History. Sie rückgängig zu machen heißt, sie durch dieselben Ebenen zurückzuschieben. Je weiter sie schon gewandert ist, desto gezielter muss der Befehl sein.

1
2
working tree  --add-->  index  --commit-->  history
   restore              restore --staged     reset / revert / amend

Wie Sie nicht gestagte Änderungen mit git restore verwerfen

Sie haben config.py bearbeitet, dabei etwas kaputt gemacht und wollen die Version von Git zurück:

1
git restore config.py

Das überschreibt die Datei in Ihrem Working Tree mit der Version im Index, die mit HEAD identisch ist, solange Sie nichts gestaged haben. Die Bearbeitung ist weg. Sie wird nicht gestashed, und kein Git-Befehl holt sie zurück. Um alle nicht gestagten Änderungen auf einmal zu verwerfen:

1
git restore .

git restore ersetzt das ältere git checkout -- <Datei>. Beide funktionieren noch, aber restore gibt es, damit “gib mir diese Datei zurück” nicht mehr derselbe Befehl ist wie “wechsle den Branch”.

Wie Sie eine Datei aus dem Index entfernen, ohne die Bearbeitung zu verlieren

Sie haben git add auf drei Dateien ausgeführt und dann bemerkt, dass eine davon nicht in diesen Commit gehört. Die Bearbeitung selbst ist in Ordnung:

1
git restore --staged migration.sql

migration.sql verlässt den Index und wird wieder zu “modifiziert, nicht gestaged”. Die Datei auf der Festplatte bleibt unberührt. Das entspricht dem älteren git reset HEAD -- migration.sql, und das ist die eine Stelle, an der sich reset und restore überschneiden: reset mit einem Pathspec fasst nur den Index an, und genau das tut restore --staged auch.

Wie Sie einen Commit mit git reset rückgängig machen: soft, mixed und hard

git restore kommt an einen Commit nicht heran. Dafür brauchen Sie git reset, und das Flag bestimmt, wie viel davon rückgängig gemacht wird:

FlagWorking TreeIndexHistory (HEAD)
--softunverändertunverändertverschoben
--mixed (Standard)unverändertauf das Ziel zurückgesetztverschoben
--hardauf das Ziel zurückgesetztauf das Ziel zurückgesetztverschoben

Alle drei bewegen den Branch-Zeiger. Der Unterschied ist, wie viel von Ihrer Arbeit dabei mit zurückwandert.

--soft spult die Commits zurück, lässt ihre Änderungen aber gestaged. Das macht es zum schnellsten Weg, die letzten drei Commits zu einem zusammenzufassen:

1
2
git reset --soft HEAD~3
git commit -m "Add payment retry logic"

--mixed ist das, was Sie mit einem einfachen git reset HEAD~1 bekommen. Es spult den Commit zurück und legt seine Änderungen als nicht gestagte Bearbeitungen wieder in den Working Tree, sodass Sie sie in kleinere Commits aufteilen oder teilweise verwerfen können.

--hard verwirft den Diff vollständig. Der Working Tree landet so, als hätte der Commit nie stattgefunden:

1
git reset --hard HEAD~1

Es gibt keine Rückfrage. Die Commits, über die Sie zurückgespult sind, finden Sie weiterhin über git reflog (siehe unten). Nicht committete Bearbeitungen in Ihrem Working Tree sind dagegen in diesem Moment endgültig weg, also prüfen Sie vorher git status. Führen Sie --hard außerdem nie auf einem Commit aus, den jemand anderes schon per Pull geholt hat: Den Branch-Zeiger auf einem geteilten Branch zurückzubewegen, sorgt dafür, dass Ihre Historie und seine nicht mehr übereinstimmen – dasselbe Problem, das hinter der goldenen Regel des Rebasing steht.

Wie Sie den letzten Commit mit git commit –amend korrigieren

Steckt der Fehler im letzten Commit, ist ein Reset übertrieben. --amend ersetzt den Commit an der Spitze, statt daran vorbei zurückzuspulen:

1
git commit --amend -m "Fix payment retry logic"

Stagen Sie zuerst weitere Änderungen, landen sie im selben Commit, mit demselben Parent:

1
2
git add retry.py
git commit --amend --no-edit

Der amendete Commit ist ein neues Objekt mit einem neuen Hash, genau wie ein rebasierter. Deshalb gilt dieselbe Regel wie beim Rebase: amenden Sie nur, was Sie noch nicht gepusht haben. Amenden Sie einen Commit, der schon auf einem geteilten Branch liegt, muss jeder, der ihn per Pull geholt hat, zwei Commits in Einklang bringen, die beide behaupten, dieselbe Änderung zu sein.

Wie Sie einen bereits gepushten Commit mit git revert rückgängig machen

Sobald ein Commit auf einem geteilten Branch liegt, sind reset und --amend keine Option mehr. Historie umzuschreiben, die andere schon per Fetch geholt haben, ist genau der Weg zu divergierten Branches und doppelten Commits. git revert geht den anderen Weg: Es lässt den fehlerhaften Commit, wo er ist, und fügt einen neuen hinzu, der das Gegenteil tut.

1
git revert a1b2c3d

Git öffnet einen Editor für die Nachricht des Revert-Commits und zeichnet ihn dann auf:

1
2
3
Revert "Add aggressive cache invalidation"

This reverts commit a1b2c3d4e5f6789a1b2c3d4e5f6789a1b2c3d4.

Die History enthält jetzt sowohl den ursprünglichen Commit als auch sein Gegenteil. Nichts wurde umgeschrieben, also holt ein einfaches git pull auf jedem anderen Klon die Korrektur. Um einen ganzen Bereich als einen einzigen Commit zu reverten:

1
2
git revert --no-commit HEAD~3..HEAD
git commit -m "Revert the last three deploy changes"

--no-commit (-n) wendet das Gegenteil jedes Commits auf Working Tree und Index an, ohne zu committen, sodass drei Reverts als ein Commit landen statt als drei. Kollidiert ein Revert mit Änderungen, die seitdem gemacht wurden, stoppt Git genauso wie mitten in einem Merge: Lösen Sie die Konfliktmarkierungen auf, stagen Sie die Datei mit git add, und führen Sie dann git revert --continue aus.

“Einen Push rückgängig machen” heißt also: den Commit reverten, dann den Revert pushen. Pushen Sie niemals umgeschriebene Historie per Force auf einen Branch, von dem andere per Pull holen.

Wie Sie sich mit git reflog von einem missglückten Reset erholen

Nach einem reset --hard verschwinden die Commits aus git log. Sie existieren trotzdem noch. git reflog listet jede Position auf, die HEAD auf Ihrer Maschine schon hatte, einschließlich der unmittelbar vor dem Reset:

1
git reflog
1
2
f3a9c21 HEAD@{0}: reset: moving to HEAD~1
7d4e1b8 HEAD@{1}: commit: Add payment retry logic

git reset --hard HEAD@{1} bringt Sie zurück auf 7d4e1b8. Wie lange Reflog-Einträge leben und warum das Reflog rein lokal ist, steht im Abschnitt zur Wiederherstellung von Git Rebase vs Merge. Es funktioniert gleich, egal welcher Befehl HEAD bewegt hat.

Das Reflog bringt Sie zurück an den Punkt, an dem Sie waren. Es sagt Ihnen nicht, welcher ältere Commit der letzte gute war. Dafür grenzt git bisect Hunderte Commits auf den einen ein, der den Bug eingeführt hat.

Git reset vs revert vs restore: was Sie wann nutzen

Die Änderung ist…Befehl
Bearbeitet, nicht gestaged, und soll weggit restore <Datei>
Gestaged, und Sie wollen sie aus dem Index nehmen (Bearbeitung bleibt)git restore --staged <Datei>
Committet, nicht gepusht, und ist der letzte Commitgit commit --amend
Committet, nicht gepusht, und Sie wollen weiter zurückspulengit reset --soft / --mixed / --hard HEAD~n
Committet und schon gepusht oder geteiltgit revert <Commit>
Verloren nach einem reset --hard, den Sie bereuengit reflog + git reset --hard HEAD@{n}

Für alles, was schon committet ist, entscheidet eine einzige Frage, welche Zeile zutrifft: Hat diesen Commit schon jemand anderes gesehen? Wenn nicht, reset oder amend, und weiter geht’s. Wenn doch, revert. Das kostet einen zusätzlichen Commit im Log, dafür muss niemand aus Ihrem Team eine Historie entwirren, die nicht mehr zu seiner eigenen passt.

Verwandte Artikel