Änderungen zurücknehmen

Mit Git 2.23 kam git restore zum Rückgängigmachen von Datei-Änderungen hinzu. Vorher übernahm git reset diese Aufgabe, das aber auch noch andere Aufgaben hat:

$ git restore

ändert Dateien im Arbeitsverzeichnis in einen Zustand, der Git zuvor bekannt war. Standardmäßig checkt Git HEAD den letzten Commit des aktuellen Zweigs aus.

Bemerkung

In Git < 2.23 steht euch git restore noch nicht zur Verfügung. In diesem Fall müsst ihr noch git checkout verwenden:

$ git checkout PATH/TO/FILE

$ git restore [-S|--staged] PATH/TO/FILE

nimmt das Hinzufügen von Dateien zurück. Die Änderungen bleiben in eurem Arbeitsbereich erhalten, so dass ihr sie bei Bedarf ändern und wieder hinzufügen könnt.

Der Befehl entspricht git reset PATH/TO/PATH.

$ git restore [-SW] PATH/TO/FILE

nimmt das Hinzufügen und Änderungen im Arbeitsbereich zurück.

$ git restore [-s|--source] BRANCH PATH/TO/FILE

setzt eine Änderung auf die Version im Zweig BRANCH zurück.

$ git restore [-s|--source] @~ PATH/TO/FILE

setzt eine Änderung auf den vorherigen Commit zurück.

$ git restore [-p|--patch]

lässt euch die rückgängig zu machenden Änderungen einzeln auswählen.

$ git reset [--hard | --mixed | --soft | --keep] TARGET_REFERENCE

setzt die Historie auf einen früheren Commit zurück.

Warnung

Das Risiko bei reset ist, dass Arbeit verloren gehen kann. Zwar werden Commits nicht unmittelbar gelöscht, allerdings können sie verwaisen, so dass es keinen direkten Pfad mehr zu ihnen gibt. Sie müssen dann zeitnah mit reflog gefunden und wiederhergestellt werden da Git üblicherweise alle verwaisten Commits nach 30 Tagen löscht.

$ git reset @~
@~

nimmt den letzten Commit zurück wobei dessen Änderungen nun wieder in den Bühnenbereich übernommen werden.

Sind Änderungen im Bühnenbereich vorhanden, so werden diese in den Arbeitsbereich verschoben, z.B.:

$ echo 'My first repo' > README.rst
$ git add README.rst
$ git status
Auf Branch main
Zum Commit vorgemerkte Änderungen:
  (benutzen Sie "git rm --cached <Datei>..." zum Entfernen aus der Staging-Area)
    neue Datei:     README.rst
$ git reset
$ git status
Auf Branch main
Unversionierte Dateien:
  (benutzen Sie "git add <Datei>...", um die Änderungen zum Commit vorzumerken)
    README.rst
@~3

nimmt den letzten drei Commits zurück.

'@{u}'

nimmt die entfernte Version (Upstream) des aktuellen Zweigs.

--hard

verwirft die Änderungen auch im Staging- und Arbeitsbereich.

$ git status
Auf Branch main
Zum Commit vorgemerkte Änderungen:
  (benutzen Sie "git rm --cached <Datei>..." zum Entfernen aus der Staging-Area)
    neue Datei:     README.rst
$ git reset --hard
$ git status
Auf Branch main
nichts zu committen (erstellen/kopieren Sie Dateien und benutzen
Sie "git add" zum Versionieren)
--mixed

setzt den Bühnen-, aber nicht den Arbeitsbereich zurück, d.h., die geänderten Dateien bleiben erhalten, werden aber nicht für den Commit markiert.

Tipp

Ich bevorzuge meist --soft gegenüber --mixed: es hält die rückgängig gemachten Änderungen getrennt, so dass alle zusätzlichen Änderungen explizit sind. Dies ist Besonders nützlich, wenn ihr im Bühnen- und Arbeitsbereich Änderungen an der gleichen Datei habt.

--soft

