Todos os artigos
139 artigos · atualizado semanalmente Veja nossas Ferramentas
Todos os artigos
Tutoriais

Git reset, revert e restore: as diferenças

Reset move ponteiros, revert cria um commit que desfaz outro e restore lida com arquivos. Entenda qual usar antes e depois do push e nunca mais quebre o histórico.

Git reset, revert e restore: as diferenças
COVER · Tutoriais

Você commitou, deu push, e só depois percebeu que metade daquilo não devia ter ido. Ou pior: rodou um comando que viu no Stack Overflow, viu seu working tree esvaziar, e agora está com aquele frio na barriga de quem acabou de apagar uma hora de trabalho. O problema raramente é "como desfazer no Git" -- é "qual dos três comandos que parecem fazer a mesma coisa eu uso sem detonar o histórico de mais alguém". reset, revert e restore resolvem coisas diferentes, e usar o errado é a diferença entre um ajuste limpo e um incidente.

Este guia separa os três de uma vez por todas: o que cada um mexe, quando é seguro, e o que fazer quando já deu push.

Os três fazem coisas diferentes

Antes de qualquer comando, fixe o modelo mental. O Git tem três áreas que importam aqui: o working tree (seus arquivos no disco), a staging area (o que está marcado com git add, também chamada de index), e os commits (o histórico, com o ponteiro HEAD apontando para o topo da branch atual).

  • git reset mexe em ponteiros: move HEAD/branch para outro commit e, dependendo do flag, mexe também no staging e no working tree.
  • git revert cria um commit novo que desfaz as mudanças de um commit anterior. Não apaga nada do histórico -- adiciona por cima.
  • git restore mexe em arquivos: descarta mudanças no working tree ou tira coisas do stage. É o comando moderno que substitui os usos antigos de git checkout e git reset para arquivos.

Resumindo a regra que você vai usar 90% do tempo: reset para histórico local, revert para histórico já compartilhado, restore para arquivos.

git reset e seus três flags

reset move o ponteiro da branch para o commit que você indicar. A diferença está no que ele faz com staging e working tree.

# desfaz o ultimo commit, MANTEM tudo staged (pronto pra recommitar)
git reset --soft HEAD~1

# default: desfaz o commit, tira do stage, MANTEM as mudancas no working tree
git reset --mixed HEAD~1
git reset HEAD~1            # mesma coisa, --mixed e o default

# desfaz o commit e DESCARTA tudo -- working tree volta ao commit alvo
git reset --hard HEAD~1

O caso mais comum é o --soft: você commitou cedo demais, quer reorganizar a mensagem ou juntar arquivos, e não quer perder nada. As mudanças voltam para o stage, prontas para um novo commit. O --mixed (default) é útil quando você quer rever o que foi staged antes de recommitar. O --hard é o único destrutivo: ele reescreve o working tree e joga fora qualquer alteração não commitada. Use com a mão firme.

Um detalhe que economiza dor: reset só é seguro enquanto o histórico é local. Se você já deu push e mais alguém puxou aquele commit, reset reescreve algo que outra pessoa já tem -- e aí a sincronização quebra para todo mundo.

git revert: desfazer sem reescrever

revert é a resposta certa quando o commit já foi compartilhado. Em vez de apagar o commit problemático, ele cria um commit novo que aplica o inverso exato daquelas mudanças.

# cria um novo commit que desfaz o commit indicado
git revert <hash>

# desfazer varios commits de uma vez (cria um commit de revert para cada)
git revert <hash-mais-antigo>..<hash-mais-novo>

# preparar o revert sem commitar na hora (pra ajustar antes)
git revert --no-commit <hash>

A vantagem é que o histórico continua linear e honesto: ele registra que algo foi feito e depois desfeito. Ninguém precisa fazer force push, ninguém fica com o working tree dessincronizado. Em branch compartilhada -- main, develop, qualquer coisa que outra pessoa puxa -- revert é a única opção defensável.

O custo é que o commit ruim continua visível no log. Para a maioria dos times, isso é uma vantagem (rastreabilidade), não um problema.

git restore: o comando moderno para arquivos

Durante anos a gente usou git checkout -- arquivo para descartar mudanças e git reset HEAD arquivo para tirar do stage. Funcionava, mas checkout fazia coisas demais (trocar branch, restaurar arquivo, mover HEAD) e era fácil errar. O Git 2.23 separou essa responsabilidade em restore.

# descarta mudancas no working tree de um arquivo (volta ao ultimo commit)
git restore arquivo.txt

# descarta TUDO no working tree (cuidado: nao tem volta facil)
git restore .

