Só pra complementar e dar mais detalhes sobre o reflog.
O Git mantém um registro chamado "reference logs" (também chamado de "reflogs"), que gravam quando a "ponta" de um branch (basicamente, o commit para o qual ele aponta) é alterado no seu repositório local.
Para exemplificar, vamos começar com um repositório vazio, e depois criamos alguns commits (para deixar mais curto, omiti algumas saídas dos comandos):
$ mkdir project
$ cd project/
$ git init
# criar o primeiro e segundo commits
$ echo first >> file.txt && git add . && git commit -m"first"
$ echo second >> file.txt && git commit -am"second"
# ver histórico
$ git log --oneline
899ed6c (HEAD -> master) second
d142056 first
Agora podemos rodar git reflog
e ver os reflogs:
$ git reflog
899ed6c (HEAD -> master) HEAD@{0}: commit: second
d142056 HEAD@{1}: commit (initial): first
Basicamente, este histórico mostra como o HEAD
mudou (do mais recente para o mais antigo). Também podemos ver a notação HEAD@{N}
, que específica o enésimo valor anterior do HEAD
(ou seja, HEAD@{0}
é valor atual, HEAD@{1}
é o anterior, etc).
Essas referências inclusive podem ser usadas em qualquer comando que espera um commit/referência como argumento. Por exemplo, git log HEAD@{1}
, git diff HEAD@{1}
, git checkout HEAD@{1}
, etc. HEAD@{N}
usa os reflogs para procurar pelo commit que corresponde ao enésimo valor anterior do HEAD
, e funciona como se você colocasse o hash do commit diretamente.
Vale notar que por isso, HEAD@{0}
é o mesmo que HEAD
.
Os reflogs também gravam quando vc muda para outro branch:
# criar um branch e mudar para ele
$ git checkout -b somebranch
$ git reflog
899ed6c (HEAD -> somebranch, master) HEAD@{0}: checkout: moving from master to somebranch
899ed6c (HEAD -> somebranch, master) HEAD@{1}: commit: second
d142056 HEAD@{2}: commit (initial): first
# adicionar commit no branch
$ echo branch >> file.txt && git commit -am"branch"
$ git reflog
d0e4769 (HEAD -> somebranch) HEAD@{0}: commit: branch
899ed6c (master) HEAD@{1}: checkout: moving from master to somebranch
899ed6c (master) HEAD@{2}: commit: second
d142056 HEAD@{3}: commit (initial): first
$ git log --oneline
d0e4769 (HEAD -> somebranch) branch
899ed6c second
d142056 first
Note como ele grava a mudança do branch master
para somebranch
. Vale notar também que todas as referências HEAD@{N}
também mudam (ex: HEAD@{0}
apontava para o commit 899ed6c
, agora aponta para d0e4769
). Os números são atualizados a todo o momento, sendo que {0}
é sempre o mais recente.
Continuando com o exemplo, digamos que outra pessoa atualizou o repositório remoto e eu quero puxar essas alterações:
$ git checkout master
$ git pull origin master
$ git reflog
0ff62c8 (HEAD -> master, origin/master) HEAD@{0}: pull origin master: Fast-forward
899ed6c HEAD@{1}: checkout: moving from somebranch to master
d0e4769 (origin/somebranch, somebranch) HEAD@{2}: commit: branch
899ed6c HEAD@{3}: checkout: moving from master to somebranch
899ed6c HEAD@{4}: commit: second
d142056 HEAD@{5}: commit (initial): first
PS: eu pulei as partes em que configurei o remote e adicionei o commit 0ff62c8
nele.
Enfim, o exemplo foi para mostrar que git pull
também atualiza o HEAD
, e portanto esta mudança é gravada nos reflogs.
Como podemos ver, o reflog é uma maneira geral de saber como o HEAD
mudou no seu repositório local (ou seja, se vc clonar novamente o remoto, cada clone terá seu próprio reflog).
Outras formas
Vale lembrar que o reflog, apesar de ser uma solução geral, não é a única forma de recuperar informações sobre "branches perdidos". Comandos específicos podem ter informações adicionais que podem ser úteis.
Por exemplo, o git pull
que fizemos acima também criou duas referências (FETCH_HEAD
e ORIG_HEAD
):
$ git log FETCH_HEAD --oneline -1
0ff62c8 (HEAD -> master, origin/master) another
$ git log ORIG_HEAD --oneline -1
899ed6c second
Segundo a descrição da documentação (em tradução livre):
FETCH_HEAD
: branch que foi puxado do repositório remoto na última vez que git fetch
foi executado. Obs: ao executar git pull
, o git fetch
também é executado, por isso esta referência foi criada.
ORIG_HEAD
: é criado por comandos que movem o HEAD
de uma maneira que o Git considera "drástica" (git am
, git merge
, git rebase
, git reset
), e contém a posição do HEAD
antes da operação, para que vc possa facilmente voltar para onde o branch estava antes do comando rodar. Obs: após o fetch
, git pull
faz um merge ou rebase (dependendo da configuração ou opções da linha de comando), por isso esta referência foi criada.
Portanto, FETCH_HEAD
aponta para o branch remoto que foi puxado do repositório remoto (neste caso, origin/master
, e corresponde ao commit 0ff62c8
), e ORIG_HEAD
aponta para onde o HEAD
antes de fazer o merge com 0ff62c8
. Podemos ver isso no histórico:
$ git log --oneline
0ff62c8 (HEAD -> master, origin/master) another
899ed6c second
d142056 first
Na mesma documentação são citadas outras referências especiais, como o MERGE_HEAD
, que grava os commits que estão sendo "mergeados" para o seu branch. Geralmente ele só existe durante o merge, e é apagado depois que ele termina. Serve portanto para vc olhar durante um conflito de merge, por exemplo.
Estas referências especiais são úteis para obter mais detalhes sobre a situação do HEAD
durante operações específicas. Mas de maneira geral, a solução genérica é verificar os reflogs.
Curiosidades
A documentação também descreve outras maneiras de obter posições anteriores do HEAD
(todas usando o reflog por debaixo dos panos). Por exemplo, HEAD@{yesterday}
, HEAD@{5.minutes.ago}
ou HEAD@{2024-08-21T08:30:00}
para buscar onde o HEAD
estava em um instante específico no passado.
E vale lembrar que esta notação não é exclusiva do HEAD
, pois funciona com qualquer outro branch. Ou seja, dá para fazer coisas como master@{yesterday}
(para onde o master apontava ontem) ou somebranch@{2}
(o segundo valor mais recente do branch somebranch
).
E se a referência for omitida, será usado o branch atual. Por exemplo, se o branch atual é o master
, então @{yesterday}
será equivalente a master@{yesterday}
. Mas atenção: isso não necessariamente é o mesmo que HEAD@{yesterday}
, porque o HEAD
poderia estar apontando para um branch diferente naquele instante em particular.
Como curiosidade, @
é um atalho para o HEAD
.
Atenção: se usar números negativos, é completamente diferente. @{-N}
significa "o enésimo branch que fiz checkout antes do atual". E vc não pode colocar uma referência antes, ou seja, HEAD@{-1}
não funciona. Números negativos sempre se referem ao branch que fiz checkout anteriormente. Como curiosidade, git checkout -
é um atalho para git checkout @{-1}
(desde a versão 1.6.2).
Por fim, vale lembrar mais uma vez que o reflog é armazenado localmente, e não é compartilhado com nenhum repositório remoto.
Mesmo se vc clonar o mesmo repositório remoto em outra pasta da mesma máquina, cada clone terá seu próprio reflog. Ou seja, HEAD@{N}
não será necessariamente o mesmo em cada um dos clones.
Por fim, se vc usar git-worktree
, cada worktree também terá seu próprio reflog.