Programação Por Equipe DevBras

Capa do artigo: Desfazer o último commit no Git e ficar com as alterações na mão

Desfazer o último commit no Git e ficar com as alterações na mão

Como tirar o último commit do histórico sem perder o trabalho, reaproveitar a mensagem e saber quando é preciso usar revert porque o commit já foi para o remoto.

Você deu git commit e, um segundo depois, viu que entrou um arquivo de teste, faltou outro ou a mensagem saiu errada. Apagar a pasta e clonar de novo joga fora o trabalho. A resposta curta: se o commit ainda não foi enviado, rode git reset --soft HEAD~1. O commit sai do histórico e todas as alterações voltam para o stage, prontas para um commit melhor. Se ele já foi enviado e outras pessoas podem ter baixado, use git revert HEAD, que cria um commit novo desfazendo o anterior sem reescrever nada.

A diferença entre os dois caminhos é uma só pergunta: alguém além de você já tem esse commit? Responda isso antes de digitar qualquer coisa.

O que o Git faz quando você desfaz um commit

Um branch no Git é só um ponteiro para um commit. Quando você comita, o ponteiro anda para o commit novo, que aponta para o anterior. HEAD~1 quer dizer "o pai do commit atual". O comando git reset move o ponteiro do branch para trás, e a opção escolhida decide o que acontece com os arquivos:

  • --soft: move só o ponteiro. O stage e os arquivos continuam como estavam no commit desfeito. É o "desfazer o botão de commit".
  • --mixed (o padrão, quando você não passa opção): move o ponteiro e limpa o stage. As alterações continuam nos arquivos, mas soltas, como se você ainda não tivesse dado git add.
  • --hard: move o ponteiro e sobrescreve stage e arquivos com o estado do commit anterior. O que estava só naquele commit some da sua árvore de trabalho.

O commit desfeito não é apagado na hora. Ele fica órfão, sem branch apontando para ele, e o Git guarda a referência no reflog por um tempo. Isso é a sua rede de segurança se algo der errado.

Já o git revert não move ponteiro nenhum para trás. Ele calcula o inverso das mudanças do commit e grava isso como um commit novo. O histórico fica com os dois: o erro e a correção. Para quem já baixou, é só mais um commit chegando, sem conflito de histórico.

Como desfazer um commit que só existe na sua máquina

  1. Confira o estado do branch em relação ao remoto. Se aparecer "ahead 1", o commit é só seu.
  2. Veja o log e confirme que o topo é o seu commit, não algo que você puxou de outra pessoa.
  3. Desfaça com --soft para manter tudo no stage.
  4. Ajuste o que precisa: tire do stage o arquivo indevido, adicione o que faltou.
  5. Comite de novo, reaproveitando a mensagem se ela estava boa.
git status -sb
## main...origin/main [ahead 1]

git log --oneline --decorate -3
a1b2c3d (HEAD -> main) Adiciona filtro por data no relatório
9f8e7d6 (origin/main) Corrige paginação da listagem
...

git reset --soft HEAD~1
git restore --staged config/local.env
git add src/relatorio/filtro.js
git commit -c ORIG_HEAD

O git log --oneline --decorate mostra os nomes dos branches ao lado de cada commit. Se origin/main está no commit de baixo e o seu topo não tem origin marcado, ele ainda não subiu. O ORIG_HEAD é uma referência que o Git grava antes de operações como reset: ela aponta para o commit que você acabou de desfazer. Com git commit -c ORIG_HEAD, o editor abre com a mensagem antiga para você revisar. Com -C ORIG_HEAD (maiúsculo), a mensagem é reaproveitada sem abrir o editor.

Quando a correção é pequena, use amend

Se você só esqueceu um arquivo ou quer trocar a mensagem, não precisa desfazer. Adicione o arquivo e rode git commit --amend. O Git substitui o último commit por um novo com o conteúdo atualizado. Por baixo é a mesma coisa que reset seguido de commit, com a mesma regra: só em commit que não foi enviado.

git add src/relatorio/filtro.test.js
git commit --amend --no-edit

Recuperar o que você desfez sem querer