# tira um arquivo do stage, mas MANTEM as mudancas no working tree
git restore --staged arquivo.txt

# restaura um arquivo a partir de um commit especifico
git restore --source=HEAD~2 arquivo.txt

Opinião curta: prefira restore ao velho checkout para arquivos. O verbo diz exatamente o que você está fazendo, e você não corre o risco de trocar de branch por acidente porque digitou um nome que coincidia. git restore --staged é o jeito limpo de "des-adicionar" sem tocar no conteúdo.

Antes de descartar mudanças com restore . ou reset --hard, vale conferir o que você vai perder -- colar o estado atual e o alvo num comparador de diff deixa visível, linha a linha, se você está apagando exatamente o que pretende e nada além disso.

A regra de ouro e o plano por cenário

A decisão quase sempre se resume a uma pergunta: isso já foi pra um lugar compartilhado?

  • Só local, ainda não deu push: reset à vontade. --soft se quiser recommitar, --hard se quer mesmo apagar.
  • Já deu push numa branch compartilhada: revert. Nada de reset --hard seguido de force push numa branch que os outros usam -- isso reescreve o histórico debaixo dos pés deles e gera conflito na próxima vez que alguém puxar.
  • É só um arquivo, não um commit inteiro: restore (ou restore --staged se a questão for só o stage).

A regra de ouro, em uma frase: nunca faça reset --hard em histórico já pushado e compartilhado -- use revert.

Para o panorama completo de como desfazer cada tipo de alteração, do git stash ao amend, veja o guia como desfazer alterações no Git.

A rede de segurança: git reflog

Aqui está a parte que tira o frio da barriga. Mesmo depois de um reset --hard, o commit "perdido" quase sempre ainda existe. O Git mantém um log de todos os lugares por onde o HEAD passou -- o reflog.

# mostra o historico de movimentos do HEAD
git reflog

# achou o hash do commit que sumiu? volte pra ele
git reset --hard <hash>

# ou crie uma branch nova a partir dele, sem mexer na atual
git branch recuperado <hash>

O reflog registra cada commit, checkout, reset e merge dos últimos 90 dias (default). Quando alguém diz "perdi meu commit", a resposta na imensa maioria dos casos é: git reflog, ache o hash, recupere. Os commits só somem de verdade depois que o garbage collector roda e nenhuma referência aponta para eles -- e isso leva tempo.

Perguntas frequentes

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

reset move o ponteiro da branch e reescreve o histórico -- o commit "some" do log. revert cria um commit novo que desfaz outro, deixando os dois visíveis no histórico. Use reset em trabalho local; use revert em qualquer coisa que já foi pushada e que outra pessoa pode ter puxado.

git reset --hard apaga meu trabalho pra sempre?

Quase nunca de forma definitiva e imediata. Commits que existiam ficam acessíveis via git reflog por cerca de 90 dias. O que o --hard realmente perde de forma irrecuperável são mudanças que nunca foram commitadas (working tree e stage sujos), porque essas não têm entrada no reflog. Por isso o hábito de commitar cedo é barato.

Quando usar git restore em vez de git checkout?

Sempre que o alvo for um arquivo, e não uma branch. restore foi criado justamente para separar "restaurar arquivos" de "trocar de branch", que o checkout misturava. Use git restore arquivo para descartar mudanças no working tree e git restore --staged arquivo para tirar do stage.

Posso desfazer um git revert?

Pode -- um revert é só mais um commit, então você pode reverter o revert (git revert <hash-do-revert>) ou, se ainda for local, usar reset para tirar ele do histórico. Como revert nunca reescreve commits antigos, desfazê-lo é seguro e não afeta o histórico de mais ninguém.

Takeaway

Três comandos, três trabalhos. reset mexe em ponteiros e é para histórico local -- --soft guarda staged, --mixed guarda no working tree, --hard joga fora. revert cria um commit que desfaz outro e é a única escolha defensável em branch compartilhada. restore é o comando moderno para arquivos: descarte de working tree ou unstage. A regra de ouro -- nunca reset --hard em histórico pushado, use revert -- evita 90% dos incidentes. E quando der ruim mesmo assim, git reflog quase sempre traz de volta o que você achou que tinha perdido.

RD
Autor
Rafael Duarte
Desenvolvedor backend com passagem por fintech e SaaS B2B — trabalhou em times que escalaram APIs de zero a milhões de requisições. Carrega cicatrizes de produção suficientes para ter opiniões fortes sobre ferramentas, padrões e decisões de arquitetura. Não é acadêmico: leu a RFC do UUID quando precisou escolher entre v4 e v7 para uma tabela de alta escrita.
Ver perfil