Como desfazer alterações no Git com segurança: reset, revert, restore e reflog
Qual comando usar para desfazer alterações no Git em cada situação — reset, revert, restore, stash e reflog — sem perder trabalho.
Você editou um arquivo errado, commitou algo pela metade ou fez git add em coisa que não devia — e agora o estômago aperta na hora de digitar o próximo comando. O medo é justo: alguns comandos de "desfazer" no Git apagam trabalho de forma silenciosa e irreversível, enquanto outros são perfeitamente seguros. A diferença entre git checkout, git restore, git reset e git revert não é estética: é a diferença entre recuperar a calma e perder uma tarde de código. Este guia mostra qual comando usar em cada situação, sempre priorizando os caminhos que não destroem nada.
Antes de qualquer coisa: descubra em que estado você está
Quase todo erro de "desfazer" vem de aplicar o comando certo no estado errado. O Git tem três áreas, e o comando correto depende de onde sua alteração está:
- Diretório de trabalho (working directory): você editou o arquivo, mas não rodou
git add. - Staging (index): você já rodou
git add, mas ainda não commitou. - Histórico (commits): você já rodou
git commit.
O comando que diz a verdade sobre isso é git status. Rode-o antes de desfazer qualquer coisa:
git status
Ele literalmente te diz qual comando usar ("use git restore to discard changes", "use git restore --staged to unstage"). Se você está começando agora e esses três estados ainda soam abstratos, vale a pena passar antes pelo nosso guia de Git para iniciantes, que explica o ciclo add → commit → push com calma. Aqui o foco é o oposto: como voltar atrás.
Desfazer alterações que ainda não foram commitadas
Tirar um arquivo do staging (depois do git add)
Você adicionou ao stage um arquivo por engano, mas quer manter as edições. Isso é seguro — nada é apagado:
git restore --staged arquivo.js # Git 2.23+
git reset HEAD arquivo.js # forma clássica, mesmo efeito
O arquivo volta para "modificado, mas não staged". Seu trabalho continua intacto.
Descartar edições no arquivo (perigo: isso apaga)
Aqui mora a primeira armadilha real. Se você quer jogar fora as alterações de um arquivo e voltar ao último commit:
git restore arquivo.js # Git 2.23+
git checkout -- arquivo.js # forma antiga
Esse comando descarta permanentemente o que não foi commitado. Não há reflog para edições não commitadas. Antes de rodar, pergunte-se: "tenho certeza de que não quero nada disso?". Se a dúvida bater, faça um git stash em vez de descartar — ele guarda tudo numa gaveta recuperável:
git stash # guarda as mudanças e limpa o working directory
git stash pop # traz de volta quando quiser
git stash é o "desfazer com rede de segurança" mais subestimado do Git.
Desfazer commits: reset vs. revert
Aqui está a decisão mais importante do guia. A regra de ouro:
Commit que ainda NÃO foi enviado (push) →
git reset. Commit que JÁ foi enviado e compartilhado →git revert.
git reset — para reescrever histórico local
git reset move o ponteiro do branch para um commit anterior. Tem três modos, e entender a diferença evita perda de trabalho:
git reset --soft HEAD~1 # desfaz o commit, MANTÉM tudo no staging
git reset --mixed HEAD~1 # (padrão) desfaz o commit, mantém edições no working dir
git reset --hard HEAD~1 # desfaz o commit E APAGA as alterações
--soft: o caso mais comum. "Commitei cedo demais / mensagem ruim." O commit some, mas seus arquivos ficam prontos para um novo commit. Seguro.--mixed: o commit some e as mudanças voltam para o working directory, fora do stage. Bom para reorganizar o que vai em cada commit. Seguro.--hard: destrutivo. Apaga as alterações do working directory. Só use quando tem 100% de certeza de que não quer nada daquilo.
Para corrigir só a mensagem do último commit, nem precisa de reset:
git commit --amend -m "mensagem correta"
git revert — para desfazer sem reescrever histórico
git revert não apaga nada: ele cria um novo commit que desfaz as mudanças de um commit anterior. É a escolha obrigatória quando o commit já está no remoto e outras pessoas podem tê-lo puxado.
git revert abc1234 # cria um commit que inverte abc1234
Por que não usar reset num branch compartilhado? Porque reset reescreve a linha do tempo. Se você fizer reset + push --force num branch que o colega já puxou, o histórico dele e o seu divergem, e o próximo pull vira um campo minado. revert mantém todo o histórico — quem está auditando enxerga tanto o erro quanto a correção, o que costuma ser desejável.
A rede de segurança que quase ninguém usa: git reflog
Se você fez um reset --hard e o sangue gelou, calma. O Git mantém um registro de para onde o HEAD apontou nas últimas ações: o reflog. Commits "perdidos" por reset ainda existem por semanas antes do garbage collector limpá-los.
git reflog # lista o histórico de movimentos do HEAD
git reset --hard HEAD@{1} # volta para onde você estava antes do erro
O reflog recupera commits que sumiram do git log. Ele não recupera, porém, edições que nunca foram commitadas nem stashadas — por isso git restore e reset --hard em arquivos não commitados são os comandos mais perigosos do conjunto.
Tabela rápida de decisão
| Situação | Comando seguro |
|---|---|
| Tirar arquivo do stage | git restore --staged arquivo |
| Descartar edição não commitada | git stash (recuperável) ou git restore arquivo (apaga) |
| Corrigir mensagem do último commit | git commit --amend |
| Desfazer último commit, manter código | git reset --soft HEAD~1 |
| Desfazer commit já no remoto | git revert <hash> |
| Recuperar de um reset --hard | git reflog + git reset --hard HEAD@{n} |
Perguntas frequentes
Como desfazer o último commit sem perder as alterações?
Use git reset --soft HEAD~1. O commit é removido do histórico, mas todas as suas mudanças permanecem no staging, prontas para um novo commit. Se quiser que as mudanças voltem para o working directory (fora do stage), use git reset --mixed HEAD~1. Em nenhum dos dois casos seu código é apagado — só o --hard faz isso.
Qual a diferença entre git reset e git revert?
git reset reescreve o histórico: ele move o ponteiro do branch para trás e os commits desfeitos somem da linha do tempo. É ideal para erros locais que ninguém viu ainda. git revert não reescreve nada — cria um novo commit que anula um commit anterior, preservando todo o histórico. Use revert sempre que o commit já tiver sido enviado para um branch compartilhado.
git checkout e git restore fazem a mesma coisa?
Em parte. O antigo git checkout era sobrecarregado: servia tanto para trocar de branch quanto para descartar alterações de arquivos, o que confundia muita gente. A partir do Git 2.23, essa segunda função virou git restore (descartar/restaurar arquivos) e a troca de branch virou git switch. Funcionalmente git restore arquivo e git checkout -- arquivo são equivalentes, mas git restore é mais explícito e menos arriscado.
Dá para recuperar alterações depois de um git reset --hard?
Depende do estado em que estavam. Se as mudanças chegaram a virar commit, sim: git reflog mostra o ponto anterior e você volta com git reset --hard HEAD@{1}. Se eram edições nunca commitadas nem stashadas, não — elas não deixam rastro recuperável. Por isso a recomendação é stashar antes de qualquer comando destrutivo.
O que levar deste guia
A segurança no Git não vem de decorar comandos, e sim de duas perguntas antes de digitar: "minha alteração está no working dir, no stage ou já commitada?" e "esse commit já foi para o remoto?". Com a primeira você escolhe entre restore e reset; com a segunda, entre reset e revert. Reserve --hard e push --force para quando tiver certeza absoluta, use git stash como rede sempre que houver dúvida, e lembre que o git reflog é seu seguro contra commits perdidos. Desfazer no Git deixa de ser assustador quando você sabe que, na grande maioria dos casos, nada é realmente irreversível.
- 01 Convertendo CSV gigantes para JSON sem travar a aba Streams, Web Workers e um truque de chunking que mantém o conversor responsivo mesmo com 200MB de dados.
- 02 chmod, chown e umask no Linux: permissões explicadas com exemplos de servidor Entenda chmod (octal e simbólico), chown e umask com profundidade suficiente para resolver qualquer Permission denied em servidor — sem adivinhação.