nimmt den oder die Commits zurück, lässt jedoch Bühnen- und Arbeitsbereich unverändert.

--keep

setzt den Bühnenbereich zurück und aktualisiert die Dateien im Arbeitsbereich, die sich zwischen COMMIT und HEAD unterscheiden, behält aber diejenigen bei, die sich zwischen Bühnen- und Arbeitsbereich unterscheiden, d.h., die Änderungen haben, aber noch nicht hinzugefügt wurden. Wenn eine Datei, die sich zwischen COMMIT und Bühnenbereich unterscheidet, nicht hinzugefügte Änderungen aufweist, wird reset abgebrochen.

Ihr könnt euch dann mit euren nicht im Commit enthaltenen Änderungen befassen, sie vielleicht mit git restore rückgängig macht oder mit git stash versteckt, bevor ihr es erneut versucht.

Tipp

Viele andere Anleitungen empfehlen --hard für diese Aufgabe, wahrscheinlich weil es diesen Modus schon länger gibt. Dieser Modus ist jedoch riskanter, da er die nicht im Commit enthaltenen Änderungen unwiderruflich verwirft ohne Fragen zu stellen. Ich verwende jedoch --keep und wenn ich alle nicht zum Commit vorgesehenen Änderungen vor dem reset verwerfen will, verwende ich git restore -SW.

$ git revert COMMIT_SHA

erstellt einen neuen Commit und nimmt die Änderungen des angegebenen Commits zurück, sodass die Änderungen invertiert werden.

$ git fetch --prune REMOTE

Remote-Refs werden entfernt wenn sie im Remote-Repository entfernt wurden.

$ git commit --amend

aktualisiert und ersetzt den letzten Commit durch einen neuen Commit, der alle bereitgestellten Änderungen mit dem Inhalt des vorherigen Commits kombiniert. Wenn nichts bereitgestellt ist, wird nur die vorherige Commit-Nachricht neu geschrieben.

Referenz für häufige Befehle zum Zurücksetzen

Alle lokalen Änderungen an einem Zweig rückgängig machen

$ git reset --keep '@{u}'

Alle Commits im aktuellen Zweig rückgängig machen

git merge-base wählt den Commit aus, bei dem sich zwei Zweige getrennt haben. Übergebt @ und main, um den Commit auszuwählen, bei dem der aktuelle Zweig von main abgezweigt ist. Setzt ihn zurück, um alle Commits auf dem lokalen Zweig rückgängig zu machen mit:

$ git reset --soft $(git merge-base @ main)

Alle Änderungen im aktuellen Zweig rückgängig machen

$ git reset --keep main

Commit im falschen Zweig zurücknehmen

Wenn ihr versehentlich einen Commit in einem bestehenden Zweig gemacht habt, anstatt zunächst einen neuen Zweig zu erstellen, könnt ihr das in den folgenden drei Schritten ändern:

  1. Erstellt einen neuen Zweig mit $ git branch NEW_BRANCH

  2. Nehmt den letzten Commit in eurem aktiven Branch zurück mit $ git reset --keep @~

  3. Übernehmt die Änderungen in den neuen Zweig mit $ git switch NEW_BRANCH

Wiederherstellen eines gelöschten Zweigs

Angenommen, ihr habt versehentlich einen nicht zusammengefügten Zweig gelöscht, so könnt ihr den Zweig mit dem zugehörigen SHA neu erstellen:

$ git branch -D new-feature
Branch new-feature entfernt (war d53e431).

Die Ausgabe enthält den SHA-Commit, auf den der Zweig zeigte. Ihr könnt den Zweig mit diesem SHA neu erstellen:

$ git branch new-feature d53e431

Was aber, wenn ihr die Verzweigung gelöscht haben und der entsprechende Terminalverlauf verloren gegangen ist? Um die SHA wiederzufinden, könnt ihr die reflog-Ausgabe an grep übergeben:

$ git reflog | grep -A 1 new-feature
12bc4d4 HEAD@{0}: checkout: moving from new-feature to main
d53e431 HEAD@{1}: commit: Add new feature
12bc4d4 HEAD@{2}: checkout: moving from main to new-feature
12bc4d4 HEAD@{3}: merge my-feature: Fast-forward

-A 1 zeigt nach nach jedem Treffer eine zusätzliche Zeile an. Die Ausgabe zeigt mehrere reflog-Einträge, die sich auf den Zweig beziehen. Der erste Eintrag zeigt einen Wechsel von new-feature zu main, mit dem Commit-SHA auf main. Der Eintrag davor ist die letzte Änderung an new-feature mit dem SHA zum Wiederherstellen:

$ git branch triceratops-enclosure 43f66f9

Standardmäßig könnt ihr einen solchen Zweig innerhalb von 30 Tagen nach dem Löschen des Zweigs speichern, da gc.reflogExpireUnreachable üblicherweise so eingestellt ist.

Rückgängigmachen einer Commit-Änderung

Kehren wir zum einleitenden Beispiel zurück. Stellt euch vor, ihr hättet einen Commit gemacht und ihn später geändert. Dann stellt ihr fest, dass die Änderung rückgängig gemacht werden sollte. Wie könnt ihr vorgehen? Wenn ihr die ursprüngliche Git-Commit-Ausgabe noch in Ihrem Terminalverlauf seht, könnt ihr die SHA von dort abrufen und die Änderung rückgängig machen. Aber wenn das nicht mehr möglich ist, ist es Zeit für das Reflog. Prüft das Reflog für den Zweig:

$ git reflog my-feature-branch
12bc4d4 (HEAD -> main, my-feature-branch) my-feature-branch@{0}: commit (amend): Add my feature and more
982d93a my-feature-branch@{1}: commit: Add my feature
900844a my-feature-branch@{2}: branch: Created from HEAD

Der erste Eintrag, commit (amend), zeigt die Erstellung des geänderten Commits an. Der zweite Eintrag zeigt den ursprünglichen Commit, zu der wir nun wiederum mit einem Hard Reset zurückkehren wollen:

$ git reset --hard 982d93a

Vielleicht möchtet ihr dann den Inhalt des geänderten Commits wiederherstellen, um ihn zu korrigieren und erneut zu ändern. Tut dies mit git restore aus dem geänderten Commit SHA, der oben in der vorherigen reflog-Ausgabe steht:

$ git restore -s 12bc4d4

Rückgängig machen eines fehlerhaften Rebase

Stellt euch vor, ihr arbeitet an einem Zweig new-feature mit drei Commits, wovon ihr den mittleren mit Git rebase rückgängig machen wollt:

$ git rebase -i main
 pick d53e431 Add new feature
-pick 329271a More performant implementation for the new feature
-pick 1d6c477 Add API docs

Versehentlich habt ihr jetzt jedoch auch den letzten Commit gelöscht. Falls ihr den SHA-Wert nicht mehr im Terminalverlauf sehen könnt, könnt ihr wieder die reflog-Ausgabe an grep übergeben:

$ git reflog| grep 'API docs'
1d6c477 HEAD@{2}: commit: Add API docs

Mit dieser SHA kann der Commit nun mit Git Cherry-Pick wiederhergestellt werden:

$ git cherry-pick 1d6c477

Entfernen einer Datei aus der Historie

Eine Datei kann vollständig aus Git-Historie des aktuellen Branches entfernt werden. Das ist nötig, wenn ihr beispielsweise aus Versehen Passwörter oder eine sehr große Datei zum Repository hinzugefügt habt.

$ git filter-repo --invert-paths --path path/somefile
$ git push --no-verify --mirror

Bemerkung

Informiert die Team-Mitglieder, dass sie erneut einen Klon des Repository erstellen sollten.

Entfernen einer Zeichenkette aus der Historie

Das Entfernen funktioniert auch mit einzelnen Wörtern oder Zeichenketten:

$ git filter-repo --message-callback 'return re.sub(b"^git-svn-id:.*\n", b"", message, flags=re.MULTILINE)'