Comparação

git reset e git revert: como desfazer um commit sem quebrar o histórico do time

Atualizado: · Skillaria.Top

O git reset move o ponteiro da branch para outro commit e reescreve o histórico, por isso serve apenas para commits locais que ainda não foram enviados. O git revert cria um novo commit com as alterações inversas e não mexe no histórico, por isso é a forma segura de desfazer um commit que já está no servidor.

Resumindo: o git reset volta a branch para trás e finge que os commits extras nunca existiram. O git revert deixa o histórico como está e adiciona um novo commit que desfaz as alterações do antigo. O primeiro serve para o trabalho local; o segundo, para tudo o que já foi para o repositório compartilhado. Se você lembrar só dessa regra, já vai evitar a maioria dos desastres com Git.

A seguir veremos como os dois comandos funcionam, qual a diferença entre os modos do reset, como desfazer um commit depois do push, como recuperar commits "perdidos" e onde entra o git clean.

As três áreas que os comandos de desfazer alteram

Para entender a diferença, lembre das três áreas do Git: o diretório de trabalho (os arquivos no disco), o índice (staging area, o que vai entrar no próximo commit) e o histórico (os commits apontados pela branch e pelo HEAD). Cada comando de desfazer atua sobre um conjunto diferente dessas áreas.

ComandoHistóricoÍndiceDiretório de trabalhoSeguro em branch compartilhada
git restore arquivonãonãosimsim
git restore --staged arquivonãosimnãosim
git reset --softsimnãonãonão
git reset --mixed (padrão)simsimnãonão
git reset --hardsimsimsimnão
git revertadiciona um commit——sim
git cleannãonãoapaga arquivos não rastreadossim, mas é irreversível

Como funciona o git reset

O comando git reset <commit> move o ponteiro da branch atual (e o HEAD junto) para o commit indicado. Os commits "depois" dele não são apagados fisicamente, mas deixam de aparecer na branch. O modo define o que acontece com o índice e com os arquivos.

git reset --soft: desfazer o commit mantendo as alterações no índice

A branch se move, mas o índice e os arquivos continuam como estavam. Todas as alterações dos commits desfeitos ficam prontas para commit. É a forma ideal de reescrever a mensagem ou juntar os últimos commits em um só.

git reset --soft HEAD~3
git commit -m "feat: formulário de cadastro"

Os três últimos commits viraram um só, e o conteúdo dos arquivos não mudou.

git reset --mixed: desfazer o commit e o índice

É o modo padrão: git reset HEAD~1 equivale a git reset --mixed HEAD~1. A branch se move, o índice é limpo e as alterações ficam no diretório de trabalho como não preparadas. Útil quando você commitou coisa demais e quer decidir de novo o que adicionar e em quais partes.

git reset HEAD~1
git status
# Changes not staged for commit:
#   modified:   app.py
#   modified:   config.yml

git reset --hard: voltar tudo

Branch, índice e diretório de trabalho ficam iguais ao commit indicado. Todas as alterações não commitadas em arquivos rastreados são perdidas sem aviso. É o único modo do reset que pode realmente destruir o seu trabalho.

git reset --hard HEAD~1          # descartar o último commit e todas as alterações
git reset --hard origin/main     # deixar a branch igual à do servidor

Antes de um --hard, vale rodar git status e git stash se houver algo importante. O que foi commitado dá para recuperar (veja abaixo); o que não foi, não.

git reset em um único arquivo

Com um caminho, o reset não move a branch, só altera o índice: git reset app.py tira o arquivo da staging area. O equivalente moderno é git restore --staged app.py, que é mais claro. As flags --soft e --hard não funcionam com caminhos.

Como funciona o git revert

O git revert <commit> calcula as alterações do commit indicado, aplica o inverso delas e cria um novo commit. Linhas adicionadas são removidas, linhas removidas voltam. O histórico só cresce para frente, nada é reescrito, então os colegas não precisam consertar nada do lado deles.

git log --oneline
# a3f9c21 (HEAD -> main) docs: README
# 7be41d0 feat: cache de respostas
# 1c02e8f fix: timeout das requisições

git revert 7be41d0
# [main 5d8e2aa] Revert "feat: cache de respostas"
#  2 files changed, 3 insertions(+), 41 deletions(-)

O Git abre o editor com uma mensagem do tipo Revert "..." e a linha This reverts commit 7be41d0.... Uma boa prática é acrescentar o motivo da reversão.

Flags úteis do revert

  • --no-edit — aceita a mensagem padrão sem abrir o editor.
  • -n (--no-commit) — aplica as alterações inversas no índice sem criar commit. Assim dá para desfazer vários commits com um único commit.
  • git revert A..B — desfaz um intervalo de commits (cada um em um commit de revert separado, do mais novo para o mais antigo).
  • -m 1 — obrigatório para commits de merge: indica qual pai é a linha principal.
git revert --no-commit HEAD~3..HEAD
git commit -m "Revert: desfazer três commits do experimento"

Revert de um commit de merge

Um merge tem dois pais, e o Git não sabe em relação a qual calcular as alterações "inversas"; por isso, sem -m, ele dá o erro is a merge but no -m option was given. Normalmente a branch principal é o primeiro pai:

git revert -m 1 9f1e2ab

A pegadinha: se depois você quiser fazer merge da mesma branch de novo, o Git vai considerar os commits dela já integrados e não vai trazer as alterações. A solução é primeiro fazer revert do próprio commit de revert e só então trazer as novas alterações.

git reset vs git revert: qual escolher

