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, ...).