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.
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 resetmexe em ponteiros: moveHEAD/branch para outro commit e, dependendo do flag, mexe também no staging e no working tree.git revertcria um commit novo que desfaz as mudanças de um commit anterior. Não apaga nada do histórico -- adiciona por cima.git restoremexe em arquivos: descarta mudanças no working tree ou tira coisas do stage. É o comando moderno que substitui os usos antigos degit checkoutegit resetpara 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.--softse quiser recommitar,--hardse quer mesmo apagar. - Já deu push numa branch compartilhada:
revert. Nada dereset --hardseguido 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(ourestore --stagedse 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.
- 01 O que é CDN: por que ela acelera sites no mundo inteiro Entenda o que é uma CDN, como edge servers e cache reduzem latência e por que ela acelera sites no mundo inteiro — além de DDoS, WAF e TLS.
- 02 Salt, pepper, bcrypt e Argon2id: como proteger senhas de verdade Em 2012, o LinkedIn expôs 117mi de senhas. SHA-1 sem salt — 90% quebradas em 4h. Entenda o que cada camada de proteção resolve e por que Argon2id é a escolha certa hoje.