Recuperando Commits Perdidos com o Git Reflog
Recupere commits perdidos no Git com reflog: use git reflog para achar commits órfãos, desfazer resets e rebases e restaurar branches.
Para recuperar um commit perdido, execute git reflog, copie o hash do commit desejado e restaure-o com git checkout -b recovered <hash>.
Você queria digitar HEAD~1, digitou HEAD~2 e viu três horas de trabalho desaparecerem do git log. Aqueles poucos segundos entre pressionar enter e lembrar que o reflog existe são os piores do Git. Quando você executa git reset --hard, estraga um rebase ou apaga uma branch, seus commits quase nunca são destruídos: eles apenas ficam órfãos, e seus hashes continuam registrados no seu reflog. Este guia apresenta a receita rápida de recuperação para os três cenários que causam o pânico e, em seguida, os limites concretos que vale a pena conhecer para você não perder o trabalho uma segunda vez. Todos os comandos abaixo podem ser copiados e colados.
Pontos Principais
- O
git reflogregistra cada movimentação doHEADno seu repositório local (commit, checkout, reset, rebase, merge), então um commit “perdido” geralmente está a umgit reset --hard <hash>de distância de voltar. - A recuperação mais segura é
git checkout -b recovered <hash>: reconstrua o commit em uma nova branch e inspecione-o antes de tocar na sua branch real. - O reflog não pode recuperar alterações não commitadas do diretório de trabalho, porque o Git nunca as registrou em uma ref.
- Por padrão, o Git mantém as entradas alcançáveis do reflog por 90 dias e as inalcançáveis por 30 dias, então recupere rapidamente e evite executar
git gcaté ter seus commits de volta. - O reflog é estritamente local e nunca é enviado (push), então ele pode resgatar o seu próprio trabalho perdido, mas não os commits não enviados de um colega de equipe.
Como funciona o Git reflog?
O reflog do Git é um registro local de cada posição que as pontas das suas branches e outras refs ocuparam. Todo commit, checkout, reset, rebase e merge move o HEAD e é registrado. Assim, quando um commit se torna “perdido” (ou seja, nenhuma branch ou tag aponta mais para ele), ele está apenas órfão, não deletado, e seu hash continua no reflog esperando para ser reanexado.
Execute o comando sem argumentos para ver o histórico do HEAD:
git reflog
b58145c HEAD@{0}: reset: moving to HEAD~2
dd7c37e HEAD@{1}: commit: one more commit
b5b3286 HEAD@{2}: checkout: moving from main to feature
b58145c HEAD@{3}: commit: very important commit
Leia cada linha como: hash curto, o índice HEAD@{n} (quantas movimentações atrás essa entrada está) e uma descrição da operação. Na saída acima, HEAD@{0} é o reset destrutivo que acabou de acontecer, e HEAD@{1} (dd7c37e) é o commit do qual ele se afastou, aquele que você quer de volta. Nos bastidores, git reflog show executa a mesma coisa que git log -g --abbrev-commit --pretty=oneline, e aceita qualquer ref, então git reflog show main mostra o histórico de uma branch específica.
Discover how at OpenReplay.com.
Recuperando-se de um hard reset acidental
Um git reset --hard move o ponteiro da sua branch, mas deixa o commit antigo intacto no object store. Encontre-o no reflog e aponte novamente para ele. O commit imediatamente anterior ao reset é aquele que você perdeu, geralmente HEAD@{1}.
git reflog
# HEAD@{0}: reset: moving to HEAD~2
# HEAD@{1}: commit: one more commit <-- this hash
git checkout -b recovered dd7c37e
A recuperação mais segura é inspecionar antes de sobrescrever: execute git checkout -b recovered <hash> para reconstruir o commit em uma nova branch, verifique-o com git log e só então decida se vai mover sua branch real. Quando tiver certeza, você pode mover a branch diretamente:
git reset --hard dd7c37e # or: git reset --hard HEAD@{1}
Aviso de risco duplo: o git reset --hard descarta quaisquer alterações não commitadas atuais na sua árvore de trabalho. Se sua árvore estiver suja, use git stash ou o caminho da nova branch primeiro. Caso contrário, o comando de recuperação causa uma segunda perda. Imediatamente após um reset, você também pode usar git reset --hard ORIG_HEAD, porque o git reset registra a ponta anterior da branch em ORIG_HEAD antes de se mover.
Desfazendo um rebase malfeito
Um rebase reescreve o histórico, e um conflito resolvido incorretamente pode descartar commits silenciosamente. O reflog guarda a ponta pré-rebase. Depois de um rebase malfeito, git reset --hard ORIG_HEAD traz sua branch de volta ao ponto de partida. Mas o ORIG_HEAD é sobrescrito pelo próximo reset, rebase ou merge, então, se você já executou outro comando desses desde então, encontre a ponta pré-rebase no git reflog.
git reflog
# HEAD@{0}: rebase (finish): returning to refs/heads/feature
# HEAD@{1}: rebase (pick): one more commit
# HEAD@{2}: rebase (start): checkout main
# HEAD@{3}: checkout: moving from main to feature <-- pre-rebase tip
git reset --hard HEAD@{3}
Procure a entrada rebase (start); a linha imediatamente anterior a ela é sua branch como estava antes do rebase. Se você quiser apenas commits específicos que o rebase descartou, em vez de desfazer tudo, pegue os hashes deles no reflog e reaplique-os:
git cherry-pick <hash>
Recuperando uma branch deletada
Deletar uma branch remove a ref, não os commits. O último commit dela ainda aparece no reflog do HEAD (de quando você fez checkout nela por último), então encontre esse hash e recrie a branch nele.
git reflog
# HEAD@{0}: checkout: moving from example-branch to main
# HEAD@{1}: commit: very important commit <-- last commit on deleted branch
git checkout -b example-branch b5b3286 # or HEAD@{1}
O git checkout -b <branch> <hash> recria a branch apontando para o commit recuperado, com o histórico intacto. Se você lembra parte de uma mensagem de commit, mas não o hash, git log --oneline -g --grep='<fragment>' busca no reflog por ela. (git switch -c é o equivalente moderno de checkout -b; ambos funcionam, e o checkout não está descontinuado.)
O que o Git reflog não consegue recuperar?
O reflog pode recuperar qualquer coisa que tenha movido o HEAD ou a ponta de uma branch (commits, resets, rebases, branches deletadas), mas ele não consegue recuperar alterações não commitadas do diretório de trabalho, porque o Git nunca as registrou em uma ref. Se uma alteração nunca foi commitada, não existe atualização de ref para o reflog referenciar, então buscá-la no reflog não retornará nada.
| O reflog PODE recuperar | O reflog NÃO PODE recuperar |
|---|---|
Commits órfãos por causa de reset --hard | Edições não commitadas na árvore de trabalho |
| Commits descartados por um rebase malfeito | Alterações no stage, mas não commitadas |
| Branches locais deletadas (recentes) | Commits não enviados de um colega de equipe |
| Sua visão local de um remoto que sofreu force-push | Entradas já expiradas e removidas pelo gc |
Outras três restrições regem a recuperação:
- É local e temporário. O reflog existe apenas no seu diretório
.gite nunca é enviado, então ele resgata o seu próprio trabalho, mas não os commits não enviados de um colega de equipe. - As entradas expiram. Por padrão, o Git mantém as entradas alcançáveis do reflog por 90 dias e as inalcançáveis por 30 dias, via
gc.reflogExpireegc.reflogExpireUnreachable. Commits órfãos sobrevivem até a expiração mais uma passagem de garbage collection, então recupere rapidamente e evite executargit gcaté terminar. - Recupere primeiro em uma nova branch. Faça do
git checkout -b recovered <hash>sua ação padrão, para poder verificar antes de alterar uma branch compartilhada. E escreva mensagens de commit descritivas: o reflog as exibe, então “Fix login bug” é bem mais fácil de identificar do que algo vago.
Bônus (depois de um force-push): o servidor remoto não tem um reflog que você possa ler, mas sua ref de tracking local tem. Para recuperar commits apagados de um remoto por um force-push, verifique seu registro local com git reflog show origin/<branch>. A entrada imediatamente anterior ao force-push contém a ponta antiga.
Conclusão
O reflog é a rede de segurança local do Git, e ele transforma quase todo momento de “perdi meu trabalho” em uma correção de dois comandos: leia o log, aponte novamente para o hash. A única coisa que ele não pode resgatar é o trabalho que você nunca commitou, então a lição duradoura é commitar cedo e com frequência, o que garante que sempre haverá uma entrada de reflog para encontrar. Na próxima vez que o terminal fizer seu estômago afundar, execute git reflog antes de qualquer outra coisa.
Perguntas Frequentes
Qual é a diferença entre git reflog e git log?
O git log mostra o histórico de commits alcançável a partir da ponta de uma branch, seguindo os ponteiros de pai, então commits órfãos nunca aparecem nele. O git reflog mostra cada posição que o HEAD ou uma ref ocupou no seu repositório local — commits, checkouts, resets, rebases e merges — incluindo commits para os quais nenhuma branch aponta mais. É por isso que o reflog pode encontrar um commit depois de um hard reset quando o git log não consegue: o commit está órfão, mas a atualização de ref dele continua registrada.
O git reflog funciona depois de clonar um repositório?
Não. O reflog é armazenado apenas no seu diretório .git local e nunca é enviado ou baixado, então um clone novo começa com um reflog vazio, cobrindo apenas as ações realizadas depois do clone. Ele não pode mostrar movimentações do HEAD anteriores ao clone nem revelar o histórico local de um colega de equipe. O reflog resgata o seu próprio trabalho perdido no seu próprio repositório; ele não é uma ferramenta de recuperação compartilhada ou respaldada pelo remoto.
Posso recuperar um commit depois que as entradas do git reflog expiraram?
Não pelo reflog em si. Por padrão, entradas alcançáveis expiram após 90 dias e as inalcançáveis após 30 dias, e assim que uma passagem de garbage collection remover os objetos órfãos, eles realmente se foram. Antes disso, você às vezes ainda consegue encontrar o hash com git fsck --lost-found, que lista commits pendentes (dangling) no object store. A atitude confiável é recuperar rapidamente e evitar executar git gc até ter seus commits de volta.
Como encontro um commit perdido se eu não souber o hash dele?
Busque diretamente no reflog. Execute git log --oneline -g --grep='fragmento' para filtrar entradas do reflog por parte de uma mensagem de commit, ou git reflog --date=relative para varrer as entradas por tempo e identificar a operação que você quer desfazer. Para commits órfãos sem entrada no reflog, git fsck --lost-found lista commits pendentes que você pode inspecionar com git show antes de recuperá-los em uma nova branch com git checkout -b recovered <hash>.