Desfez o commit errado, ou usou --hard e se arrependeu? O reflog lista por onde o HEAD passou. Ache o hash anterior e volte para ele.

git reflog -5
9f8e7d6 HEAD@{0}: reset: moving to HEAD~1
a1b2c3d HEAD@{1}: commit: Adiciona filtro por data no relatório

git reset --hard a1b2c3d

Atenção: o reflog guarda commits, não arquivos que nunca foram comitados. Alteração que estava só na árvore de trabalho e foi apagada por --hard não volta por aqui.

Como desfazer um commit que já foi enviado

Se o commit já está no remoto, reescrever o histórico obriga um push forçado, e quem já tinha baixado fica com uma cópia que não bate mais com a do servidor. Em branch compartilhado, o caminho padrão é o revert:

git revert HEAD
git push

O revert abre o editor com uma mensagem sugerida. Ela deixa claro no histórico que aquele commit foi desfeito, o que ajuda quem investigar depois.

Se o branch é só seu, como um branch de feature que ninguém mais usa, você pode desfazer com reset e mandar com git push --force-with-lease. Essa opção recusa o envio se o remoto tiver algo que você ainda não viu, o que evita apagar o commit de outra pessoa. Prefira sempre ela ao --force puro. Em branch protegido, o servidor vai recusar de qualquer jeito, e é bom que recuse.

Quando o commit tinha senha ou token

Aqui a regra muda de figura. Se o commit com o segredo já foi para o remoto, desfazer localmente não resolve nada: a cópia continua no servidor, em forks, em clones de colegas e possivelmente em caches de CI. Até um revert deixa o segredo visível no commit anterior. A primeira ação é trocar o segredo no serviço de origem, para que o valor vazado pare de funcionar. Limpar o histórico vem depois e é trabalho à parte. O passo a passo está em segredo no Git se troca antes de limpar.

Se o commit com segredo ainda não subiu, o reset --soft resolve, desde que você tire o arquivo do stage e o coloque no .gitignore antes de comitar de novo.

Erros comuns

  • Usar --hard quando queria --soft. As alterações que não estavam em outro commit somem da árvore de trabalho, e o que nunca foi comitado não volta nem pelo reflog.
  • Rodar reset em commit que já foi enviado e dar push forçado em branch compartilhado. Os colegas passam a ter históricos divergentes e alguém acaba trazendo o commit de volta num merge.
  • Usar --force em vez de --force-with-lease. Se outra pessoa enviou algo no meio tempo, o push apaga o trabalho dela no remoto sem aviso.
  • Não olhar o log antes. Depois de um git pull, o topo pode ser um merge ou o commit de outra pessoa, e você desfaz o que não era seu.
  • Achar que desfazer o commit apaga um segredo vazado. O valor continua no remoto e nas cópias. Sem trocar o segredo, o vazamento segue ativo.
  • Usar HEAD~1 no primeiro commit do repositório. Ele não tem pai e o comando falha. Nesse caso específico, git update-ref -d HEAD remove o commit e mantém os arquivos no stage.

Checklist

  • O git status -sb mostra "ahead" e o commit a desfazer não tem origin marcado no log.
  • O topo do git log é um commit seu, não um merge ou trabalho puxado de outra pessoa.
  • Você escolheu --soft (manter no stage) ou --mixed (manter solto), não --hard por reflexo.
  • Arquivo indevido saiu do stage e entrou no .gitignore antes do novo commit.
  • Para commit já enviado em branch compartilhado, você usou git revert.
  • Se precisou forçar push num branch só seu, foi com --force-with-lease.
  • Se havia segredo no commit enviado, ele já foi trocado no serviço de origem.

Abra agora o terminal no projeto em que você está trabalhando e rode git log --oneline --decorate -3. O commit do topo já tem origin do lado, ou ainda dá para desfazer sem avisar ninguém? E, se tivesse uma senha dentro dele, você saberia qual serviço precisaria trocá-la?

Avalie este artigo

Referência: https://jtemporal.com/desfazendo-o-ultimo-commit-e-reaproveitando-a-mensagem/