Tutorial

git reflog: como recuperar commits e branches perdidos

Atualizado: · Skillaria.Top

O git reflog é um registro local em que o Git anota cada movimento do HEAD e das branches: commits, reset, checkout, rebase, merge. Encontre no git reflog a entrada anterior ao erro, por exemplo HEAD@{2}, e restaure o estado com git reset --hard HEAD@{2} ou crie uma branch com git branch rescue HEAD@{2}.

git reflog é o log de referências (reference log): o Git registra nele cada movimento do HEAD e das pontas das branches. Fez um commit, mudou de branch, rodou reset, rebase ou merge — aparece no reflog uma linha com o hash para onde a referência apontava depois da operação. Por isso, mesmo um commit que não é mais referenciado por nenhuma branch pode ser encontrado e recuperado.

O principal a saber: o reflog é local. Ele existe só no seu clone, não é enviado ao servidor e não vem com o git clone. Em compensação, no seu repositório ele salva você de praticamente qualquer erro com o histórico.

Como ler a saída do git reflog

git reflog
3c4d5e6 (HEAD -> main) HEAD@{0}: reset: moving to HEAD~2
9a8b7c6 HEAD@{1}: commit: Adicionar validação de e-mail
1f2e3d4 HEAD@{2}: commit: Mover config para arquivo separado
3c4d5e6 (HEAD -> main) HEAD@{3}: checkout: moving from feature to main
7b6a5c4 (feature) HEAD@{4}: commit: Rascunho do formulário de login

Cada linha é um estado do HEAD, com o mais recente no topo:

  • hash — o commit para onde o HEAD apontava depois da operação;
  • HEAD@{n} — "onde o HEAD estava n movimentos atrás"; é uma referência completa e pode ser passada para qualquer comando;
  • descrição — o tipo de operação (commit, reset, checkout, rebase, merge, pull, cherry-pick, commit (amend)) e os detalhes.

No exemplo dá para ver: dois commits — "Mover config" e "Adicionar validação" — foram feitos e depois o reset HEAD~2 voltou a branch. No git log eles não aparecem mais, mas no reflog continuam lá.

Reflog de uma branch específica

Cada branch tem seu próprio log. Ele é mais curto e mais útil quando você alternou bastante entre branches:

git reflog show feature
git reflog show origin/main   # como a branch remota mudou a cada fetch

Referências por tempo

Em vez de um número, você pode indicar um momento — o Git encontra para onde a referência apontava naquela hora:

git show main@{yesterday}
git diff main@{"2 hours ago"} main
git log -g --since="1 day ago" --oneline

A flag -g (--walk-reflogs) do git log percorre o reflog em vez das ligações de parentesco e permite usar os formatos e filtros de sempre. Para exibir datas, é prático usar git reflog --date=iso.

Como recuperar commits depois de um git reset --hard

A situação mais comum: você rodou git reset --hard para o commit errado e o trabalho "sumiu". Na verdade, os commits não foram a lugar nenhum — só não estão mais visíveis a partir das branches.

  1. Abra o log: git reflog.
  2. Encontre a linha logo antes de reset: moving to ... — é o estado anterior ao erro. No exemplo acima, HEAD@{1} com o hash 9a8b7c6.
  3. Confira se é isso mesmo: git log --oneline -5 HEAD@{1}.
  4. Restaure a branch: git reset --hard HEAD@{1}.
git reset --hard HEAD@{1}
HEAD is now at 9a8b7c6 Adicionar validação de e-mail

Atenção: a cada operação os números se deslocam. O que era HEAD@{1} vira HEAD@{2} depois do reset. Por isso é mais seguro copiar o hash, e não o número.

Uma limitação importante: o reflog só guarda o que foi commitado. Alterações não commitadas apagadas por reset --hard ou git checkout -- arquivo não voltam pelo reflog. Ainda resta alguma esperança se os arquivos tiverem sido adicionados ao índice — falamos disso mais abaixo.

Como recuperar uma branch apagada

Você apagou a branch com git branch -D feature, e ela tinha commits que não foram enviados. O log da própria branch é apagado junto com ela, mas o log do HEAD continua — e o HEAD passou pelos commits dela enquanto você trabalhava ali.

git reflog | grep feature
7b6a5c4 HEAD@{4}: commit: Rascunho do formulário de login
3c4d5e6 HEAD@{3}: checkout: moving from feature to main

# O último commit da branch é aquele de onde você saiu dela
git branch feature 7b6a5c4

Procure a linha checkout: moving from feature to ...: o hash da entrada anterior (mais abaixo na lista) é o último estado da branch. Aliás, ao apagar, o próprio Git imprime o hash: Deleted branch feature (was 7b6a5c4). — se o terminal ainda estiver aberto, isso basta.

Como desfazer um rebase que deu errado

O rebase reescreve commits, e se no meio do caminho você resolveu mal os conflitos, o resultado pode ficar pior que o original. O reflog guarda o estado anterior ao início do rebase.

git reflog
e1f2a3b HEAD@{0}: rebase (finish): returning to refs/heads/feature
e1f2a3b HEAD@{1}: rebase (pick): Rascunho do formulário de login
c4d5e6f HEAD@{2}: rebase (start): checkout main
7b6a5c4 HEAD@{3}: commit: Rascunho do formulário de login

git reset --hard HEAD@{3}

Você precisa da entrada anterior a rebase (start). Há também um atalho: antes de operações perigosas (rebase, merge, reset), o Git salva a posição anterior na referência ORIG_HEAD, então logo após o rebase funciona git reset --hard ORIG_HEAD. Mas o ORIG_HEAD é sobrescrito pela próxima operação desse tipo, enquanto o reflog guarda todo o histórico.

Como voltar ao commit anterior ao git commit --amend

