Reflog: o histórico de tudo que aconteceu localmente

1. O que é o Reflog e por que ele existe?

O Git mantém dois tipos de histórico: o histórico de commits, que é compartilhado com outros desenvolvedores via push e pull, e o reflog (reference log), que registra todas as ações locais que movem os ponteiros de referência do seu repositório. Enquanto o histórico de commits mostra apenas o estado dos commits em si, o reflog documenta cada operação que alterou onde HEAD ou um branch apontava.

O reflog registra ações como:
- Commits normais
- git reset (qualquer modo: --soft, --mixed, --hard)
- git checkout que move HEAD
- git merge e git rebase
- git commit --amend
- Criação e exclusão de branches

Uma característica fundamental: o reflog é exclusivamente local. Quando você executa git push, o reflog não é enviado para o repositório remoto. Isso significa que o reflog é seu diário pessoal de operações Git, visível apenas no seu clone do repositório.

2. Como visualizar o reflog

O comando básico para acessar o reflog é:

git reflog

A saída padrão mostra uma lista cronológica reversa (mais recente primeiro) com entradas como:

abc1234 HEAD@{0}: commit: Corrige bug no cálculo de juros
def5678 HEAD@{1}: rebase (finish): refatorando módulo de pagamentos
789abcd HEAD@{2}: reset: movendo para antes do merge problemático
123def4 HEAD@{3}: commit: Adiciona testes unitários

Opções úteis incluem:

git reflog --all          # Mostra reflog de todas as referências
git reflog --date=iso     # Exibe datas no formato ISO
git reflog --relative-date # Mostra datas relativas (ex: "2 horas atrás")

A diferença entre git reflog e git log -g é sutil: git log -g mostra o mesmo conteúdo do reflog, mas com formatação de log (autor, data completa, mensagem), enquanto git reflog foca nas referências e ações.

3. Navegando pelo reflog com referências

Cada entrada no reflog pode ser referenciada usando a sintaxe HEAD@{n} ou <nome-do-branch>@{n}. O número n indica a posição da entrada: HEAD@{0} é o estado mais recente, HEAD@{1} é o anterior, e assim por diante.

Você pode usar essas referências em qualquer comando Git que aceite uma revisão:

git show HEAD@{2}              # Mostra o commit apontado por HEAD há 2 ações atrás
git diff HEAD@{1} HEAD@{0}     # Compara o estado atual com o anterior
git checkout HEAD@{3}          # Move HEAD para o estado de 3 ações atrás (detached HEAD)

Importante: entradas do reflog expiram. Por padrão:
- Commits alcançáveis expiram em 90 dias
- Commits não alcançáveis (órfãos) expiram em 30 dias
- Outras referências (como reflog de branches) seguem regras similares

4. Recuperando commits "perdidos" com o reflog

O caso de uso mais valioso do reflog é recuperar trabalho que parecia perdido. Considere este cenário: você executou git reset --hard HEAD~3 e percebeu que descartou commits importantes.

O reflog ainda mantém o registro:

git reflog
# Saída:
# abc1234 HEAD@{0}: reset: movendo para HEAD~3
# def5678 HEAD@{1}: commit: Implementa feature X
# 789abcd HEAD@{2}: commit: Adiciona validação de dados

Para recuperar o commit def5678:

git checkout def5678          # Vai para o commit em detached HEAD
# ou
git cherry-pick def5678       # Aplica o commit no branch atual
# ou
git branch feature-recuperada def5678  # Cria um branch apontando para o commit

Para recriar um branch apagado acidentalmente:

git branch meu-branch HEAD@{5}

Isso cria um branch chamado meu-branch apontando para onde HEAD estava há 5 ações atrás.

5. Reflog de branches específicos vs. HEAD

O comando git reflog sem argumentos mostra o reflog de HEAD. Mas cada branch tem seu próprio reflog:

git reflog main              # Mostra o histórico do branch main
git reflog develop           # Mostra o histórico do branch develop

O reflog de um branch é atualizado apenas quando a ponta do branch se move. Já o reflog de HEAD é atualizado sempre que HEAD muda, incluindo checkouts entre branches.

Exemplo prático:

# Início: no branch main
git checkout -b experimento
# HEAD@{0}: checkout: movendo para experimento
# experimento@{0}: branch: Criado a partir de main

git commit -m "Feature experimental"
# HEAD@{1}: commit: Feature experimental
# experimento@{1}: commit: Feature experimental

git checkout main
# HEAD@{2}: checkout: movendo para main
# (experimento não é atualizado)

6. Boas práticas e limitações do reflog

Quando confiar no reflog:
- Para recuperação de commits perdidos localmente
- Para entender o que aconteceu em uma sessão de trabalho
- Para desfazer operações recentes

Limitações importantes:
- O reflog nunca é enviado para repositórios remotos
- Ele não salva alterações não rastreadas (untracked files)
- Stashes perdidos não aparecem no reflog (use git stash list)
- Entradas expiram automaticamente

Gerenciando o reflog:

# Limpar entradas expiradas manualmente
git reflog expire --expire=now --all

# Remover entradas específicas
git reflog delete HEAD@{5}

# Configurar tempo de expiração
git config gc.reflogExpire 180.days
git config gc.reflogExpireUnreachable 60.days

7. Casos de uso avançados e armadilhas comuns

Desfazendo um git commit --amend:

git commit --amend -m "Mensagem corrigida"
# Oops! Quero a mensagem original
git reflog
# HEAD@{1}: commit (amend): Mensagem corrigida
# HEAD@{2}: commit: Mensagem original
git reset HEAD@{2}  # ou git cherry-pick HEAD@{2}

Recuperando de um rebase interativo problemático:

git rebase -i HEAD~5
# Durante o rebase, algo deu errado
git rebase --abort
# Mas você quer o estado de antes do abort
git reflog
# HEAD@{3}: rebase: parando em commit X
git checkout HEAD@{3}

Armadilha comum: o reflog não substitui backups. Se você clonar um repositório novo, o reflog começa vazio. Além disso, o git gc (coleta de lixo) remove entradas expiradas, então confiar apenas no reflog para recuperação a longo prazo é arriscado.

Para objetos órfãos que não estão mais no reflog, use git fsck --lost-found, que varre o banco de objetos do Git em busca de commits sem referência.

Referências