web-dev-qa-db-fra.com

Pourquoi Git Stash Pop indique-t-il qu'il ne peut pas restaurer les fichiers non suivis à partir d'une entrée Stash?

J'avais un tas de changements mis en scène et non mis en scène et je voulais changer rapidement de branche puis revenir en arrière.

J'ai donc mis en scène mes modifications en utilisant:

$ git stash Push -a

(Avec le recul, j'aurais probablement pu utiliser --include-untracked au lieu de --all)

Ensuite, quand je suis allé chercher la réserve, j'ai beaucoup d'erreurs du genre:

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

Il ne semble pas y avoir de modifications restaurées à partir de la réserve.

J'ai aussi essayé $ git stash branch temp mais cela montre les mêmes erreurs.

J'ai trouvé un moyen de contourner ce problème en utilisant:

$ git stash show -p | git apply

Catastrophe évitée pour l'instant mais cela soulève des questions.

Pourquoi cette erreur s'est-elle produite en premier lieu et comment puis-je l'éviter la prochaine fois?

29
Steiny

Comme explication supplémentaire, notez que git stash Effectue deux commits ou trois commits. La valeur par défaut est deux. vous en obtenez trois si vous utilisez une orthographe des options --all ou --include-untracked.

Ces deux ou trois commits sont spéciaux d'une manière importante: ils sont sur pas de branche . Git les localise par le nom spécial stash.1 La chose la plus importante, cependant, est ce que Git vous permet - et vous fait - de faire avec ces deux ou trois commits. Pour comprendre cela, nous devons examiner le contenu de ces commits.

Ce qu'il y a dans une cachette

Chaque commit peut lister un ou plusieurs parent commits. Ceux-ci forment un graphique, où les commits ultérieurs renvoient aux précédents. La cachette contient normalement deux commits, que j'aime appeler i pour le contenu de la zone d'index/staging, et w pour le contenu de l'arbre de travail. Rappelez-vous également que chaque commit contient un instantané. Dans une validation normale, cet instantané est créé à partir du contenu de la zone d'index/staging. Donc, le commit i est en fait un commit parfaitement normal! C'est juste sur aucune branche:

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

Si vous créez une réserve normale, le code git stash Donne w maintenant en copiant tous vos fichiers d'arborescence de travail suivis (dans un index auxiliaire temporaire). Git définit le premier parent de ce commit w pour qu'il pointe vers le commit HEAD, et le deuxième parent pour pointer vers commit i. Enfin, il définit stash pour pointer sur ce w commit:

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

Si vous ajoutez --include-untracked Ou --all, Git fait un commit supplémentaire, u, entre i et w. Le contenu de l'instantané pour u correspond aux fichiers non suivis mais non ignorés (--include-untracked) Ou aux fichiers non suivis même s'ils sont ignorés (--all). Ce commit u supplémentaire a no parent, puis lorsque git stash Crée w, il définit w troisième parent de cette u commit, afin que vous obteniez:

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

Git aussi, à ce stade, supprime tous les fichiers d'arbre de travail qui se sont retrouvés dans le u commit (en utilisant git clean Pour le faire).

Restaurer une cachette

Lorsque vous accédez à restaurer une réserve, vous avez la possibilité d'utiliser --index Ou de ne pas l'utiliser. Ceci indique à git stash apply (Ou à l’une des commandes utilisant en interne apply, telle que pop) qu’elle devrait utiliser le i commit pour tenter de modifier votre index actuel. Cette modification est faite avec:

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

(plus ou moins; il y a une foule de détails qui entravent l'idée de base ici).

Si vous omettez --index, git stash apply Ignore complètement le commit i.

Si la cachette n'a que deux commits, git stash apply Peut maintenant appliquer le commit w. Pour cela, il appelle git merge2 (sans lui permettre de valider ou de traiter le résultat comme une fusion normale), en utilisant la validation d'origine sur laquelle la dissimulation a été effectuée (le parent de i et le premier parent de w), w en tant que commit --theirs Et votre commit actuel (HEAD) en tant que cible de la fusion. Si la fusion réussit, tout va bien - enfin, au moins , Git le pense - et le git stash apply Lui-même réussit. Si vous avez utilisé git stash pop Pour appliquer la cachette, le code maintenant supprime la cachette.3 Si la fusion échoue, Git déclare que la requête a échoué. Si vous avez utilisé git stash pop, Le code conserve la stash et fournit le même statut d'échec que pour git stash apply.

Mais si vous avez ce troisième commit — s'il y a un commit u dans la réserve que vous postulez, les choses changent! Il n'y a pas d'option pour prétendre que le commit u n'existe pas.4 Git insiste pour extraire tous les fichiers de que u commit, dans l’arbre de travail courant. Cela signifie que les fichiers ne doivent pas exister du tout ou avoir le même contenu que dans le commit u.

Pour que cela se produise, vous pouvez utiliser git clean Vous-même, mais souvenez-vous que les fichiers non suivis (ignorés ou non) n'ont pas d'autre existence dans un référentiel Git, alors assurez-vous qu'ils peuvent tous être détruits! Ou bien, vous pouvez créer un répertoire temporaire et y déplacer les fichiers à des fins de sauvegarde, ou même créer un autre git stash save -u Ou git stash save -a, Car ces derniers exécuteront git clean À votre place. Mais cela vous laisse juste avec un autre cachette de style u- à traiter plus tard.


1C'est en fait refs/stash. Ceci est important si vous créez une branche nommée stash: le nom complet de la branche est refs/heads/stash, Ils ne sont donc pas en conflit. Mais ne faites pas cela: Git ne vous dérange pas, mais vous allez vous perdre. :-)

2Le code git stash Utilise en réalité git merge-recursive Directement ici. Cela est nécessaire pour plusieurs raisons, et a également pour effet secondaire de s'assurer que Git ne la traite pas comme une fusion lorsque vous résolvez des conflits et que vous commettez.

3C'est pourquoi je recommande d'éviter git stash pop, En faveur de git stash apply. Vous avez la possibilité d'examiner ce qui a été appliqué et de décider s'il a été appliqué correctement . Sinon, vous avez toujours votre cachette , ce qui signifie que vous pouvez utiliser git stash branch Pour tout récupérer à la perfection. Eh bien, en supposant le manque de cet embêtant u commettre.

4Il devrait vraiment y avoir: git stash apply --skip-untracked Ou quelque chose. Il devrait également exister une variante qui signifie et supprime tous les fichiers u commit dans un nouveau répertoire , par exemple, git stash apply --untracked-into <dir>, Peut-être.

26
torek

J'ai réussi à recréer votre problème. Il semble que si vous stockez des fichiers non suivis, puis créez ces fichiers (dans votre exemple, foo.txt et bar.txt), vous avez alors des modifications locales à apporter aux fichiers non suivis qui seraient écrasées lorsque vous appliquerez git stash pop.

Pour résoudre ce problème, vous pouvez utiliser la commande suivante:

git checkout stash -- .

Cela remplacera toutes les modifications locales non enregistrées, alors soyez prudent. Voici quelques informations supplémentaires que j'ai trouvées dans la commande précédente .

38
Daniel Smith

Pour développer réponse de Daniel Smith : ce code ne restaure que les fichiers suivis , même si vous avez utilisé --include-untracked (ou -u) lors de la création de la réserve. Le code complet requis est:

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

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

Ceci restaure complètement le contenu suivi (dans stash) et le contenu non suivi (dans stash^3), puis supprime la réserve. Quelques notes:

  • Faites attention - Cela écrasera tout avec votre contenu stash!
  • Restaurer les fichiers avec git checkout provoque leur mise en scène automatiquement, donc j’ai ajouté git reset pour tout défaire.
  • Certaines ressources utilisent stash@{0} et stash@{0}^3, lors de mes tests, il fonctionne de la même manière avec ou sans @{0}

Sources:

5
Erik Koopmans