Warum sagt git stash pop, dass nicht verfolgte Dateien nicht aus dem Stash-Eintrag wiederhergestellt werden konnten?

94

Ich hatte eine Reihe von inszenierten und nicht inszenierten Änderungen und wollte schnell zu einem anderen Zweig wechseln und dann zurückwechseln.

Also habe ich meine Änderungen inszeniert mit:

$ git stash push -a

(Im Nachhinein hätte ich wahrscheinlich --include-untrackedstattdessen verwenden können --all)

Als ich dann ging, um das Versteck zu öffnen, bekam ich eine Menge Fehler in der Art von:

$ git stash pop
foo.txt already exists, no checkout
bar.txt already exists, no checkout
...
Could not restore untracked files from stash entry

Es scheint keine Änderungen aus dem Vorrat wiederherzustellen.

Ich habe es auch versucht, $ git stash branch tempaber das zeigt die gleichen Fehler.

Ich habe einen Weg gefunden, um dies zu umgehen:

$ git stash show -p | git apply

Katastrophe vorerst abgewendet, aber dies wirft einige Fragen auf.

Warum ist dieser Fehler überhaupt aufgetreten und wie vermeide ich ihn beim nächsten Mal?

Steinybot
quelle
29
Ich musste verwenden:git stash show -p | git apply --3
xmedeko
2
Das einzige, was für mich funktioniert hat, ist OBEN KOMMENTAR !!
Mehraj Malik
4
Danke @xmedeko, kann jemand den Unterschied zwischen git stash show -p | erkennen? git apply und git stash show -p | git bewerben --3?
Deepak Mohandas
3
Wenn Sie in Panik geraten, listen Sie einfach Ihre versteckten Dateien mit git stash showund als Rettungsdateien einzeln auf : $ git show stash@{0}:your/stashed/file.txt > your_rescued_file.txt. Dadurch wird die Datei aus dem Stash abgerufen und unter einem anderen Namen gespeichert. Jetzt können Sie sicher mit geeigneten Rettungsmethoden experimentieren (siehe Antworten unten). Wenn die Dinge in die Irre gehen, haben Sie Ihre geretteten Dateien immer als letzte Ressource.
Danijel
Wow, danke @xmedeko! Wieder einmal war Ihr Kommentar das einzige, was funktionierte, und es war so einfach. +1!
pcdev

Antworten:

99

Beachten Sie als zusätzliche Erklärung, dass git stashentweder zwei oder drei Festschreibungen vorgenommen werden. Der Standardwert ist zwei; Sie erhalten drei, wenn Sie die Schreibweise der Optionen --alloder verwenden --include-untracked.

Diese zwei oder drei Commits sind in einer wichtigen Hinsicht besonders: Sie befinden sich in keinem Zweig. Git findet sie über den speziellen Namen stash. 1 Das Wichtigste ist jedoch, was Git Ihnen mit diesen zwei oder drei Commits ermöglicht - und bringt . Um dies zu verstehen, müssen wir uns ansehen, was in diesen Commits enthalten ist.

Was ist in einem Vorrat

Jedes Commit kann ein oder mehrere übergeordnete Commits auflisten. Diese bilden ein Diagramm, in dem spätere Festschreibungen auf frühere verweisen. Der Stash enthält normalerweise zwei Commits, die ich gerne ifür den Inhalt des Index- / Staging-Bereichs und wfür den Inhalt des Arbeitsbaums aufrufe. Denken Sie auch daran, dass jedes Commit einen Snapshot enthält. Bei einem normalen Commit wird dieser Snapshot aus dem Inhalt des Index- / Staging-Bereichs erstellt. Das iCommit ist also in der Tat ein ganz normales Commit! Es ist einfach nicht auf einem Zweig:

...--o--o--o   <-- branch (HEAD)
           |
           i

Wenn Sie einen normalen Stash git stasherstellen, wkopiert der Code jetzt alle nachverfolgten Arbeitsbaumdateien (in einen temporären Hilfsindex). Git setzt das erste übergeordnete wElement dieses Commits auf das HEADCommit und das zweite übergeordnete Element auf das Commit i. Zuletzt wird stashauf dieses wCommit hingewiesen:

...--o--o--o   <-- branch (HEAD)
           |\
           i-w   <-- stash

Wenn Sie --include-untrackedoder hinzufügen --all, macht Git uzwischen dem Erstellen von iund ein zusätzliches Commit w. Der Snapshot-Inhalt für usind Dateien, die nicht verfolgt, aber nicht ignoriert werden ( --include-untracked), oder Dateien, die nicht verfolgt werden, selbst wenn sie ignoriert werden ( --all). Dieses zusätzliche uCommit hat kein übergeordnetes Element. Wenn es dann erstellt git stashwird w, wird wdas dritte übergeordnete Element für dieses uCommit festgelegt, sodass Sie Folgendes erhalten:

...--o--o--o   <-- branch (HEAD)
           |\
           i-w   <-- stash
            /
           u

Git auch an dieser Stelle, entfernt alle Arbeit Baum - Dateien , die in der Liquidation ubegehen (mit , git cleandas zu tun).

Ein Versteck wiederherstellen

Wenn Sie einen Stash wiederherstellen , haben Sie die Möglichkeit --index, ihn zu verwenden oder nicht zu verwenden. Dies teilt git stash apply(oder einem der intern verwendeten Befehle apply, z. B. pop) mit, dass das Commit verwendet werden soll ,i um zu versuchen, Ihren aktuellen Index zu ändern. Diese Änderung erfolgt mit:

git diff <hash-of-i> <hash-of-i's-parent> | git apply --index

(mehr oder weniger; es gibt eine Reihe von Details, die der Grundidee hier im Wege stehen).

Wenn Sie dies weglassen --index, wird git stash applydas Festschreiben vollständig ignoriert i.

Wenn der Stash nur zwei Commits hat, git stash applykann das wCommit jetzt angewendet werden . Es tut dies durch Aufrufe von git merge2 (ohne dass es das Ergebnis als eine normale merge zu begehen oder zu behandeln), auf das die ursprünglichen commit Verwendung der Vorrat hergestellt wurde ( is Elternteil, und w"erste Mutter s) als merge Base, wwie die --theirsCommit, und Ihr aktuelles (HEAD) Commit als Ziel der Zusammenführung. Wenn die Zusammenführung erfolgreich ist, ist alles in Ordnung - zumindest glaubt Git das - und das git stash applyselbst ist erfolgreich. Wenn Sie verwendet git stash popdie Stash anwenden, wird der Code jetzt fällt das Versteck. 3 Wenn die Zusammenführung fehlschlägt, erklärt Git, dass die Anwendung fehlgeschlagen ist. Wenn du benutzt hastgit stash popDer Code behält den Stash bei und liefert den gleichen Fehlerstatus wie für git stash apply.

Aber wenn Sie dieses dritte Commit haben - wenn udas von Ihnen angewendete Stash ein Commit enthält - ändern sich die Dinge! Es gibt keine Möglichkeit, so zu tun, als ob das uCommit nicht existiert. 4 Git besteht darauf, alle Dateien aus diesem uCommit in den aktuellen Arbeitsbaum zu extrahieren . Dies bedeutet, dass die Dateien entweder überhaupt nicht vorhanden sein dürfen oder denselben Inhalt wie beim uFestschreiben haben dürfen.

Um dies zu erreichen, können Sie sich git cleanselbst verwenden - aber denken Sie daran, dass nicht verfolgte Dateien (ignoriert oder nicht) keine andere Existenz in einem Git-Repository haben. Stellen Sie also sicher, dass diese Dateien alle zerstört werden können! Sie können auch ein temporäres Verzeichnis erstellen und die Dateien zur sicheren Aufbewahrung dorthin verschieben - oder sogar ein anderes git stash save -uoder git stash save -a, da diese git cleanfür Sie ausgeführt werden. Aber das lässt Sie nur mit einem anderen uStash zurück, mit dem Sie sich später befassen müssen.


1 Das ist in der Tat refs/stash. Dies ist wichtig, wenn Sie einen Zweig mit dem Namen erstellen stash: Der vollständige Name des Zweigs lautet refs/heads/stash, sodass diese nicht in Konflikt stehen. Aber tu das nicht: Git macht es nichts aus, aber du wirst dich verwirren. :-)

2 Der git stashCode wird git merge-recursivehier tatsächlich direkt verwendet . Dies ist aus mehreren Gründen erforderlich und hat auch den Nebeneffekt, dass Git es nicht als Zusammenführung behandelt, wenn Sie Konflikte lösen und festschreiben.

3 Deshalb empfehle ich git stash pop, zugunsten von zu vermeiden git stash apply. Sie haben die Möglichkeit zu überprüfen, was angewendet wurde, und zu entscheiden, ob es tatsächlich richtig angewendet wurde. Wenn nicht, haben Sie immer noch Ihren Vorrat, was bedeutet, dass Sie git stash branchalles perfekt wiederherstellen können. Nun, vorausgesetzt, das Fehlen dieses lästigen uCommits.

4 Es sollte wirklich geben: git stash apply --skip-untrackedoder so. Es sollte auch eine Variante geben, die bedeutet, dass alle diese uFestschreibungsdateien in einem neuen Verzeichnis abgelegt werden , z git stash apply --untracked-into <dir>. B. vielleicht.

