Desfazer git reset --hard com arquivos não confirmados na área de teste

110

Estou tentando recuperar meu trabalho. Eu estupidamente fiz git reset --hard, mas antes disso eu só get add .fiz e não fiz git commit. Por favor ajude! Aqui está meu log:

MacBookPro:api user$ git status
# On branch master
# Changes to be committed:
#   (use "git reset HEAD <file>..." to unstage)

#   modified:   .gitignore
...


MacBookPro:api user$ git reset --hard
HEAD is now at ff546fa added new strucuture for api

É possível desfazer git reset --hardnesta situação?

Eistrati
fonte
@MarkLongair homem incrível! Você acabou de receber meu trabalho de volta! Eu escrevi um script Python para criar arquivos de todas as saídas! Vou adicionar o script como uma resposta
Menino
4
Não 'estupidamente' .. mas 'ingenuamente' ... porque eu fiz o MESMO!
Rosdi Kasim,
Ainda pode ser estupidamente ;-)
Duncan McGregor
Aqui está um ótimo artigo sobre como reverter isso. Vai exigir algum trabalho manual.
Jan Swart
@MarkLongair `` `find .git / objects / -type f -printf '% TY-% Tm-% Td% TT% p \ n' | tipo `` `funcionou para mim. as datas também aparecem, comece a verificar os blobs a partir do final.
ZAky,

Respostas:

175

Você deve conseguir recuperar todos os arquivos adicionados ao índice (por exemplo, como na sua situação, com git add .), embora possa ser um pouco trabalhoso. Para adicionar um arquivo ao índice, git o adiciona ao banco de dados de objetos, o que significa que ele pode ser recuperado desde que a coleta de lixo ainda não tenha acontecido. Há um exemplo de como fazer isso dado na resposta de Jakub Narębski aqui:

No entanto, tentei fazer isso em um repositório de teste e houve alguns problemas - --cacheddeveria haver --cache, e descobri que ele não criou o .git/lost-founddiretório. No entanto, as seguintes etapas funcionaram para mim:

git fsck --cache --unreachable $(git for-each-ref --format="%(objectname)")

Isso deve gerar todos os objetos no banco de dados de objetos que não podem ser acessados ​​por nenhum ref, no índice ou por meio do reflog. A saída será semelhante a esta:

unreachable blob 907b308167f0880fb2a5c0e1614bb0c7620f9dc3
unreachable blob 72663d3adcf67548b9e0f0b2eeef62bce3d53e03

... e para cada um desses blobs, você pode fazer:

git show 907b308

Para produzir o conteúdo do arquivo.


Muita produção?

Atualização em resposta a sehe 's comentário abaixo:

Se você achar que tem muitos commits e árvores listados na saída daquele comando, você pode querer remover da saída quaisquer objetos que são referenciados de commits não referenciados. (Normalmente você pode voltar a esses commits por meio do reflog de qualquer maneira - estamos apenas interessados ​​em objetos que foram adicionados ao índice, mas nunca podem ser encontrados por meio de um commit.)

Primeiro, salve a saída do comando, com:

git fsck --cache --unreachable $(git for-each-ref --format="%(objectname)") > all

Agora, os nomes de objeto desses commits inacessíveis podem ser encontrados com:

egrep commit all | cut -d ' ' -f 3

Assim, você pode encontrar apenas as árvores e objetos que foram adicionados ao índice, mas não confirmados em nenhum ponto, com:

git fsck --cache --unreachable $(git for-each-ref --format="%(objectname)") \
  $(egrep commit all | cut -d ' ' -f 3)

Isso reduz enormemente o número de objetos que você terá que considerar.


Atualização: Philip Oakley abaixo sugere outra maneira de reduzir o número de objetos a serem considerados, que é apenas considerar os arquivos modificados mais recentemente em .git/objects. Você pode encontrá-los com:

find .git/objects/ -type f -printf '%TY-%Tm-%Td %TT %p\n' | sort