Critériogit resetgit revert
O que faz com o históricoreescreve (os commits somem da branch)adiciona um novo commit
Commit já enviado (push)exige push --force, atrapalha os colegasgit push normal
Desfazer um commit do meio do históricosó junto com todos os seguintespode ser pontual
Rastro no históriconão ficafica, dá para ver quem desfez e por quê
Cenário típicolimpar commits locais antes do pushreverter na main o commit que quebrou produção

Regra simples: se alguém além de você já viu o commit, use revert; se não, reset. A exceção é a sua feature branch pessoal que ninguém baixou: nela é aceitável reescrever o histórico e fazer git push --force-with-lease.

Por que reset --hard em uma branch compartilhada é má ideia

Imagine: você fez git reset --hard HEAD~2 na main e git push --force. Os colegas continuam com esses dois commits. No próximo git pull, o Git vai tentar fazer merge da branch deles com a sua, e os commits "apagados" voltam, agora com um commit de merge inútil. Pior ainda se alguém já tinha enviado commits por cima: o force push simplesmente os apaga.

Por isso, em branches protegidas (main, release), o force push costuma ser bloqueado nas configurações da plataforma. Se reescrever for inevitável, use git push --force-with-lease: ele recusa o envio se surgiram no servidor commits que você ainda não viu.

Cenários comuns para desfazer

Como desfazer o último commit que ainda não foi enviado

git reset --soft HEAD~1   # alterações no índice, dá para commitar de novo
git reset HEAD~1          # alterações nos arquivos, índice vazio
git reset --hard HEAD~1   # alterações descartadas

Se você só precisa corrigir a mensagem ou adicionar um arquivo esquecido, não precisa de reset: git add esquecido.txt e git commit --amend.

Como desfazer um commit depois do push

git revert <hash>
git push

Essa é a resposta para a busca comum "git reverse commit": não existe um comando git reverse no Git; quem cria o commit "inverso" é justamente o git revert.

Como deixar a branch igual à do servidor

git fetch origin
git reset --hard origin/main

Útil quando a branch local ficou bagunçada depois de um rebase malsucedido ou de experimentos. Todos os commits locais que não estão no servidor somem da branch.

Como recuperar commits depois de um reset errado

O Git registra cada movimento do HEAD no reflog. Depois do reset, os commits continuam no banco de objetos por pelo menos algumas semanas (por padrão, entradas inalcançáveis do reflog são mantidas por 30 dias).

git reflog
# 1c02e8f HEAD@{0}: reset: moving to HEAD~2
# a3f9c21 HEAD@{1}: commit: docs: README
# 7be41d0 HEAD@{2}: commit: feat: cache de respostas

git reset --hard a3f9c21   # ou HEAD@{1}

O reflog é local: existe só no seu clone e não é enviado ao servidor.

git clean: apagar arquivos não rastreados

O git reset --hard não mexe nos arquivos que o Git não rastreia: arquivos novos que não foram adicionados ao índice, artefatos de build, logs. Para eles existe o git clean. O comando é irreversível: os arquivos apagados não vão para o reflog nem para a lixeira.

git clean -n      # dry run: mostra o que será apagado
git clean -f      # apaga arquivos não rastreados
git clean -fd     # o mesmo, mais pastas não rastreadas
git clean -fdx    # mais arquivos ignorados (.gitignore): node_modules, dist
git clean -i      # escolha interativa

Sem -f, o Git se recusa a rodar por padrão (configuração clean.requireForce). Comece sempre com -n, principalmente com -x: ele apaga até o seu .env local, se estiver no ignore.

A combinação para voltar totalmente ao último commit: git reset --hard && git clean -fd.

Erros comuns

  • reset --hard no lugar de --soft. O que foi commitado o reflog salva; o que não foi está perdido. O hábito de fazer git stash antes de experimentar resolve o problema.
  • revert sem entender as dependências. Se commits posteriores mexeram nas mesmas linhas, o revert gera conflito. Resolva como qualquer outro: edite os arquivos, git add, git revert --continue; desistiu — git revert --abort.
  • Confundir restore e reset. Para desfazer alterações em um arquivo, não é preciso dar reset na branch inteira: git restore arquivo.
  • force push na main. Melhor um revert e um push normal, mesmo que o histórico fique um pouco mais longo.

Resumo

O git reset é a ferramenta para o seu próprio histórico, ainda não publicado: --soft mantém as alterações no índice, --mixed nos arquivos, --hard descarta tudo. O git revert é a forma segura de desfazer em branches compartilhadas, deixando um rastro honesto no histórico. O git clean termina de limpar os arquivos não rastreados, mas não perdoa erros. Os cenários de desfazer com restore, reset e revert são praticados no curso de Git.

Perguntas frequentes

O git reset apaga commits para sempre?

Não, os commits continuam no banco de objetos e podem ser acessados pelo git reflog até que o garbage collector os remova — por padrão, depois de 30 dias para entradas inalcançáveis.

Dá para desfazer um git revert?

Sim, o commit de revert é um commit normal: dá para desfazê-lo com outro git revert ou, se ainda não foi enviado, removê-lo com git reset.

Qual a diferença entre git reset e git checkout?

O git reset move o ponteiro da branch atual, enquanto o git checkout (ou git switch) muda o HEAD para outra branch ou commit sem alterar a branch em si.

O que significam HEAD~1 e HEAD^?

Os dois indicam o pai do commit atual; HEAD~3 é o terceiro ancestral seguindo os primeiros pais, e HEAD^2 é o segundo pai de um commit de merge.

Como desfazer um git clean?

Pelo Git, não tem como: os arquivos não rastreados apagados nunca estiveram no repositório, por isso rode sempre primeiro git clean -n.

Aprenda este tema em vídeo

Este tema é abordado em profundidade, com prática, no curso “Git do zero ao desenvolvedor confiante”.

Ir para o curso