Comment récupérer une cachette perdue dans Git?

1739

J'utilise fréquemment git stashet git stash poppour enregistrer et restaurer les modifications dans mon arbre de travail. Hier, j'ai eu quelques changements dans mon arbre de travail que j'avais cachés et sautés, puis j'ai apporté plus de changements à mon arbre de travail. Je voudrais revenir en arrière et revoir les modifications cachées d'hier, mais git stash popsemble supprimer toutes les références au commit associé.

Je sais que si j'utilise git stashalors .git / refs / stash contient la référence du commit utilisé pour créer le stash. Et .git / logs / refs / stash contient la totalité de la cachette. Mais ces références ont disparu git stash pop. Je sais que le commit est toujours quelque part dans mon référentiel, mais je ne sais pas ce que c'était.

Existe-t-il un moyen facile de récupérer la référence de validation de cache d'hier?

Notez que ce n'est pas critique pour moi aujourd'hui car j'ai des sauvegardes quotidiennes et je peux revenir à l'arborescence de travail d'hier pour obtenir mes modifications. Je demande parce qu'il doit y avoir un moyen plus simple!

Greg Hewgill
la source
74
Remarque pour l'avenir: si vous ne voulez pas perdre vos réserves à chaque fois git stash pop, vous pouvez le faire à la git stash applyplace. Il fait la même chose, sauf qu'il ne supprime pas la référence à la cachette appliquée.
Kevin
3
J'ai tout essayé ici, je n'ai pas pu trouver une cachette qui avait déjà été sautée. Tellement heureux pour jetbrains.com/help/idea/local-history.html
Juan Mendes
J'ai eu ce problème. Pour mettre à jour mon repo, je courais git stash, git pull -r upstream, git push -f origin, git stash popet pop dit « fatale: journal refs / Stash est vide ». 😲 J'ai essayé un tas de ces réponses, rien n'a fonctionné. Quand j'ai regardé en .git / refs / stash , le SHA était là. Peut-être un problème avec le marquage d'un lecteur réseau Windows pour la synchronisation hors ligne? 🤷‍♂️
brianary

Réponses:

2788

Une fois que vous connaissez le hachage du commit de stash que vous avez supprimé, vous pouvez l'appliquer en tant que stash:

git stash apply $stash_hash

Ou, vous pouvez créer une branche distincte avec

git branch recovered $stash_hash

Après cela, vous pouvez faire ce que vous voulez avec tous les outils normaux. Lorsque vous avez terminé, soufflez simplement la branche.

Trouver le hachage

Si vous venez de le faire sauter et que le terminal est toujours ouvert, vous aurez toujours la valeur de hachage imprimée par git stash popà l'écran (merci, Dolda).

Sinon, vous pouvez le trouver en utilisant ceci pour Linux, Unix ou Git Bash pour Windows:

git fsck --no-reflog | awk '/dangling commit/ {print $3}'

... ou en utilisant Powershell pour Windows:

git fsck --no-reflog | select-string 'dangling commit' | foreach { $bits = $_ -split ' '; echo $bits[2];}

Cela vous montrera toutes les validations aux extrémités de votre graphique de validation qui ne sont plus référencées à partir d'une branche ou d'une balise - chaque validation perdue, y compris chaque validation de cache que vous avez jamais créée, se trouvera quelque part dans ce graphique.

Le moyen le plus simple de trouver le commit de sauvegarde que vous souhaitez est probablement de transmettre cette liste à gitk:

gitk --all $( git fsck --no-reflog | awk '/dangling commit/ {print $3}' )

... ou consultez la réponse des emragins si vous utilisez Powershell pour Windows.

Cela lancera un navigateur de référentiel vous montrant chaque commit unique dans le référentiel , qu'il soit accessible ou non.

Vous pouvez gitky remplacer quelque chose comme git log --graph --oneline --decoratesi vous préférez un joli graphique sur la console plutôt qu'une application graphique distincte.

Pour repérer les validations stash, recherchez les messages de validation de ce formulaire:

        WIP sur somebranch : commithash Un vieux message de commit

Remarque : Le message de validation ne sera sous cette forme (commençant par "WIP activé") que si vous n'avez pas fourni de message lorsque vous l'avez fait git stash.