(Encontrei essa findinvocação aqui .) O final dessa lista pode ser assim:

2011-08-22 11:43:43.0234896770 .git/objects/b2/1700b09c0bc0fc848f67dd751a9e4ea5b4133b
2011-09-13 07:36:37.5868133260 .git/objects/de/629830603289ef159268f443da79968360913a

Nesse caso, você pode ver esses objetos com:

git show b21700b09c0bc0fc848f67dd751a9e4ea5b4133b
git show de629830603289ef159268f443da79968360913a

(Observe que você deve remover o /no final do caminho para obter o nome do objeto.)

Mark Longair
fonte
antes de postar minha resposta, considerei incluir exatamente essas etapas. No entanto, ao tentar isso com um de meus repositórios locais, obtive centenas de inacessíveis e não consegui encontrar uma abordagem adequada para, digamos, classificá-los por data de envio. Isso seria incrível
ver
3
Não é possível dar uma olhada nos carimbos de hora do objeto para os objetos mais recentes posteriores ao seu último commit? Ou eu estou esquecendo de alguma coisa.
Philip Oakley,
1
@Philip Oakley: obrigado por essa sugestão, adicionei a sugestão de encontrar os objetos modificados mais recentemente no banco de dados de objetos.
Mark Longair
2
Cara, isso é incrível! Isso salvou meu @ $$ agora. E aprendi alguma ciência git. Melhor observar o que você adiciona ao índice ...
bowsersenior
2
para não roubar a luz de cal de ninguém aqui. Mas só como uma observação, já que acabei de encontrar o mesmo problema e pensei ter perdido todo o meu trabalho. Para aqueles que usam um IDE , os IDEs geralmente têm uma solução de recuperação com um clique. No caso do PHPstorm, eu só tinha que clicar com o botão direito do mouse no diretório e selecionar mostrar histórico local e então reverter para o último estado válido.
Shalom Sam
58

Acabei de fazer um git reset --harde perdi um commit. Mas eu sabia o hash do commit, então fui capaz de fazer git cherry-pick COMMIT_HASHpara restaurá-lo.

Fiz isso alguns minutos depois de perder o commit, então pode funcionar para alguns de vocês.

Richard Saunders
fonte
5
Obrigado, obrigado, obrigado. Conseguiu recuperar um dia de trabalho graças a esta resposta
Dace
21
Provavelmente, vale a pena mencionar que você pode ver esses hashes usando git reflog, por exemplo git reset --hard-> git reflog(olhando para o hash HEAD @ {1}) e finalmentegit cherry-pick COMMIT_HASH
Javier López
2
Pessoal lendo isso, fiquem atentos que isso só funciona para um único hash de commit, se houver mais de um commit não vai resolver o problema de uma vez!
Ain Tohvri
4
Na verdade, você pode redefinir "encaminhar". Então, se você resetou e passou por vários commits, fazer outro 'git reset --hard <commit>' deve restaurar toda a cadeia de commits até aquele ponto. (dado que ainda não foram aprovados no GC)
akimsko
6
O one-liner para recuperar commits perdidos com git reset --hard(assumindo que você não fez mais nada depois desse comando) égit reset --hard @{1}
Ajedi32
13

Graças a Mark Longair recebi minhas coisas de volta!

Primeiro salvei todos os hashes em um arquivo:

git fsck --cache --unreachable $(git for-each-ref --format="%(objectname)") > allhashes

em seguida, coloco todos eles (removendo o 'blob inacessível') em uma lista e coloco todos os dados em novos arquivos ... você tem que escolher seus arquivos e renomeá-los novamente, o que você precisa ... mas eu só precisava de alguns arquivos .. espero que isso ajude alguém ...