torek
quelle
8
Erstaunlich detaillierte Antwort. Vielen Dank, dass Sie sich die Zeit genommen haben, dies aufzuschreiben. Ich hab viel gelernt!
Steinybot
Diese Erklärung verdient ein Heldenabzeichen. Vielen Dank, dass Sie so viele Details eingegeben haben!
Lucas Fowler
1
@seelts Leider entwickelt sich Git ständig weiter, sodass Bücher schnell veraltet sind. Aber Git sollte als eine Reihe von Werkzeugen verstanden werden - Befehle, die eine Festschreibung von Dateien oder das Festschreibungsdiagramm oder was auch immer manipulieren -, die Sie zu dem zusammensetzen, was für Sie nützlich ist , und nicht zu viele Bücher scheinen sich dem anzunähern übrigens auch.
Torek
3
Ich verstehe die Lösung des Problems nicht. Fügt es nur hinzu --index: git stash apply --index?
Danijel
1
Danke @torek. Ich habe es zuerst getan git stash save --all, dann sofort git stash apply, aber einige Dateien fehlten, weil ich sie umbenannt und dann erneut erstellt habe (vor dem Verstecken). Was geholfen hat war: git checkout stash@{0} -- .Ich werde mich nicht einmal darum kümmern, git checkout stash^3 -- .weil jetzt alles in Ordnung zu sein scheint. Es ist schade, dass ich keine Zeit habe, wirklich zu verstehen, was los war. Vielen Dank.
Danijel
100

Ich habe es geschafft, Ihr Problem neu zu erstellen. Wenn Sie nicht verfolgte Dateien speichern und diese Dateien dann erstellen (in Ihrem Beispiel foo.txtund bar.txt), haben Sie anscheinend lokale Änderungen an nicht verfolgten Dateien, die beim Anwenden überschrieben werden git stash pop.

Um dieses Problem zu umgehen, können Sie den folgenden Befehl verwenden. Dadurch werden nicht gespeicherte lokale Änderungen außer Kraft gesetzt. Seien Sie also vorsichtig.

git checkout stash -- .

Hier sind einige weitere Informationen, die ich im vorherigen Befehl gefunden habe .

Daniel Smith
quelle
Mein Arbeitsbaum war definitiv sauber, obwohl Änderungen an ignorierten Dateien hätten vorgenommen werden können.
Steinybot
1
Es sieht so aus, als würde --all/ -a ignorierte Dateien verwenden , sodass dies relevant sein kann.
Daniel Smith
1
Ich gehe davon aus, dass dieselbe Logik für ignorierte Dateien gilt, und markiere dies als Antwort (obwohl ich denke, dass der git merge --squash --strategy-option=theirs stashAnsatz in diesem Fall besser ist).
Steinybot
Einverstanden, ich mag diesen zweiten Ansatz! Viel Glück mit deiner Arbeit!
Daniel Smith
Das war hilfreich , aber es nicht wieder her untracked Dateien - wenn Sie das gleiche Problem (haben already exists, no checkout), überprüfen Sie meine Antwort unten.
Erik Koopmans
25

Um die Antwort von Daniel Smith zu erweitern : Dieser Code stellt nur die verfolgten Dateien wieder her, selbst wenn Sie sie beim Erstellen des Stashs verwendet haben --include-untracked(oder -u). Der vollständige Code ist:

git checkout stash -- .
git checkout stash^3 -- .
git stash drop

# Optional to unstage the changes (auto-staged by default).
git reset

Dadurch werden die nachverfolgten Inhalte (in stash) und die nicht nachverfolgten Inhalte (in ) vollständig wiederhergestellt und stash^3anschließend der Stash gelöscht. Ein paar Anmerkungen:

  • Seien Sie vorsichtig - dies überschreibt alles mit Ihrem Vorratsinhalt!
  • Wenn Sie die Dateien mit wiederherstellen git checkout, werden sie alle automatisch git resetbereitgestellt , daher habe ich hinzugefügt , um alles zu entfernen.
  • Einige Ressourcen verwenden stash@{0}und stash@{0}^3in meinen Tests funktioniert es mit oder ohne@{0}

Quellen:

Erik Koopmans
quelle
1
Warum sollten Sie den Stash löschen, warum ihn aus Sicherheitsgründen nicht einfach eine Weile dort lassen?
Danijel
@Danijel Sicher, Sie könnten das Versteck behalten - hängt von Ihrem Anwendungsfall ab, denke ich. Für meine Zwecke war ich mit dem Versteck fertig, nachdem es restauriert worden war.
Erik Koopmans
3

Abgesehen von anderen Antworten habe ich einen kleinen Trick gemacht

  • Alle neuen Dateien gelöscht (bereits vorhandene Dateien, z. B. foo.txt und bar.txt in der Frage)
  • git stash apply (Kann einen beliebigen Befehl verwenden, z. B. anwenden, Pop usw.)
Sanjay Nishad
quelle
Funktioniert nicht. Die gelöschten Dateien werden nur wieder im Stash angezeigt. Und der Fehler auch!
Aditya Rewari