Como recuperar um stash descartado no Git?

1738

Eu freqüentemente uso git stashe git stash poppara salvar e restaurar alterações na minha árvore de trabalho. Ontem, tive algumas alterações na minha árvore de trabalho que havia escondido e aparado e, em seguida, fiz mais alterações na minha árvore de trabalho. Gostaria de voltar e revisar as alterações ocultas de ontem, mas git stash popparece remover todas as referências ao commit associado.

Eu sei que, se eu usar git stash, o .git / refs / stash contém a referência do commit usado para criar o stash. E .git / logs / refs / stash contém todo o stash. Mas essas referências se foram depois git stash pop. Eu sei que o commit ainda está no meu repositório em algum lugar, mas não sei o que era.

Existe uma maneira fácil de recuperar a referência de confirmação de stash de ontem?

Observe que isso não é crítico para mim hoje porque tenho backups diários e posso voltar à árvore de trabalho de ontem para obter minhas alterações. Estou perguntando, porque deve haver uma maneira mais fácil!

Greg Hewgill
fonte
74
Nota para o futuro: se você não quiser perder seus esconderijos toda vez git stash pop, poderá fazê-lo git stash apply. Faz o mesmo, exceto que não remove a referência ao stash aplicado.
Kevin
3
Tentei de tudo aqui, não consegui encontrar um esconderijo que já tinha sido estourado. Estou
Juan Mendes
Eu tive esse problema. Para atualizar meu repo, eu corri git stash, git pull -r upstream, git push -f origin, git stash pop, e pop disse "fatal: log para refs / estoque está vazio". 😲 Tentei várias respostas, nada funcionou. Quando procurei em .git / refs / stash , o SHA estava lá. Talvez um problema ao marcar uma unidade de rede do Windows para sincronização offline? --️
brianary 14/11/19

Respostas:

2786

Depois de saber o hash do commit do stash que você eliminou, você pode aplicá-lo como um stash:

git stash apply $stash_hash

Ou, você pode criar uma ramificação separada para isso com

git branch recovered $stash_hash

Depois disso, você pode fazer o que quiser com todas as ferramentas normais. Quando terminar, apenas sopre o galho.

Encontrando o hash

Se você apenas o abriu e o terminal ainda está aberto, você ainda terá o valor do hash impresso git stash popna tela (obrigado, Dolda).

Caso contrário, você pode encontrá-lo usando isso para Linux, Unix ou Git Bash para Windows:

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

... ou usando o Powershell para Windows:

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

Isso mostrará a você todas as confirmações nas dicas do gráfico de confirmação que não são mais referenciadas em nenhuma ramificação ou tag - toda confirmação perdida, incluindo toda confirmação de stash que você já criou, estará em algum lugar desse gráfico.

A maneira mais fácil de encontrar o commit stash desejado é provavelmente passar essa lista para gitk:

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

... ou veja a resposta de emragins se estiver usando o Powershell para Windows.

Isso iniciará um navegador de repositório, mostrando todos os commit no repositório de todos os tempos , independentemente de serem alcançáveis ​​ou não.

Você pode substituí gitk-lo por algo como git log --graph --oneline --decoratese você preferir um bom gráfico no console do que um aplicativo GUI separado.

Para detectar confirmações de stash, procure por mensagens de confirmação deste formulário:

        WIP em somebranch : commithash Alguma mensagem antiga de commit

Nota : A mensagem de confirmação estará neste formato apenas (começando com "WIP ativado") se você não forneceu uma mensagem quando o fez git stash.

Aristóteles Pagaltzis
fonte
49
Jaydel tirou as palavras da minha boca. Este post salvou meu trabalho :) Gostaria de acrescentar - lembrar a data em que você trabalhou no que perdeu, facilita a navegação no gitk pelo que você está procurando.
Sridhar Sarnobat 4/15
4
@ Codey: Porque o PowerShell. Não sei se o MsysGit envia um binário AWK. Googling me diz que algo como %{ $_.Split(' ')[2]; }deve fazer o equivalente do {print $3}em que awkcomando em PowerShell, mas eu não tenho um sistema do Windows para testar isso, e você ainda precisa de um equivalente para a /dangling commit/parte. De qualquer forma, basta executar git fsck --no-refloge olhar para a saída. Você deseja os hashes das linhas "dangling commit <commitID>".
Aristóteles Pagaltzis 02/10/2015
7
Vale ressaltar que a mensagem de confirmação só terá a string "WIP" se você não fornecer sua própria mensagem ao esconder (ou seja, fazer git stash save "<message>").
Samir Aguiar
12
Se você souber quando a queda aconteceu, poderá usar esta linha para obter a lista de confirmações pendentes aumentando o tempo: git fsck --no-reflog | awk '/dangling commit/ {print $3}' | xargs -L 1 git --no-pager show -s --format="%ci %H" | sorta última entrada é provavelmente a que você deseja stash apply.
ris8_allo_zen0
3
git stash apply {ref}restaurou um esconderijo caído! gité tão bom que deveria ser ilegal!
Tom Russell #
707

