Como Desfazer Commits Locais e Remotos no Git com Segurança
Desfaça commits do Git com segurança usando reset, revert, reflog e force-with-lease sem perder trabalho ou quebrar branches compartilhados.
Como Desfazer Commits Locais e Remotos no Git com Segurança
Desfazer um commit no Git é fácil. Desfazer a coisa certa, sem perder trabalho ou surpreender todos os outros no branch, é a parte que exige julgamento.
A primeira pergunta não é "qual comando eu executo?" É "quem viu este commit?" Se o commit existe apenas no seu laptop, você geralmente pode reescrevê-lo com git reset ou git commit --amend. Se o commit já foi enviado para um branch que outras pessoas usam, prefira git revert. Isso mantém o histórico intacto e cria um novo commit que desfaz a alteração ruim.
Antes de mexer no histórico, tire um instantâneo rápido de onde você está:
git status
git branch backup-antes-de-desfazer
git log --oneline --decorate -5
Esse branch temporário é um seguro barato. Se você resetar demais ou mudar de ideia, o commit antigo ainda tem um nome.
Se o commit não foi enviado
Para um commit local que você não enviou, git reset geralmente é a ferramenta mais limpa. Ele move o ponteiro do seu branch para trás. O modo que você escolhe decide o que acontece com os arquivos.
Use --soft quando a mensagem do commit estava errada ou você esqueceu um arquivo pequeno:
git reset --soft HEAD~1
Seu último commit desaparece, mas as alterações permanecem em staged. Você pode adicionar o arquivo faltante e commitar novamente:
git add arquivo-faltante.yml
git commit -m "Atualizar configuração de deploy"
Use o reset misto padrão quando quiser as alterações de volta na sua árvore de trabalho, sem staged:
git reset HEAD~1
Esse é o comando do dia a dia "desfazer este commit, mas manter minhas edições". É útil quando um commit deveria realmente se tornar dois commits menores, ou quando você commitou uma declaração de depuração junto com código real.
Use --hard apenas quando você realmente quiser descartar as alterações locais:
git reset --hard HEAD~1
Isso redefine os arquivos rastreados para o commit anterior. Ele não pergunta educadamente se você quis dizer isso. Se você tem trabalho não commitado em arquivos rastreados, ele pode desaparecer da árvore de trabalho. Verifique git status primeiro, e faça stash ou branch de qualquer coisa que você possa querer de volta.
Para uma pequena correção no commit local mais recente, git commit --amend é geralmente melhor do que reset:
git add arquivo-corrigido.js
git commit --amend
Isso substitui o último commit por um novo. A mesma regra se aplica: altere livremente antes de enviar; tenha cuidado depois de enviar.
Se o commit já foi enviado
Em um branch compartilhado, use git revert a menos que você tenha uma forte razão para reescrever o histórico. Revert cria um novo commit que aplica o patch oposto.
git revert a1b2c3d
Esse comando abre seu editor com uma mensagem gerada. Salve, e o Git cria um novo commit. O commit original permanece no histórico, que é exatamente o que você quer em main, master, develop, branches de release, e qualquer branch que colegas de equipe possam ter puxado.
Se você precisar reverter vários commits consecutivos, pode fazer isso em um lote preparado:
git revert --no-commit HEAD~3..HEAD
git status
git commit -m "Reverter alterações recentes de deploy"
Leia o intervalo com cuidado. HEAD~3..HEAD significa os últimos três commits, não incluindo o HEAD~3 em si. Em caso de dúvida, liste os commits primeiro:
git log --oneline HEAD~3..HEAD
Commits de merge precisam de uma decisão extra. Um merge tem mais de um pai, então o Git precisa saber qual lado deve ser tratado como linha principal:
git revert -m 1 <sha-do-commit-de-merge>
A maioria das equipes usa -m 1 ao reverter um merge de branch de feature a partir do branch alvo, mas não execute isso cegamente. Olhe o commit de merge com git show --summary <sha> e confirme a ordem dos pais.
Se você enviou a coisa errada e precisa removê-la
Às vezes, revert não é suficiente. Se você enviou um segredo, um binário enorme, ou dados privados de cliente, um revert apenas remove da árvore mais recente. O conteúdo sensível ainda existe no histórico. Isso se torna um problema de resposta a incidentes, não apenas um problema de limpeza do Git.
Para segredos, rotacione a credencial primeiro. Depois remova os dados do histórico com uma ferramenta adequada de reescrita de histórico, como git filter-repo, BFG Repo-Cleaner, ou um processo específico de remoção de segredos do host. Coordene com o proprietário do repositório e assuma que qualquer pessoa com acesso pode ter buscado o commit ruim antes de você removê-lo.
Para um commit normal errado no seu próprio branch de feature, reescrever o branch remoto pode ser aceitável. Reset localmente, depois force push com lease:
git reset --hard HEAD~1
git push --force-with-lease origin meu-branch-de-feature
--force-with-lease é a forma mais segura porque se recusa a atualizar o remoto se alguém enviou novo trabalho desde seu último fetch. Ainda é um force push. Ainda reescreve o branch. Use-o em branches de feature pessoais ou coordenados, não casualmente em branches compartilhados.
Um bom hábito antes de force push é:
git fetch origin
git log --oneline --left-right --graph origin/meu-branch-de-feature...meu-branch-de-feature
Isso mostra o que existe apenas no remoto e o que existe apenas localmente. Se o lado esquerdo contém commits de outra pessoa, pare e converse com eles.
Recuperando com reflog
git reflog é o comando que salva muitas tardes ruins. Ele registra onde seu HEAD local e referências de branch apontaram recentemente. Se você resetou para o lugar errado, deletou um branch, ou alterou o commit errado, o reflog geralmente conhece o SHA do commit antigo.
git reflog
Você pode ver algo como:
7cc8a91 HEAD@{0}: reset: movendo para HEAD~1
2b41f0d HEAD@{1}: commit: adicionar retry em torno da etapa de deploy
Para recuperar o commit antigo, crie um branch nele:
git branch trabalho-deploy-recuperado 2b41f0d
Prefiro criar um branch em vez de executar imediatamente outro hard reset. Isso te dá uma alça estável, e você pode inspecionar o trabalho recuperado calmamente:
git show trabalho-deploy-recuperado
git switch trabalho-deploy-recuperado
Reflog é local. O reflog do seu colega não conterá seus commits locais perdidos, e hosts remotos podem não expor o mesmo caminho de recuperação. Ele também é podado ao longo do tempo de acordo com as configurações de coleta de lixo do Git, então trate-o como uma rede de segurança recente, não armazenamento de arquivo.
Um guia prático de decisão
Se você commitou cedo demais e não enviou, use git reset --soft HEAD~1 ou git reset HEAD~1.
Se você commitou o arquivo errado e quer as edições sumidas localmente, use git reset --hard HEAD~1 apenas depois de verificar git status.
Se o commit ruim já está em um branch compartilhado, use git revert <sha>.
Se seu branch de feature é remoto mas só você o usa, git reset mais git push --force-with-lease é geralmente aceitável.
Se o commit contém um segredo ou dado sensível, não confie em revert. Rotacione o segredo, reescreva o histórico com a ferramenta certa, e coordene a limpeza.
O fluxo de trabalho mais seguro para desfazer no Git é chato: inspecione primeiro, faça um branch de backup, escolha o comando baseado em se o commit é compartilhado, e use reflog quando precisar se recuperar da sua própria tentativa de recuperação.