Você fez um commit que quer desfazer. E agora?
Acontece com todo mundo. Você faz commit cedo demais, inclui um arquivo que não deveria, escreve a mensagem errada ou percebe que o código simplesmente não está certo. A boa notícia: Git foi projetado para lidar com isso. A notícia ainda melhor: na maioria dos casos, você pode desfazer um commit sem perder seu trabalho.
A abordagem correta depende de uma pergunta chave: o commit já foi pushado para um remote, ou ainda é local? Commits locais dão mais flexibilidade. Commits pushados requerem uma estratégia mais segura porque outras pessoas podem já ter pullado suas mudanças.
Desfazendo um commit local com git reset
Se o commit ainda é local (não pushado), git reset é a ferramenta mais comum. Ele move o ponteiro da branch de volta para um commit anterior, efetivamente "descommitando" suas mudanças. O que acontece com essas mudanças depende da flag que você usar.
git reset --soft: desfaz o commit, mantém tudo em staging
git reset --soft HEAD~1
Isso desfaz o último commit mas mantém todas as mudanças em staging (no índice). Seu diretório de trabalho não é tocado. É como se você nunca tivesse executado git commit -- você está de volta ao momento exato antes de commitar.
Quando usar: Você quer mudar a mensagem do commit, adicionar mais arquivos ao commit ou dividir o commit em menores. Seu código está bem, você apenas commitou prematuramente.
git reset --mixed: desfaz o commit, remove as mudanças do staging
git reset --mixed HEAD~1
Este é o comportamento padrão do git reset (você pode omitir a flag --mixed). Desfaz o commit e remove as mudanças do staging, mas seus arquivos permanecem modificados no diretório de trabalho. Nada é perdido.
Quando usar: Você quer repensar o que incluir no commit. Talvez queira colocar em staging apenas alguns arquivos, ou queira revisar as mudanças antes de recommitar.
git reset --hard: desfaz o commit, descarta todas as mudanças
git reset --hard HEAD~1
Isso desfaz o commit e deleta permanentemente todas as mudanças. Seu diretório de trabalho é resetado para corresponder ao commit anterior. Não há como recuperar as mudanças descartadas por operações normais do Git.
Quando usar: Você tem certeza de que não quer as mudanças de forma alguma. Talvez tenha commitado código experimental que não funcionou e quer começar do zero. Use com cautela.
Voltando mais atrás
HEAD~1 refere-se a um commit antes do HEAD atual. Você pode ir mais longe:
git reset --soft HEAD~3
Isso desfaz os últimos três commits, combinando todas as suas mudanças na área de staging. Você também pode apontar para um commit específico pelo seu hash:
git reset --soft a1b2c3d
Corrigindo o último commit com git commit --amend
Se você só precisa ajustar o último commit em vez de desfazê-lo completamente, --amend é a opção mais rápida:
git commit --amend -m "corrected commit message"
Isso substitui o commit anterior por um novo. Você também pode usá-lo para adicionar arquivos esquecidos:
git add forgotten-file.ts
git commit --amend --no-edit
A flag --no-edit mantém a mensagem de commit original. O resultado é um único commit corrigido em vez de uma sequência de commits "ops".
Importante: Como reset, amend reescreve o histórico. Use-o apenas em commits que não foram pushados. Se você fizer amend de um commit pushado e depois force-push, qualquer pessoa que tenha pullado o commit original terá problemas.
Desfazendo um commit pushado com git revert
Uma vez que um commit foi pushado para um remote compartilhado, você não deve reescrever o histórico. Outros desenvolvedores podem ter baseado seu trabalho nesse commit. Em vez disso, use git revert:
git revert HEAD
Isso cria um novo commit que faz exatamente o oposto do commit alvo. Se o commit original adicionou uma linha, o commit de revert a remove. Se deletou um arquivo, o commit de revert o restaura. O commit original permanece no histórico, e o revert é adicionado por cima.
Quando usar: Sempre que precisar desfazer um commit que já foi pushado. É seguro porque adiciona ao histórico em vez de reescrevê-lo.
Revertendo um commit mais antigo
Você pode reverter qualquer commit, não apenas o mais recente:
git revert a1b2c3d
Git tentará criar um commit de revert que desfaz apenas as mudanças daquele commit específico. Se essas mudanças se sobrepõem com trabalho posterior, você pode obter um conflito que precisa de resolução manual.
Revertendo múltiplos commits
Para reverter um intervalo de commits:
git revert HEAD~3..HEAD
Isso cria commits de revert individuais para cada commit no intervalo. Se preferir um único commit de revert, adicione a flag --no-commit e faça o commit manualmente:
git revert --no-commit HEAD~3..HEAD
git commit -m "revert last 3 commits"
Escolhendo a abordagem certa
| Situação | Método | O que acontece com suas mudanças |
|---|---|---|
| Commitou cedo demais, quer re-stagar | git reset --soft HEAD~1 |
Mudanças permanecem em staging |
| Commitou cedo demais, quer retrabalhar | git reset --mixed HEAD~1 |
Mudanças permanecem no diretório de trabalho |
| Commitou algo que quer descartar | git reset --hard HEAD~1 |
Mudanças são deletadas |
| Precisa corrigir a mensagem ou adicionar um arquivo | git commit --amend |
Commit anterior é substituído |
| Precisa desfazer um commit pushado com segurança | git revert HEAD |
Novo commit desfaz as mudanças |
Recuperando-se de erros
Mesmo se você usar git reset --hard e perceber que não deveria, geralmente há uma forma de recuperar. Git mantém um registro de onde HEAD apontou, chamado reflog:
git reflog
Isso mostra uma lista de posições recentes de HEAD. Você pode encontrar o commit que pensou ter perdido e voltar para ele:
git reset --hard HEAD@{2}
O reflog é sua rede de segurança. As entradas são mantidas por 90 dias por padrão, então você tem uma janela generosa para se recuperar de erros.
Desfazendo commits no GitSquid
GitSquid simplifica as operações de desfazer mais comuns. Você pode pressionar Cmd+Z (ou Ctrl+Z no Windows e Linux) imediatamente após commitar para desfazer o último commit, mantendo suas mudanças em staging e prontas para retrabalhar. Para operações mais específicas, clicar com o botão direito em qualquer commit no grafo dá acesso a opções de reset e revert através de um menu de contexto, para que você possa mirar exatamente o commit que quer desfazer sem memorizar flags.
Desfazer um commit Git não precisa ser estressante. Entenda a diferença entre reset, amend e revert, saiba qual se encaixa na sua situação, e você poderá corrigir erros com confiança. A distinção mais importante é simples: se é local, use reset ou amend. Se foi pushado, use revert.