Se você não fechou o terminal, basta olhar para a saída git stash pope você terá o ID do objeto do estoque descartado. Normalmente é assim:

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

(Observe que git stash droptambém produz a mesma linha.)

Para recuperar esse estoque, basta executar git branch tmp 2cae03ee você o obterá como um ramo. Para converter isso em um esconderijo, execute:

git stash apply tmp
git stash

Tê-lo como um ramo também permite manipulá-lo livremente; por exemplo, para selecioná-lo ou mesclá-lo.

Dolda2000
fonte
54
Você também pode fazer git stash apply commitidisso git stashpara obter um novo esconderijo.
Matthew Flaschen
32
Observe que se o git mesclar automaticamente o stash e tiver conflitos, ele não mostrará o hash.
James
31
@ James: Então, novamente, se esses conflitos resultarem da execução git stash pop, ele também não eliminará o estoque, então isso normalmente não é um problema.
precisa saber é o seguinte
2
Não havia SHA na minha saída pop do git stash. :(
Jogue fora a conta
2
@ Mel: Esse é o ponto git stash pop. Se você deseja aplicar o esconderijo sem soltá-lo, use-o git stash apply. Além disso, se você deseja aplicar uma alteração em várias ramificações, também pode escolher a confirmação.
Dolda2000
271

Só queria mencionar essa adição à solução aceita. Não foi imediatamente óbvio para mim na primeira vez que tentei esse método (talvez devesse ter sido), mas para aplicar o stash a partir do valor de hash, basta usar "git stash apply":

$ git stash apply ad38abbf76e26c803b27a6079348192d32f52219

Quando eu era novo no git, isso não estava claro para mim, e eu estava tentando diferentes combinações de "git show", "git apply", "patch" etc.

Wade
fonte
3
Observe que isso aplica (duh!) O stash à árvore de trabalho atual. Se a árvore estiver suja, convém usar uma ramificação temporária ou esconderijo primeiro, aplicar o esconderijo do SHA-1, esconder novamente e depois colocar o penúltimo penúltimo esconderijo (chamado stash @ {1}).
musikk
111

Para obter a lista de stashes que ainda estão no seu repositório, mas não são mais acessíveis:

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

Se você deu um título ao seu esconderijo, substitua "WIP" no -grep=WIPfinal do comando por uma parte da sua mensagem, por exemplo -grep=Tesselation.

O comando está aguardando "WIP" porque a mensagem de confirmação padrão para uma ocultação está no formato WIP on mybranch: [previous-commit-hash] Message of the previous commit.

Senthil A Kumar
fonte
1
echo 'git fsck - inacessível | confirmação grep | cut -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 você pode adicionar isso ao seu .gitconfig como um alias (preceda o comando com a !).
asmeurer
Salvei meu bacon - bem, não realmente, mas me salvou re-codificando os dias de trabalho - apreciado - dado que eu só caí recentemente, apenas escolhi o SHA superior na saída do seu comando - então ... git stash aplique SHA ... como mencionado em outras respostas - muitos thx
danday74
75

Acabei de construir um comando que me ajudou a encontrar meu commit de stash perdido:

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

Isso lista todos os objetos na árvore .git / objects, localiza aqueles que são do tipo commit e mostra um resumo de cada um. A partir desse ponto, era apenas uma questão de examinar os commits para encontrar um "WIP on work: 6a9bb2" apropriado ("work" é meu ramo, 619bb2 é um commit recente).

Observo que, se eu usar "git stash apply" em vez de "git stash pop", não teria esse problema e, se usar "git stash save message ", a confirmação poderá ter sido mais fácil de encontrar.

Atualização: Com a idéia de Nathan, isso se torna mais curto:

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

git fsck --unreachable | grep commitdeve mostrar o sha1, embora a lista retornada possa ser bastante grande. git show <sha1>mostrará se é o commit que você deseja.

git cherry-pick -m 1 <sha1> mesclará o commit na ramificação atual.

Nathan Jones
fonte
37

Equivalente ao Windows PowerShell usando o gitk:

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

Provavelmente, existe uma maneira mais eficiente de fazer isso em um pipe, mas isso faz o trabalho.

emragins
fonte
1
Sou muito grato por sua resposta
Виталий Шебаниц 23/12/19
32

Se você deseja restabelecer um stash perdido, primeiro precisa encontrar o hash do seu stash perdido.

Como Aristóteles Pagaltzis sugeriu uma git fsck deve ajudá-lo.

Pessoalmente, uso meu log-allalias, que mostra todas as confirmações (confirmações recuperáveis) para ter uma visão melhor da situação:

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

Você pode fazer uma pesquisa ainda mais rápida se estiver procurando apenas mensagens "WIP on".

Depois de conhecer o sha1, basta alterar o reflog do stash para adicionar o stash antigo:

git update-ref refs/stash ed6721d

Você provavelmente prefere ter uma mensagem associada para que -m

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

E você ainda vai querer usar isso como um alias:

restash = !git update-ref -m $(git log -1 --pretty=format:'%s' $1) refs/stash $1
Colin Hebert
fonte
2
No entanto, o -d\\ deve ser -d\ (ou ainda mais claro -d' ')
joeytwiddle
Ocorreu um erro: "fatal: argumento ambíguo 'dangling': revisão desconhecida ou caminho que não está na árvore de trabalho."
Daniel Ryan
você também precisa envolver sub-comando com citações git update-ref -m "$(git log -1 --pretty=format:'%s' ed6721d)" refs/stash ed6721
Andrei Shostik
18

Eu gostei da abordagem de Aristóteles, mas não gostei de usar o GITK ... como estou acostumado a usar o GIT na linha de comando.

Em vez disso, peguei as confirmações pendentes e produzi o código em um arquivo DIFF para revisão no meu editor de código.

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

Agora você pode carregar o arquivo diff / txt resultante (na sua pasta pessoal) no editor de txt e ver o código real e o SHA resultante.

Então é só usar

git stash apply ad38abbf76e26c803b27a6079348192d32f52219
Shaheen Ghiassy
fonte
17

Você pode listar todos os commits inacessíveis escrevendo este comando no terminal -

git fsck --unreachable

Verificar hash de confirmação inacessível -

git show hash

Por fim, aplique se você encontrar o item escondido

git stash apply hash
Vivek Kumar
fonte
15

Por que as pessoas fazem essa pergunta? Porque eles ainda não conhecem ou entendem o reflog.

A maioria das respostas a essa pergunta fornece comandos longos com opções que quase ninguém se lembrará. Assim, as pessoas entram nessa questão e copiam e colam o que acham que precisam e esquecem-na quase imediatamente depois.

Eu aconselharia a todos com esta pergunta que apenas checassem o reflog (git reflog), não muito mais que isso. Depois de ver a lista de todos os commits, existem centenas de maneiras de descobrir qual commit você está procurando e selecioná-lo ou criar um branch a partir dele. No processo, você aprenderá sobre o reflog e as opções úteis para vários comandos básicos do git.

RobbyD
fonte
1
Oi Robby. Isso é relevante se você estava trabalhando, se desviou e precisa voltar ao ponto em que parou algumas semanas atrás, apenas para saber que não consegue encontrar seu trabalho escondido - provavelmente ele se perdeu em algum lugar nas outras coisas que você estava fazendo. O reflog é ótimo se for história recente, mas não por muito tempo.
emragins
1
Ei emragins, eu concordo, mas esse era exatamente o caso de uso do OP. Eu não sei ao certo como os outros comandos que estão sendo postados aqui se comportariam, mas meu problema seria que eles também parariam de trabalhar assim que o árbitro do seu commit oculto fosse limpo.
precisa saber é o seguinte
1
Hmm ... o cenário acima foi o que me levou a essa pergunta, e eu sei que foram pelo menos algumas semanas ou até mais perto de um mês entre quando eu (sem saber) perdi meu estoque e quando consegui recuperá-lo.
emragins
15

No OSX com o git v2.6.4, eu apenas corro o git stash drop acidentalmente, e o encontrei seguindo as etapas abaixo

Se você souber o nome do esconderijo, use:

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

caso contrário, você encontrará o ID do resultado manualmente com:

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

Então, quando você encontrar o commit-id, basta clicar no git stash apply {commit-id}

Espero que isso ajude alguém rapidamente

Can Tecim
fonte
12

Quero adicionar à solução aceita outra boa maneira de passar por todas as alterações, quando você não tem o gitk disponível ou não há X para saída.

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

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

Então você obtém todas as diferenças para esses hashes exibidos um após o outro. Pressione 'q' para chegar ao próximo diff.

Phil
fonte
12

Não consegui obter nenhuma das respostas para funcionar no Windows em uma janela de comando simples (Windows 7 no meu caso). awk, grepe Select-stringnão foram reconhecidos como comandos. Então, tentei uma abordagem diferente:

  • Primeira corrida: git fsck --unreachable | findstr "commit"
  • copie a saída para o bloco de notas
  • localize substituir "commit inacessível" por start cmd /k git show

será algo como isto:

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

  • salve como um arquivo .bat e execute-o
  • o script abrirá várias janelas de comando, mostrando cada confirmação
  • se você encontrou o que estava procurando, execute: git stash apply (your hash)

pode não ser a melhor solução, mas funcionou para mim

Koen
fonte
Você pode usar o git bash, mesmo no Windows. No git bash, você tem todas as ferramentas de linha de comando (unixoid) necessárias.
Adrian W
10

A resposta aceita por Aristóteles mostrará todos os commits alcançáveis, incluindo commits não semelhantes a stash. Para filtrar o ruído:

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

Isso incluirá apenas confirmações com exatamente 3 confirmações principais (que terão um stash) e cuja mensagem inclui "WIP ativado".

Lembre-se de que, se você salvou seu estoque com uma mensagem (por exemplo git stash save "My newly created stash"), isso substituirá a mensagem "WIP on ..." padrão.

Você pode exibir mais informações sobre cada confirmação, por exemplo, exibir a mensagem de confirmação ou passá-la para 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
fonte
6

O meu favorito é este one-liner:

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

Esta é basicamente a mesma idéia que essa resposta, mas muito mais curta. Obviamente, você ainda pode adicionar --graphpara obter uma exibição em forma de árvore.

Quando você encontrar o commit na lista, aplique com

git stash apply THE_COMMIT_HASH_FOUND

Para mim, o uso --no-reflogsrevelou a entrada de stash perdido, mas --unreachable(como encontrado em muitas outras respostas) não.

Execute-o no git bash quando estiver no Windows.

Créditos: Os detalhes dos comandos acima são obtidos em https://gist.github.com/joseluisq/7f0f1402f05c45bac10814a9e38f81bf

Adrian W
fonte
5

Recuperou-o usando as seguintes etapas:

  1. Identifique o código de hash stash excluído:

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

  2. Cereja Escolha o esconderijo:

    git cherry-pick -m 1 $ stash_hash_code

  3. Resolver conflitos, se houver, usando:

    git mergetool

Além disso, você pode estar tendo problemas com a mensagem de confirmação se estiver usando o gerrit. Guarde suas alterações antes de seguir as próximas alternativas:

  1. Use a reinicialização completa da confirmação anterior e, em seguida, confirme novamente essa alteração.
  2. Você também pode ocultar a alteração, refazer a nova refazer e reenviar.
Abhijeet
fonte
@ miva2 sua edição removeu o link para a resposta mais correta nesta pergunta. Adicionando o link novamente no comentário stackoverflow.com/questions/89332/…
Abhijeet
4

O que eu vim aqui procurando é como recuperar o esconderijo, independentemente do que eu verifiquei. Em particular, eu escondi algo, depois fiz o check-out de uma versão mais antiga e a abri, mas o esconderijo não era uma opção naquele momento anterior, então o esconderijo desapareceu; Eu não poderia simplesmente fazer git stashisso de volta na pilha. Isso funcionou para mim:

$ 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.

Em retrospecto, eu deveria estar usando git stash applynão git stash pop. Eu estava fazendo um bisecte tinha um pequeno patch que queria aplicar a cada bisectpasso. Agora eu estou fazendo isso:

$ git reset --hard; git bisect good; git stash apply
$ # Run tests
$ git reset --hard; git bisect bad; git stash apply
etc.
Ben
fonte
isso é uma resposta ou a continuação da pergunta?
Alex Brown
Um pouco dos dois. Encontrei esta página porque perdi um estoque e estava tentando recuperá-lo. O caso de uso para mim é fazer uma divisão em que quero aplicar uma alteração antes de testar em cada etapa. Aprendi da maneira mais difícil que você não pode simplesmente estourar, testar, esconder, dividir, porque isso pode deixar um commit diferente no esconderijo stash apply.
Ben