Git: Wie macht man die letzten Commits rückgängig?

Den letzten Commit rückgängig zu machen braucht jeder in der ersten Woche. Welchen Befehl du willst, hängt davon ab, ob du die Änderungen behalten möchtest — und davon, ob der Commit schon gepusht ist.

Die drei Resets und was sie unterscheidet

Alle drei entfernen den Commit. Der Unterschied liegt darin, was mit der Arbeit passiert, die darin steckte:

user@pc:~$ git reset --soft  HEAD~1     # Aenderungen bleiben, gestaged
user@pc:~$ git reset         HEAD~1     # Aenderungen bleiben, ungestaged (--mixed, Standard)
user@pc:~$ git reset --hard  HEAD~1     # Aenderungen weg

Am deutlichsten sieht man es an git status --short, das zwei Spalten verwendet — die erste für den Index, die zweite für das Arbeitsverzeichnis:

--soft  : status = "M  file.txt"   file = one two three
--mixed : status = " M file.txt"   file = one two three
--hard  : status = ""              file = one two

Ein M in der ersten Spalte heißt gestaged und bereit für einen neuen Commit. Ein M in der zweiten heißt, die Änderung steckt noch in deiner Datei, du müsstest sie aber erneut mit git add vormerken. Gar nichts heißt: die Arbeit ist weg.

Also: --soft, wenn du den Commit gleich neu machen willst, der Standard, wenn du umsortieren willst was hineinkommt, und --hard nur dann, wenn die Arbeit wirklich weg soll.

–hard nimmt nicht committete Arbeit mit

Das ist der Teil, der echte Arbeit kostet, und man übersieht ihn leicht, weil der Befehl ja einen Commit benennt:

before: status --short :  M file.txt
after : status --short : ''
after : file           : one two

Diese Änderung war nie committet. reset --hard verschiebt nicht nur den Branch-Zeiger — es überschreibt auch dein Arbeitsverzeichnis mit dem Zielcommit, und alles was du nicht committet hattest wird dabei schlicht überschrieben.

Alles andere auf dieser Seite lässt sich zurückholen. Das hier nicht.

Der Commit selbst ist zu retten

Wenn du einen Commit per --hard weggeworfen hast, ist er noch da. Git hat nur den Branch-Zeiger bewegt, gelöscht wurde nichts:

user@pc:~$ git reflog
a4ae70b HEAD@{0} reset: moving to HEAD~1
3f09ed6 HEAD@{1} commit: third commit
a4ae70b HEAD@{2} commit: second commit

user@pc:~$ git reset --hard 3f09ed6
3f09ed6 third commit  a4ae70b second commit  ea0365c first commit

git reflog protokolliert jede Position, die HEAD hatte — auch die, auf die kein Branch mehr zeigt. Das ist das Erste, wonach man nach jedem “ich glaube das ist gerade weg” greift.

Unreferenzierte Commits werden irgendwann aufgeräumt, eine Sicherung ist das also nicht. Die Standardfrist beträgt aber 90 Tage, und das reicht für die Schrecksekunde.

Ist der Commit schon gepusht, nimm revert

reset schreibt die Historie um. Solange die Commits nur auf deinem Rechner liegen ist das in Ordnung, und sobald sie jemand anderes hat, ist es unhöflich — er bekommt die alten Commits beim nächsten Push zurück.

git revert erzeugt einen neuen Commit, der einen alten rückgängig macht:

user@pc:~$ git revert HEAD
79a8358 Revert "third commit"   3f09ed6 third commit   a4ae70b second commit
file : one two

Der Inhalt ist zurückgenommen und die Historie läuft vorwärts weiter. Nichts, was jemand anderes bereits hat, wird ungültig.

Die Faustregel: nicht gepusht: reset. Gepusht: revert.

Wenn nur die Nachricht falsch war

user@pc:~$ git commit --amend -m "third commit, better message"
20b765b third commit, better message   a4ae70b second commit

Beachte, dass sich der Hash geändert hat — aus 3f09ed6 wurde 20b765b. --amend bearbeitet den Commit nicht, sondern baut einen Ersatz und setzt den Branch darauf. Es zählt damit ebenfalls als Umschreiben der Historie, mit demselben Vorbehalt bei bereits gepushten Commits.

--amend ohne -m kannst du außerdem verwenden, um vergessene Dateien in den letzten Commit nachzureichen: erst git add, dann amend.

Mehr als ein Commit

HEAD~1 heißt “einen Commit zurück”, es ändert sich also nur die Zahl:

user@pc:~$ git reset --soft HEAD~2
206acf5 first commit
status --short : M  file.txt

Beide Commits sind weg und ihr gesamter Inhalt ist gestaged, bereit als ein einziger Commit.

Und weil wir gerade dabei sind, der Unterschied zwischen den beiden Suffixen:

HEAD   : 45584d7
HEAD~1 : 369ca07
HEAD^  : 369ca07

Bei einer linearen Historie identisch. ~ geht n Commits zurück und folgt dabei immer dem ersten Elternteil, ^ wählt aus, welchen Elternteil eines Merges du meinst. Der Unterschied fällt erst an einem Merge-Commit auf, wo HEAD^2 der hineingemergte Branch ist.

Hinweis zu Netcup (Werbung)

Der deutsche Hoster Netcup bietet unter anderem günstige und zugleich leistungsstarke Webhosting Pakete, KVM-basierte Root Server und dezidierte Server an. Mit unseren Gutscheincodes kannst du noch mehr Geld sparen (6€ bei deiner ersten Bestellung, 30% Rabatt auf alle KVM-basierten Root Server, ...).