Aristote Pagaltzis
la source
49
Jaydel a sorti les mots de ma bouche. Ce message a sauvé mon travail :) Je voudrais juste ajouter - se souvenir de la date à laquelle vous avez travaillé sur tout ce que vous avez perdu facilite la navigation sur gitk pour ce que vous recherchez.
Sridhar Sarnobat
4
@Codey: Parce que PowerShell. Je ne sais pas si MsysGit expédie un binaire AWK. Google me dit que quelque chose comme %{ $_.Split(' ')[2]; }devrait faire l'équivalent de la commande {print $3}in that awkdans PowerShell, mais je n'ai pas de système Windows pour le tester, et vous avez toujours besoin d'un équivalent pour la /dangling commit/pièce. Quoi qu'il en soit, exécutez simplement git fsck --no-refloget regardez la sortie. Vous voulez les hachages des lignes «dangling commit <commitID>».
Aristote Pagaltzis
7
Il convient de mentionner que le message de validation n'aura la chaîne "WIP" que si vous n'avez pas fourni votre propre message lors du stockage (c'est-à-dire en le faisant git stash save "<message>").
Samir Aguiar
12
Si vous savez quand la chute s'est produite, vous pouvez utiliser ce one-liner pour obtenir la liste des validations pendantes en augmentant le temps: git fsck --no-reflog | awk '/dangling commit/ {print $3}' | xargs -L 1 git --no-pager show -s --format="%ci %H" | sortla dernière entrée est probablement celle que vous souhaitez stash apply.
ris8_allo_zen0
3
git stash apply {ref}restauré une cachette abandonnée! gitest tellement génial que ça devrait être illégal!
Tom Russell
707

Si vous n'avez pas fermé le terminal, regardez simplement la sortie de git stash popet vous aurez l'ID d'objet de la cachette déposée. Cela ressemble normalement à ceci:

$ git stash pop
[...]
Dropped refs/stash@{0} (2ca03e22256be97f9e40f08e6d6773c7d41dbfd1)

(Notez que git stash dropproduit également la même ligne.)

Pour récupérer cette réserve, exécutez simplement git branch tmp 2cae03eet vous l'obtiendrez comme une branche. Pour convertir cela en stash, exécutez:

git stash apply tmp
git stash

Le faire en tant que branche vous permet également de le manipuler librement; par exemple, pour le sélectionner ou le fusionner.

Dolda2000
la source
54
Vous pouvez également faire git stash apply commitidensuite git stashpour obtenir une nouvelle planque.
Matthew Flaschen
32
Notez que si git fusionne automatiquement la cachette et a des conflits, il ne vous montrera pas le hachage.
James
31
@James: Là encore, si ces conflits sont le résultat de l'exécution git stash pop, cela ne supprimera pas non plus la cachette, donc ce n'est normalement pas un problème.
Dolda2000
2
Il n'y avait pas de SHA dans ma sortie pop git stash. :(
Jetez compte
2
@Honey: C'est le but de git stash pop. Si vous souhaitez appliquer la réserve sans la supprimer, utilisez git stash applyplutôt. De plus, si vous souhaitez appliquer une modification à plusieurs branches, vous pouvez également sélectionner le commit à la place.
Dolda2000
271

Je voulais juste mentionner cet ajout à la solution acceptée. Ce n'était pas immédiatement évident pour moi la première fois que j'ai essayé cette méthode (peut-être qu'elle aurait dû l'être), mais pour appliquer la cachette à partir de la valeur de hachage, il suffit d'utiliser "git stash apply":

$ git stash apply ad38abbf76e26c803b27a6079348192d32f52219

Quand j'étais nouveau sur git, ce n'était pas clair pour moi, et j'essayais différentes combinaisons de "git show", "git apply", "patch", etc.

Patauger
la source
3
Notez que cela applique (duh!) La cachette à l'arbre de travail actuel. Si l'arborescence est sale, vous souhaiterez peut-être utiliser une branche temporaire ou une stash en premier, appliquer la stash du SHA-1, la stash à nouveau, puis faire apparaître l'avant-dernière stash (appelée stash @ {1}).
musiKk
111

Pour obtenir la liste des stashes qui sont toujours dans votre référentiel, mais qui ne sont plus accessibles:

git fsck --unreachable | grep commit | cut -d" " -f3 | xargs git log --merges --no-walk --grep=WIP

Si vous avez donné un titre à votre cachette, remplacez "WIP" -grep=WIPà la fin de la commande par une partie de votre message, par exemple -grep=Tesselation.

La commande attend "WIP" car le message de validation par défaut pour une stash est sous la forme WIP on mybranch: [previous-commit-hash] Message of the previous commit.

Senthil A Kumar
la source
1
echo 'git fsck - inaccessible | grep commit | coupe -d "" -f3 | xargs git log --merges --no-walk --grep = WIP '> / usr / local / bin / git-stashlog; chmod a + rx / usr / local / bin / git-stashlog # git stashlog
Erik Martino
Ou vous pouvez l'ajouter à votre .gitconfig en tant qu'alias (précéder la commande avec a !).
asmeurer
J'ai sauvé mon bacon - enfin pas vraiment mais m'a sauvé le recodage des jours de travail - apprécié - étant donné que je n'ai baissé que récemment, je viens de choisir le meilleur SHA de la sortie de votre commande - alors .... git stash appliquer SHA ... comme mentionné dans d'autres réponses - beaucoup de thx
danday74
75

Je viens de construire une commande qui m'a aidé à retrouver mon commit de stash perdu:

for ref in `find .git/objects | sed -e 's#.git/objects/##' | grep / | tr -d /`; do if [ `git cat-file -t $ref` = "commit" ]; then git show --summary $ref; fi; done | less

Cela répertorie tous les objets dans l'arborescence .git / objects, localise ceux qui sont de type commit, puis affiche un résumé de chacun. À partir de là, il s'agissait simplement de parcourir les commits pour trouver un "WIP on work: 6a9bb2" approprié ("work" est ma branche, 619bb2 est un commit récent).

Je note que si j'utilise "git stash apply" au lieu de "git stash pop" je n'aurais pas ce problème, et si j'utilise "git stash save message " alors le commit aurait pu être plus facile à trouver.

Mise à jour: Avec l'idée de Nathan, cela devient plus court:

for ref in `git fsck --unreachable | grep commit | cut -d' ' -f3`; do git show --summary $ref; done | less
Greg Hewgill
la source
41

git fsck --unreachable | grep commitdevrait montrer le sha1, bien que la liste qu'il renvoie puisse être assez grande. git show <sha1>montrera si c'est le commit que vous voulez.

git cherry-pick -m 1 <sha1> fusionnera le commit sur la branche courante.

Nathan Jones
la source
37

Équivalent Windows PowerShell utilisant gitk:

gitk --all $(git fsck --no-reflog | Select-String "(dangling commit )(.*)" | %{ $_.Line.Split(' ')[2] })

Il existe probablement un moyen plus efficace de le faire dans un seul tuyau, mais cela fait le travail.

emragins
la source
1
Je suis très reconnaissant de votre réponse
Виталий Шебаниц
32

Si vous souhaitez restaurer une réserve perdue, vous devez d'abord trouver le hachage de votre réserve perdue.

Comme l'a suggéré Aristote Pagaltzis, un git fsckdevrait vous aider.

Personnellement j'utilise mon log-allalias qui me montre chaque commit (commits récupérables) pour avoir une meilleure vue de la situation:

git log --graph --decorate --pretty=oneline --abbrev-commit --all $(git fsck --no-reflogs | grep commit | cut -d' ' -f3)

Vous pouvez effectuer une recherche encore plus rapide si vous recherchez uniquement des messages "WIP on".

Une fois que vous connaissez votre sha1, vous changez simplement votre reflog de stash pour ajouter l'ancien stash:

git update-ref refs/stash ed6721d

Vous préférerez probablement avoir un message associé afin qu'un -m

git update-ref -m "$(git log -1 --pretty=format:'%s' ed6721d)" refs/stash ed6721d

Et vous voudrez même l'utiliser comme alias:

restash = !git update-ref -m $(git log -1 --pretty=format:'%s' $1) refs/stash $1
Colin Hebert
la source
2
Cependant, cela -d\\ devrait être -d\ (ou même plus clair -d' ')
joeytwiddle
Vous avez une erreur: "fatal: argument ambigu" balançant ": révision inconnue ou chemin d'accès absent de l'arborescence de travail."
Daniel Ryan
vous devez également envelopper la sous-commande avec des guillemets git update-ref -m "$(git log -1 --pretty=format:'%s' ed6721d)" refs/stash ed6721
Andrei Shostik
18

J'ai aimé l'approche d'Aristote, mais je n'ai pas aimé utiliser GITK ... car j'ai l'habitude d'utiliser GIT depuis la ligne de commande.

Au lieu de cela, j'ai pris les validations pendantes et sorti le code dans un fichier DIFF pour examen dans mon éditeur de code.

git show $( git fsck --no-reflog | awk '/dangling commit/ {print $3}' ) > ~/stash_recovery.diff

Maintenant, vous pouvez charger le fichier diff / txt résultant (son dans votre dossier personnel) dans votre éditeur txt et voir le code réel et SHA résultant.

Ensuite, utilisez simplement

git stash apply ad38abbf76e26c803b27a6079348192d32f52219
Shaheen Ghiassy
la source
17

Vous pouvez lister tous les commits inaccessibles en écrivant cette commande dans le terminal -

git fsck --unreachable

Vérifier le hachage de validation inaccessible -

git show hash

Enfin, appliquez si vous trouvez l'article caché -

git stash apply hash
Vivek Kumar
la source
15

Pourquoi les gens posent-ils cette question? Parce qu'ils ne connaissent pas ou ne comprennent pas encore le reflog.

La plupart des réponses à cette question donnent de longues commandes avec des options dont presque personne ne se souviendra. Donc, les gens entrent dans cette question et copient-collent tout ce qu'ils pensent avoir besoin et l'oublient presque immédiatement après.

Je conseillerais à tout le monde avec cette question de simplement vérifier le reflog (git reflog), pas beaucoup plus que cela. Une fois que vous voyez cette liste de tous les commits, il y a cent façons de savoir quel commit vous recherchez et de le sélectionner ou de créer une branche à partir de celui-ci. Dans le processus, vous aurez appris le reflog et les options utiles de diverses commandes de base de git.

RobbyD
la source
1
Salut Robby. Cela est pertinent si vous travailliez, que vous vous retrouviez sur le côté et que vous deviez reprendre là où vous vous étiez arrêté il y a quelques semaines seulement pour apprendre que vous ne pouviez pas trouver votre travail caché - il s'est probablement perdu quelque part dans ces autres choses que vous faisaient. reflog est génial s'il s'agit d'une histoire récente, mais pas pour de longs intervalles de temps.
emragins
1
Hé emragins, je suis d'accord, mais c'était exactement le cas d'utilisation de l'OP. Je ne sais pas avec certitude comment les autres commandes affichées ici se comporteraient, mais mon geus serait qu'elles cesseraient également de fonctionner une fois que la référence à son commit caché serait nettoyée.
RobbyD
1
Hmm ... le scénario ci-dessus m'a amené à cette question, et je sais que cela a duré au moins quelques semaines, voire plus d'un mois, entre le moment où j'ai (sans le savoir) perdu ma réserve et le moment où j'ai pu la récupérer.
emragins
15

Dans OSX avec git v2.6.4, je lance accidentellement git stash drop, puis je l'ai trouvé en parcourant les étapes ci-dessous

Si vous connaissez le nom de la cachette, utilisez:

$ git fsck --unreachable | grep commit | cut -c 20- | xargs git show | grep -B 6 -A 2 <name of the stash>

sinon, vous trouverez l'ID du résultat manuellement avec:

$ git fsck --unreachable | grep commit | cut -c 20- | xargs git show

Ensuite, lorsque vous trouvez le commit-id, appuyez simplement sur la cachette git apply {commit-id}

J'espère que cela aide quelqu'un rapidement

Can Tecim
la source
12

Je veux ajouter à la solution acceptée un autre bon moyen de passer par tous les changements, lorsque vous n'avez pas de gitk disponible ou pas de X pour la sortie.

git fsck --no-reflog | awk '/dangling commit/ {print $3}' > tmp_commits

for h in `cat tmp_commits`; do git show $h | less; done

Ensuite, vous obtenez tous les différences pour ces hachages affichées l'une après l'autre. Appuyez sur 'q' pour passer au diff suivant.

Phil
la source
12

Je n'ai pu obtenir aucune des réponses pour travailler sur Windows dans une fenêtre de commande simple (Windows 7 dans mon cas). awk, grepEt Select-stringne sont pas reconnus comme des commandes. J'ai donc essayé une approche différente:

  • première exécution: git fsck --unreachable | findstr "commit"
  • copier la sortie dans le bloc-notes
  • trouver remplacer "commit inaccessible" par start cmd /k git show

ressemblera à ceci:

start cmd /k git show 8506d235f935b92df65d58e7d75e9441220537a4 start cmd /k git show 44078733e1b36962571019126243782421fcd8ae start cmd /k git show ec09069ec893db4ec1901f94eefc8dc606b1dbf1 start cmd /k git show d00aab9198e8b81d052d90720165e48b287c302e

  • enregistrer en tant que fichier .bat et l'exécuter
  • le script ouvrira un tas de fenêtres de commandes, montrant chaque commit
  • si vous avez trouvé celui que vous cherchez, lancez: git stash apply (your hash)

n'est peut-être pas la meilleure solution, mais a fonctionné pour moi

Koen
la source
Vous pouvez utiliser git bash même sous Windows. Dans git bash, vous disposez de tous les outils (unixoïdes) de ligne de commande dont vous avez besoin.
Adrian W
10

La réponse acceptée par Aristote montrera tous les commits accessibles, y compris les commits non-stash. Pour filtrer le bruit:

git fsck --no-reflog | \
awk '/dangling commit/ {print $3}' | \
xargs git log --no-walk --format="%H" \
  --grep="WIP on" --min-parents=3 --max-parents=3

Cela n'inclura que les validations qui ont exactement 3 validations parentes (dont une cachette aura) et dont le message comprend "WIP on".

Gardez à l'esprit que si vous avez enregistré votre cachette avec un message (par exemple git stash save "My newly created stash"), cela remplacera le message par défaut "WIP on ...".

Vous pouvez afficher plus d'informations sur chaque commit, par exemple afficher le message de commit, ou le transmettre à git stash show:

git fsck --no-reflog | \
awk '/dangling commit/ {print $3}' | \
xargs git log --no-walk --format="%H" \
  --grep="WIP on" --min-parents=3 --max-parents=3 | \
xargs -n1 -I '{}' bash -c "\
  git log -1 --format=medium --color=always '{}'; echo; \
  git stash show --color=always '{}'; echo; echo" | \
less -R
Brad Feehan
la source
6

Mon préféré est ce one-liner:

git log --oneline  $( git fsck --no-reflogs | awk '/dangling commit/ {print $3}' )

C'est fondamentalement la même idée que cette réponse mais beaucoup plus courte. Bien sûr, vous pouvez toujours ajouter --graphpour obtenir un affichage arborescent.

Lorsque vous avez trouvé le commit dans la liste, appliquez avec

git stash apply THE_COMMIT_HASH_FOUND

Pour moi, l'utilisation --no-reflogsa révélé l'entrée cachée perdue, mais --unreachable(comme dans de nombreuses autres réponses) ne l'a pas fait.

Exécutez-le sur git bash lorsque vous êtes sous Windows.

Crédits: Les détails des commandes ci-dessus sont tirés de https://gist.github.com/joseluisq/7f0f1402f05c45bac10814a9e38f81bf

Adrian W
la source
5

Récupéré en utilisant les étapes suivantes:

  1. Identifiez le code de hachage caché supprimé:

    gitk --all $ (git fsck --no-reflog | awk '/ dangling commit / {print $ 3}')

  2. Cherry Pick the Stash:

    git cherry-pick -m 1 $ stash_hash_code

  3. Résolvez les conflits, le cas échéant, en utilisant:

    git mergetool

De plus, vous pouvez rencontrer des problèmes avec le message de validation si vous utilisez gerrit. Veuillez cacher vos modifications avant de suivre les alternatives suivantes:

  1. Utilisez la réinitialisation matérielle du commit précédent, puis recommencez cette modification.
  2. Vous pouvez également cacher le changement, rebaser et recommencer.
Abhijeet
la source
@ miva2 votre modification a supprimé le lien vers la réponse la plus correcte dans cette question. Ajout du lien dans le commentaire stackoverflow.com/questions/89332/…
Abhijeet
4

Ce que je suis venu ici, c'est comment récupérer la cachette, indépendamment de ce que j'ai vérifié. En particulier, j'avais caché quelque chose, puis extrait une version plus ancienne, puis l'avais sortie, mais la cachette était un no-op à ce moment-là, donc la cachette a disparu; Je ne pouvais pas simplement le git stashrepousser sur la pile. Cela a fonctionné pour moi:

$ git checkout somethingOld
$ git stash pop
...
nothing added to commit but untracked files present (use "git add" to track)
Dropped refs/stash@{0} (27f6bd8ba3c4a34f134e12fe69bf69c192f71179)
$ git checkout 27f6bd8ba3c
$ git reset HEAD^    # Make the working tree differ from the parent.
$ git stash # Put the stash back in the stack.
Saved working directory and index state WIP on (no branch): c2be516 Some message.
HEAD is now at c2be516 Some message.
$ git checkout somethingOld # Now we are back where we were.

Rétrospectivement, j'aurais dû utiliser git stash applynon git stash pop. Je faisais un bisectet j'avais un petit patch que je voulais appliquer à chaque bisectétape. Maintenant je fais ça:

$ git reset --hard; git bisect good; git stash apply
$ # Run tests
$ git reset --hard; git bisect bad; git stash apply
etc.
Ben
la source
est-ce une réponse ou la continuation de la question?
Alex Brown
Un peu des deux. J'ai trouvé cette page parce que j'ai perdu une cachette et que j'essayais de la récupérer. Le cas d'utilisation pour moi est de faire une bissecte où je veux appliquer un changement avant de tester à chaque étape. J'ai appris à la dure que vous ne pouvez pas simplement pop, tester, stash, bissect, car cela peut laisser un commit différent sur la stash, par conséquent stash apply.
Ben