O --amend não altera o commit, ele cria um novo no lugar. O antigo continua no reflog:

git reflog
5a6b7c8 HEAD@{0}: commit (amend): Adicionar validação de e-mail
9a8b7c6 HEAD@{1}: commit: Adicionar validação de e-mail

git diff HEAD@{1} HEAD          # o que o amend mudou
git reset --soft HEAD@{1}       # volta ao commit antigo; as mudanças do amend ficam no índice

Recuperação segura: branch em vez de reset

Se não tiver certeza de qual commit precisa, não faça reset --hard às cegas. Crie uma branch de resgate — ela não quebra nada e protege o commit encontrado de ser apagado:

git branch rescue HEAD@{5}
git log --oneline rescue
git switch rescue

Depois, os commits da rescue podem ser trazidos com cherry-pick ou merge, e a branch pode ser apagada quando tudo estiver de volta no lugar.

Por quanto tempo o reflog é mantido

As entradas não são eternas. Elas são limpas pelo git gc, que o Git roda sozinho de tempos em tempos. Os prazos são definidos por configurações:

ConfiguraçãoPadrãoO que significa
gc.reflogExpire90 diasPrazo para entradas com commits alcançáveis a partir das branches atuais
gc.reflogExpireUnreachable30 diasPrazo para entradas com commits que nenhuma branch alcança — justamente os "perdidos"
gc.pruneExpire2 semanasDepois de quanto tempo são apagados os próprios objetos inalcançáveis que mais nada referencia

Na prática: um commit perdido fica garantidamente acessível pelo reflog por cerca de um mês. Não adie a recuperação e não rode manualmente git gc --prune=now ou git reflog expire --expire=now --all antes de recuperar o que precisa.

Se o reflog não ajudou: git fsck

Há casos em que o commit não está no reflog: por exemplo, ele foi criado pelo git stash e depois o stash foi apagado com git stash drop, ou as alterações estavam só no índice. Aí procure objetos "soltos" (dangling):

git fsck --lost-found
dangling commit 2b3c4d5e6f...
dangling blob 8e9f0a1b2c...

git show 2b3c4d5e6f            # ver o conteúdo
git branch recovered 2b3c4d5e6f

Commits soltos vindos de um stash apagado têm a mensagem "WIP on ...". Blobs soltos são o conteúdo de arquivos que em algum momento foram adicionados com git add, mas não commitados. Eles não têm nome de arquivo, então é preciso identificá-los pelo conteúdo — mas às vezes é assim que se salva o trabalho depois de um reset --hard. Com a flag --lost-found, o Git ainda os copia para .git/lost-found/.

O que o reflog não faz

  • Não funciona entre máquinas. Se um colega fez force push e apagou seus commits no servidor, procure-os no reflog de quem os tinha localmente — por exemplo, git reflog show origin/main na máquina de qualquer pessoa que tenha feito fetch antes da sobrescrita.
  • Não enxerga o que não foi commitado. Por isso, o melhor seguro antes de uma operação arriscada é um commit ou um git stash.
  • Não guarda o histórico para sempre. Valem os prazos acima, e um clone novo começa com o log vazio.

Na prática: um seguro antes de operações perigosas

O reflog salva, mas recuperar por ele exige atenção: é preciso achar a entrada certa entre dezenas de parecidas. É bem mais simples deixar uma marca antes. Antes de um rebase, de um reset grande ou de experimentos com o histórico, crie uma branch ou tag temporária:

git branch backup/before-rebase
git rebase -i main
# algo deu errado
git reset --hard backup/before-rebase
# deu tudo certo
git branch -D backup/before-rebase

Custa um comando e poupa a caça ao hash. O reflog fica como plano B para quando você se lembrou do seguro tarde demais.

Como achar rápido a entrada certa

Num log longo, filtrar ajuda. Busque pelo tipo de operação ou pelo texto da mensagem do commit e, em caso de dúvida, veja o conteúdo:

git reflog | grep -E "reset|rebase \(start\)"
git log -g --grep="validação" --oneline
git show --stat HEAD@{7}

O git log -g com --grep busca nas mensagens dos commits do reflog, e o git show --stat mostra quais arquivos foram afetados — com isso fica fácil reconhecer o commit certo, mesmo que a mensagem fosse pouco informativa, tipo "wip".

Conclusão

O git reflog é a rede de segurança do Git: tudo o que foi commitado no seu repositório nas últimas semanas pode ser encontrado e recuperado. O roteiro é sempre o mesmo: abrir o log, achar a entrada anterior ao erro, conferir com git log ou git show e então criar uma branch ou fazer reset --hard. O uso de restore, reset, revert e a recuperação depois de erros são praticados no curso de Git.

Perguntas frequentes

Qual a diferença entre git reflog e git log?

O git log segue as ligações de parentesco dos commits e mostra o histórico da branch. O git reflog mostra para onde o HEAD e as branches se moveram no seu repositório, incluindo commits que já não estão no histórico de nenhuma branch.

O reflog vai para o GitHub ou GitLab?

Não, o reflog fica só na pasta local .git/logs e não é transferido no push nem no clone.

Dá para limpar o reflog?

Sim, com git reflog expire --expire=now --all seguido de git gc --prune=now, mas depois disso não será mais possível recuperar commits perdidos.

Onde o reflog fica fisicamente?

Nos arquivos de texto .git/logs/HEAD e .git/logs/refs/heads/nome-da-branch, com uma linha para cada movimento.

O reflog funciona em repositórios bare no servidor?

Por padrão, o reflog vem desativado em repositórios bare (core.logAllRefUpdates), então não dá para contar com ele no servidor.

Aprenda este tema em vídeo

Este tema é abordado em profundidade, com prática, no curso “Git do zero ao desenvolvedor confiante”.

Ir para o curso