commits = ["c2520e04839c05505ef17f985a49ffd42809f",
    "41901be74651829d97f29934f190055ae4e93",
    "50f078c937f07b508a1a73d3566a822927a57",
    "51077d43a3ed6333c8a3616412c9b3b0fb6d4",
    "56e290dc0aaa20e64702357b340d397213cb",
    "5b731d988cfb24500842ec5df84d3e1950c87",
    "9c438e09cf759bf84e109a2f0c18520",
    ...
    ]

from subprocess import call
filename = "file"
i = 1
for c in commits:
    f = open(filename + str(i),"wb")
    call(["git", "show", c],stdout=f)
    i+=1
Garoto
fonte
1
Sim! Isso é exatamente o que eu precisava. Eu gosto do script Python para recriar todos os arquivos também. As outras respostas me deixaram nervoso por perder meus dados com a coleta de lixo, então descartar os arquivos é uma vitória para mim :)
Eric Olson
1
Escrevi este script com base nesta resposta. Funciona imediatamente
Alexar
1
Acho que é mais fácil de automatizá-lo como este: mkdir lost; git fsck --cache --unreachable $(git for-each-ref --format="%(objectname)") | grep -Po '\s\S{40}$' | xargs -i echo "git show {} > lost/{}.blob" | sh. Os arquivos acabarão emlost/*.blob
Matija Nalis
6

A solução de @Ajedi32 nos comentários funcionou para mim exatamente nesta situação.

git reset --hard @{1}

Observe que todas essas soluções dependem de não haver git gc, e algumas delas podem causar um, então eu compactaria o conteúdo do seu diretório .git antes de tentar qualquer coisa para que você tenha um instantâneo para voltar, caso não haja não funciona para você.

Duncan McGregor
fonte
Eu tinha vários arquivos não confirmados. Então enviei 1 arquivo e fiz um git reset --hard, que bagunçou meu repositório de uma maneira desconhecida. @Duncan, o que @ {1} faz? e a qual comentário você está se referindo? Está reiniciando um git reset?
alpha_989
Acho que @ {1} é uma referência relativa ao último, exceto um commit, mas não sou um especialista, apenas relatando o que funcionou para mim
Duncan McGregor
3

Encontrou o mesmo problema, mas não adicionou as alterações ao índice. Portanto, todos os comandos acima não me trouxeram de volta as mudanças desejadas.

Depois de todas as respostas elaboradas acima, esta é uma dica ingênua, mas pode salvar alguém que não pensou nisso primeiro, como eu.

Em desespero, tentei pressionar CTRL-Z no meu editor (LightTable), uma vez em cada guia aberta - isso felizmente recuperou o arquivo nessa guia, ao seu último estado antes do git reset --hard. HTH.

olange
fonte
1
Exatamente a mesma situação, faltou adicionar o índice e fez o hard reset e nenhuma das soluções funcionou acima. Este foi um movimento SMART, obrigado, usei Ctrl + Z e salvei meu dia. Meu editor: SublimeText. Um em cima do meu lado!
Vivek
1

Isso provavelmente é óbvio para os profissionais git por aí, mas eu queria colocá-lo, pois em minha busca frenética não vi isso mencionado.

Eu testei alguns arquivos, e me git reset --hardapavorei um pouco, e então percebi que meu status mostrava todos os meus arquivos ainda testados, bem como todas as suas exclusões não testadas.

Neste ponto, você pode comprometer essas alterações em estágio, contanto que não faça o estágio de suas exclusões. Depois disso, você só precisa reunir coragem para fazer git reset --hardmais uma vez, o que o trará de volta às mudanças que você encenou e agora acabou de comprometer.

Novamente, isso provavelmente não é novidade para a maioria, mas espero que, como me ajudou e não encontrei nada que sugerisse isso, possa ajudar outra pessoa.

ddoty
fonte
1

Meu Deus, puxei meu cabelo até que encontrei essa pergunta e suas respostas. Acredito que a resposta correta e sucinta para a pergunta feita só estará disponível se você juntar dois dos comentários acima, então aqui está tudo em um só lugar:

  1. Como mencionado por chilicuil, execute git reflogpara identificar lá o hash de commit que você deseja retornar

  2. Como mencionado por akimsko, você provavelmente NÃO vai querer escolher a menos que tenha perdido apenas um commit, então você deve executar git reset --hard <hash-commit-you-want>

Observação para usuários do egit Eclipse: Não consegui encontrar uma maneira de fazer essas etapas dentro do Eclipse com egit. Fechar o Eclipse, executar os comandos acima em uma janela de terminal e, em seguida, reabrir o Eclipse funcionou perfeitamente para mim.

Lolo
fonte
0

As soluções acima podem funcionar, no entanto, existem maneiras mais simples de se recuperar disso, em vez de passar pelo gitdesfazer complexo. Eu imagino que a maioria dos git-resets acontecem em um pequeno número de arquivos, e se você já usa o VIM, esta pode ser a solução mais eficiente em termos de tempo. A ressalva é que você já deve estar usando ViM'spersistent-undo, o que deve ser feito de qualquer maneira, porque fornece a capacidade de desfazer um número ilimitado de alterações.

Aqui estão as etapas:

  1. No vim pressione :e digite o comando set undodir. Se você persistent undoativou em seu .vimrc, ele mostrará um resultado semelhante a undodir=~/.vim_runtime/temp_dirs/undodir.

  2. No seu repo, use git logpara descobrir a última data / hora em que você fez o último commit

  3. Em seu shell, navegue até seu undodiruso cd ~/.vim_runtime/temp_dirs/undodir.

  4. Neste diretório use este comando para encontrar todos os arquivos que você alterou desde o último commit

    find . -newermt "2018-03-20 11:24:44" \! -newermt "2018-03-23" \( -type f -regextype posix-extended -regex '.*' \) \-not -path "*/env/*" -not -path "*/.git/*"

    Aqui, "2018-03-20 11:24:44" é a data e a hora da última confirmação. Se a data em que você fez o git reset --hardfor "22/03/2018", use "22/03/2018" e "23/03/2018". Isso ocorre devido a uma peculiaridade de localizar, em que o limite inferior é inclusivo e o superior é exclusivo. https://unix.stackexchange.com/a/70404/242983

  5. Em seguida, vá para cada um dos arquivos, abra-os no vim e faça "20m anteriores". Você pode encontrar mais detalhes sobre "anterior" usando "h anterior". Aqui earlier 20msignifica voltar ao estado do arquivo 20 minutos atrás, supondo que você tenha feito isso git hard --reset20 minutos atrás. Repita isso para todos os arquivos que foram gerados a partir do findcomando. Tenho certeza de que alguém pode escrever um script que combine essas coisas.

alpha_989
fonte
0

Estou usando o IntelliJ e consegui simplesmente percorrer cada arquivo e fazer:

Edit -> reload from disk

Felizmente, eu tinha acabado de fazer um git statuscerto antes de apagar minhas alterações de trabalho, então eu sabia exatamente o que tinha que recarregar.

Forrest
fonte
0

Lembrar sua hierarquia de arquivos e usar a técnica de Mark Longair com a modificação de Phil Oakley produz resultados dramáticos.

Essencialmente, se você pelo menos adicionou os arquivos ao repo, mas não os confirmou, você pode restaurar usando interativamente git show, inspecionar o log e usar o redirecionamento de shell para criar cada arquivo (lembrando seu caminho de interesse).

HTH!

ainthu
fonte
0

Se você revisou recentemente um, git diffentão há outra maneira de se recuperar de incidentes como este, mesmo quando você ainda não testou as alterações: se a saída de git diffainda estiver no buffer do console, você pode simplesmente rolar para cima, copiar e colar o diff em um arquivo e usar a patchferramenta para aplicar o diff a sua árvore: patch -p0 < file. Essa abordagem me salvou algumas vezes.

Confusão
fonte