"Ich habe alles verloren" (Spoiler: Wahrscheinlich nicht)
Atmen Sie erst einmal durch. Wenn die Arbeit, die Sie vermissen, jemals committet wurde -- auch nur ein einziges Mal, auch auf dem falschen branch, auch in einem commit, den Sie später "gelöscht" haben -- dann liegt sie mit an Sicherheit grenzender Wahrscheinlichkeit noch genau jetzt in Ihrem Repository. Git ist viel besser darin, Dinge zu behalten, als sie zu verlieren, und die nächsten zehn Minuten Lektüre enden sehr wahrscheinlich damit, dass Ihr Code wieder auf dem Bildschirm steht.
Hier die beruhigende Wahrheit darüber, wie Git intern arbeitet: Git löscht fast nie etwas sofort. Jeder commit, den Sie erstellen, wird als Objekt im .git-Verzeichnis gespeichert und über seinen SHA identifiziert. Wenn Sie git reset --hard ausführen, einen branch löschen oder die Historie mit einem rebase umschreiben, löscht Git diese commit-Objekte nicht. Es verschiebt lediglich Zeiger -- branch-Namen und HEAD -- sodass diese nicht mehr darauf zeigen. Die commits werden "unerreichbar", das heißt, kein branch und kein tag führt mehr zu ihnen, aber die Objekte selbst bleiben wochenlang auf der Festplatte, bevor die garbage collection überhaupt in Erwägung zieht, sie anzufassen.
Das Problem ist also nicht, dass Ihre commits weg sind. Das Problem ist, dass Sie keinen Namen mehr für sie haben. Sie brauchen den SHA. Und Git führt ein Journal, das still und leise jeden SHA aufgezeichnet hat, den Ihr Repository je besucht hat: das reflog.
Was das Reflog wirklich ist
Das reflog (reference log) ist ein lokales, maschinengebundenes Journal darüber, wohin HEAD und jede branch-Spitze im Laufe der Zeit gezeigt haben. Jedes Mal, wenn sich HEAD bewegt -- weil Sie committen, einen checkout machen, mergen, rebasen, resetten oder amenden -- hängt Git eine Zeile an dieses Journal an. Es ist der Flugschreiber Ihres Repositorys.
Um es anzusehen, führen Sie aus:
git reflog
Die Ausgabe sieht so aus:
e4f5a6b HEAD@{0}: reset: moving to HEAD~3
a1b2c3d HEAD@{1}: commit: add payment validation
9f8e7d6 HEAD@{2}: commit: refactor checkout flow
3c4d5e6 HEAD@{3}: checkout: moving from main to feature/payments
Lesen Sie sie von oben nach unten als "Neuestes zuerst". Jede Zeile zeigt den SHA, auf den HEAD gezeigt hat, eine Positionsreferenz wie HEAD@{1}, die Operation, die HEAD bewegt hat, und eine kurze Beschreibung. Die HEAD@{n}-Syntax bedeutet "wo HEAD vor n Bewegungen stand": HEAD@{0} ist Ihre aktuelle Position, HEAD@{1} ist eine Operation zurück, und so weiter. Sie können diese Referenzen in jedem Befehl verwenden, der einen commit akzeptiert -- genau das macht Rettungsaktionen so direkt.
Im obigen Beispiel ist die Geschichte klar: Sie haben zwei commits erstellt (HEAD@{2} und HEAD@{1}), dann hat ein reset HEAD drei commits zurückbewegt. Diese beiden commits sind nicht verloren -- sie liegen direkt dort, bei a1b2c3d.
HEAD ist nicht die einzige Referenz mit einem Log. Jeder branch führt sein eigenes:
git reflog show feature/payments
Das zeigt jede Position, die die Spitze von feature/payments je eingenommen hat -- nützlich, wenn Sie die Historie eines einzelnen branches sehen wollen, ohne das Rauschen jedes checkouts, den Sie je gemacht haben.
Rettungsszenarien, Schritt für Schritt
1. Sie sind mit git reset --hard zu weit gegangen
Sie wollten einen commit rückgängig machen und haben drei ausradiert, oder Sie haben auf eine völlig falsche Stelle resettet. Das reflog hat aufgezeichnet, wo Sie unmittelbar vor dem reset waren, und diese Position ist HEAD@{1}:
git reset --hard HEAD@{1}
Das ist schon die ganze Lösung. Der reset selbst war nur eine weitere HEAD-Bewegung, also stellt das Zurückbewegen von HEAD auf die vorherige Position alles wieder her. Wenn Sie seit dem missglückten reset noch andere Dinge getan haben, führen Sie zuerst git reflog aus, suchen Sie die Zeile direkt vor dem reset-Eintrag und resetten Sie stattdessen auf diesen SHA. Einen breiteren Überblick über Undo-Strategien finden Sie unter wie man einen Git-Commit rückgängig macht.
2. Sie haben einen branch gelöscht
Das Löschen eines branches löscht den Zeiger, nicht die commits. Wenn der branch auf dieser Maschine kürzlich ausgecheckt war oder commits erhalten hat, steht seine Spitze im HEAD-reflog. Finden Sie sie:
git reflog | grep "feature/payments"
Suchen Sie nach dem letzten commit auf diesem branch oder nach dem letzten Eintrag checkout: moving from feature/payments -- der SHA in dieser Zeile ist die branch-Spitze. Dann erstellen Sie den branch neu und lassen ihn darauf zeigen:
git branch feature/payments a1b2c3d
Der branch ist zurück, identisch mit dem Moment seiner Löschung. Als Bonus: Wenn Sie einen branch löschen, gibt Git dessen Spitzen-SHA aus (Deleted branch feature/payments (was a1b2c3d)) -- wenn diese Meldung noch im Scrollback Ihres Terminals steht, können Sie sich die reflog-Suche komplett sparen.
3. Ein rebase ist schiefgegangen
Ein rebase schreibt commits um, und mitten in einem konfliktreichen rebase kann es sich anfühlen, als wäre Ihr branch irreparabel zerstört. Ist er nicht: Der Zustand vor dem rebase steht im reflog. Wenn der rebase noch läuft, ist der sauberste Ausweg git rebase --abort. Wenn er bereits abgeschlossen ist und Sie das Ergebnis nicht ertragen, suchen Sie den Eintrag von vor dem Start:
git reflog
# suchen Sie nach: "rebase (start)" -- der Eintrag direkt DARUNTER
# ist die Position Ihres branches vor dem rebase
git reset --hard HEAD@{5}
(Ersetzen Sie {5} durch die Position, die der Eintrag von vor dem rebase in Ihrem reflog einnimmt.) Ihr branch ist exakt wie zuvor. Wenn rebases Sie regelmäßig in diese Lage bringen, zeigt unser Leitfaden interaktives Rebase ohne Angst, wie sie zur Routine statt zum Risiko werden.
4. Ein commit ist nach --amend verschwunden
git commit --amend bearbeitet keinen commit -- es erstellt einen neuen und bewegt den branch dorthin. Das Original ist unerreichbar, aber intakt, und es liegt direkt nach dem amend bei HEAD@{1}. Zum Inspizieren:
git show HEAD@{1}
Wenn Sie den falschen commit amendet haben oder das Original zurückbrauchen, resetten Sie darauf oder cherry-picken Sie es dorthin, wo es hingehört.
5. Sie haben auf einem detached HEAD committet und dann gewechselt
Sie haben einen bestimmten commit oder tag ausgecheckt, dort im detached-HEAD-Zustand commits erstellt und dann auf einen branch gewechselt -- und Ihre commits schienen sich in Luft aufzulösen. Git warnt Sie beim Wechsel sogar davor und gibt den verwaisten SHA aus. Das reflog hat ihn in jedem Fall:
git reflog
# finden Sie den letzten commit, den Sie vor dem Eintrag
# "checkout: moving from <sha> to <branch>" erstellt haben
git branch rescued-work e4f5a6b
Ihre detached commits leben jetzt auf einem echten branch, und Sie können sie mergen oder rebasen, wohin auch immer sie gehören.
Erst inspizieren, dann wiederherstellen
Bevor Sie irgendetwas auf einen wiedergefundenen SHA zeigen lassen, schauen Sie ihn an. Vergewissern Sie sich, dass es der commit ist, für den Sie ihn halten:
git show a1b2c3d
Das gibt die commit-Nachricht, den Autor, das Datum und den vollständigen Diff aus. Wenn Sie das reflog mit vollständigen commit-Details statt einzeiliger Einträge durchstöbern wollen, nutzen Sie den reflog-Modus des Logs:
git log -g
Und hier die sicherste Gewohnheit von allen: Statt Ihren aktuellen branch auf einen wiedergefundenen commit zu resetten, erstellen Sie zuerst einen temporären branch darauf:
git branch rescue a1b2c3d
Jetzt hat die gerettete Arbeit einen permanenten, erreichbaren Namen. An Ihrem aktuellen branch hat sich nichts geändert, die garbage collection kann die geretteten commits nie mehr anrühren, und Sie können in aller Ruhe inspizieren, diffen und mergen. Ein branch kostet nichts; legen Sie einen an, wann immer Sie nicht 100% sicher sind.
Die Grenzen, ehrlich gesagt
Das reflog ist ein bemerkenswertes Sicherheitsnetz, aber es hat echte Grenzen, und Sie sollten sie kennen, bevor Sie sie brauchen:
- Es ist strikt lokal. Das reflog lebt auf Ihrer Maschine und wird nie gepusht, gepullt oder geklont. Ein frischer clone startet mit einem leeren reflog. Wenn Sie commits auf Ihrem Laptop verloren haben, zählt das reflog auf Ihrem Laptop -- der clone eines Kollegen kann nicht helfen, und die Kopie auf dem Server auch nicht.
- Einträge laufen ab. Standardmäßig verfallen reflog-Einträge für commits, die noch von einem branch aus erreichbar sind, nach 90 Tagen, und Einträge für unerreichbare commits nach 30 Tagen. Nach Ablauf kann die garbage collection die unerreichbaren Objekte tatsächlich löschen. In der Praxis ist das reichlich Zeit -- aber es bedeutet: Das reflog rettet den Fehler vom letzten Monat, nicht den vom letzten Jahr.
- Es kann nicht retten, was nie committet wurde. Uncommittete Änderungen, die durch
git reset --hardodergit checkout -- <file>zerstört wurden, waren nie Git-Objekte, also hat kein Journal sie aufgezeichnet. Die eine Teilausnahme: Dateien, die zumindest mitgit addgestaged wurden, existieren als blob-Objekte, undgit fsck --lost-foundkann diese hängenden blobs nach.git/lost-found/ausgraben -- Inhalt ohne Dateinamen oder Historie, aber besser als nichts. Das ist ein letzter Ausweg, kein Workflow. - Gelöschte stashes haben ihre eigene Notluke. Ein stash, den Sie gedroppt oder geleert haben, steht nicht im HEAD-reflog, aber die stash-commits überleben oft als hängende Objekte.
git fsck --unreachable | grep commitplusgit showauf den Kandidaten kann sie finden. Wenn Sie viel mit stash arbeiten, zeigt unser Leitfaden zu git stash die sichereren Workflows.
Vorbeugung: Machen Sie künftige Paniken langweilig
Jedes der obigen Szenarien hatte eine Voraussetzung: Die Arbeit war committet. Daraus folgt der einzige Vorbeugungsratschlag, der zählt: früh committen und oft committen. Kleine, unpolierte Work-in-Progress-commits kosten nichts -- Sie können sie später squashen, umbenennen oder umsortieren. In dem Moment, in dem etwas committet ist, liegt es in der Objektdatenbank und das reflog hält ihm den Rücken frei. In dem Moment, in dem nicht, verlassen Sie sich auf Glück.
Für schnelle Kontextwechsel, bei denen sich ein commit zu schwergewichtig anfühlt, nutzen Sie stash, statt uncommittete Änderungen über checkouts hinweg zu jonglieren -- ein stash ist ebenfalls ein echtes Objekt, das Git wiederfinden kann.
Wo GitSquid hilft (und wo die CLI gewinnt)
Ehrliche Antwort: Das reflog ist einer der Orte, an denen die Kommandozeile das richtige Werkzeug ist. GitSquid hat keinen reflog-Browser, und wenn Sie git reflog brauchen, sollten Sie ein Terminal öffnen und es benutzen -- die Befehle in diesem Artikel sind der Rettungsweg.
Was GitSquid stattdessen tut: Es macht den umgebenden Workflow sicherer, sodass Sie das reflog seltener brauchen. Die Datei-Timeline (Rechtsklick auf eine beliebige Datei und Show history wählen) liefert eine visuelle Historie pro Datei, die die Frage "wo ist dieser Code hin?" oft ganz ohne Rettungsaktion beantwortet. Destruktive Operationen wie reset und branch-Löschung laufen über explizite Kontextmenüs im commit-Graphen, sodass Sie genau sehen, was Sie gleich tun, bevor Sie es tun. Und wenn Sie rekonstruieren, "was gerade passiert ist", zeigt das Befehlsprotokoll (Cmd/Ctrl+Shift+L) jeden Git-Befehl, den GitSquid auf Ihrem Repository ausgeführt hat, mit allen Argumenten und Exit-Codes -- genau die Beweislage, die Sie neben einem reflog haben wollen, wenn Sie einen Vorfall rekonstruieren.
Schnellreferenz
| Situation | Befehl |
|---|---|
| Sehen, wo HEAD gewesen ist | git reflog |
| Sehen, wo eine branch-Spitze gewesen ist | git reflog show <branch> |
Einen missglückten reset --hard rückgängig machen |
git reset --hard HEAD@{1} |
| Einen gelöschten branch wiederherstellen | git branch <name> <sha> |
| Einen abgeschlossenen rebase rückgängig machen | git reset --hard HEAD@{n} (Eintrag von vor dem rebase) |
| Einen wiedergefundenen commit inspizieren | git show <sha> |
| Reflog mit vollen Details durchstöbern | git log -g |
| Eine Rettung sicher parken | git branch rescue <sha> |
| Letzter Ausweg für gestagte, aber nie committete Dateien | git fsck --lost-found |
Das reflog verwandelt "Ich habe alles verloren" in "Ich hatte zehn Minuten lang einen Zeiger verlegt". Committen Sie oft, prüfen Sie das reflog, bevor Sie in Panik geraten, und parken Sie Wiederherstellungen auf einem temporären branch, bevor Sie irgendetwas anderes anfassen. Und wenn Sie einen Git-Client wollen, der destruktive Operationen explizit macht und ein vollständiges Protokoll jedes ausgeführten Befehls führt: Laden Sie GitSquid kostenlos herunter -- es ersetzt Ihr reflog nicht, aber es hilft Ihnen, es seltener zu brauchen.