15%

Poupe 15% em todos os serviços

Teste as suas habilidades e obtenha Desconto em qualquer plano

Utilizar o código:

Skills
Começar a trabalhar
22.10.2024

Git Reset vs. Git Checkout vs. Git Revert: Um Guia Técnico Completo

Compreender a diferença entre git reset, git checkout e git revert é essencial para qualquer desenvolvedor que trabalhe com controlo de versões. Em resumo: git reset reescreve o histórico movendo o ponteiro HEAD; git checkout navega entre branches, commits ou ficheiros sem alterar o histórico; e git revert desfaz um commit criando um novo commit inverso, mantendo o histórico intacto. Escolher o comando errado — especialmente numa branch partilhada — pode corromper o histórico de commits da sua equipa ou causar perda irreversível de dados.

Este guia vai além da sintaxe superficial para explicar a mecânica interna do Git, o estado exato da sua árvore de trabalho e índice após cada operação, e os cenários do mundo real onde cada comando é a escolha correta (ou catastroficamente errada).

Como o Git Gere o Estado: As Três Árvores

Antes de comparar comandos, precisa de um modelo mental sólido de como o Git rastreia o estado. O Git opera em três camadas distintas:

  • Diretório de trabalho — os ficheiros que vê e edita no disco.
  • Área de staging (índice) — um snapshot do que irá para o próximo commit.
  • Histórico de commits (HEAD) — a cadeia de objetos de commit imutáveis armazenados em .git/.

Cada comando de desfazer no Git tem como alvo uma ou mais destas camadas. A confusão entre reset, checkout e revert quase sempre resulta de não saber quais camadas um determinado comando afeta.

git reset: Reescrever o Histórico Local

git reset move o ponteiro HEAD da branch atual para um commit especificado. Dependendo do sinalizador de modo que passar, também pode atualizar o índice e o diretório de trabalho. É uma operação de reescrita de histórico e deve ser tratada como destrutiva quando usada com --hard.

Modos de Reset Explicados

git reset --soft <commit>   # Move HEAD only; index and working tree unchanged
git reset --mixed <commit>  # Move HEAD + reset index; working tree unchanged (default)
git reset --hard <commit>   # Move HEAD + reset index + reset working tree
ModoHEAD MoveÍndice ReiniciadoÁrvore de Trabalho ReiniciadaAlterações Preservadas
--softSimNãoNãoStaged e unstaged
--mixedSimSimNãoApenas unstaged
--hardSimSimSimNenhuma — perdida permanentemente

Casos de Uso Práticos

Squashing de commits antes de um push. Se tiver três commits de trabalho em progresso confusos que ainda não foram enviados, git reset --soft HEAD~3 recolhe-os de volta para o índice para que possa fazer um novo commit como um único commit limpo.

Remover um ficheiro acidentalmente adicionado do staging. Executar git reset HEAD <file> (ou git reset sem uma referência de commit) remove um ficheiro do índice sem tocar no diretório de trabalho — idêntico em efeito a git restore --staged <file> no Git 2.23+.

Recuperar de um merge local mal executado. git reset --hard ORIG_HEAD restaura o estado que existia imediatamente antes de um merge, porque o Git escreve o HEAD pré-merge em ORIG_HEAD automaticamente.

Armadilha Crítica: Push Após um Reset

Se fizer reset a uma branch que já foi enviada para um remoto e depois fizer force-push, todos os colaboradores cuja branch local rastreia esse remoto terão um histórico divergente. Esta é uma das causas mais comuns de commits perdidos em ambientes de equipa. Nunca execute git reset seguido de git push --force numa branch partilhada sem coordenação explícita com a equipa.

# Dangerous on shared branches — use only on private/local branches
git reset --hard HEAD~2
git push --force origin feature/my-branch

git checkout: Navegação Sem Modificação do Histórico

git checkout é um comando multiuso que muda a árvore de trabalho para corresponder a uma branch, um commit específico ou um único ficheiro. Não modifica o histórico de commits. No Git 2.23 e posterior, as suas responsabilidades foram divididas em git switch (para branches) e git restore (para ficheiros), mas git checkout permanece totalmente funcional e ainda é dominante em ambientes de produção.

Referência de Sintaxe

git checkout <branch_name>          # Switch to an existing branch
git checkout -b <new_branch>        # Create and switch to a new branch
git checkout <commit_hash>          # Enter detached HEAD state at a specific commit
git checkout -- <file_name>         # Discard working directory changes to a file
git checkout <commit_hash> -- <file> # Restore a single file from a specific commit

Estado de HEAD Desanexado: O Que Significa Realmente

Quando executa git checkout <commit_hash>, o Git move HEAD para apontar diretamente para um objeto de commit em vez de uma referência de branch. Isto é chamado de estado de HEAD desanexado. Quaisquer commits que faça neste estado não são acessíveis a partir de nenhuma branch — ficam órfãos e eventualmente serão recolhidos pelo garbage collector do Git, a menos que crie uma branch para os capturar.

git checkout 4f7a2c1          # HEAD now points directly to commit 4f7a2c1
git checkout -b hotfix/patch  # Rescue those commits by creating a branch

Um cenário comum no mundo real: um desenvolvedor faz checkout de um commit antigo para testar uma regressão, acidentalmente faz uma correção, faz commit, e depois volta para main — perdendo a correção completamente porque nunca foi anexada a uma branch.

Restaurar um Único Ficheiro do Histórico

Uma das formas mais subutilizadas de git checkout é a restauração direcionada de ficheiros:

git checkout HEAD~3 -- src/config/database.php

Isto extrai database.php tal como existia três commits atrás diretamente para o seu índice e diretório de trabalho, sem tocar em nenhum outro ficheiro ou mover HEAD. É o equivalente cirúrgico de uma mudança completa de branch.

git revert: Desfazer com Segurança para Histórico Partilhado

git revert cria um novo commit que aplica o diff inverso de um commit especificado. O commit original permanece no histórico intocado. Este é o único mecanismo de desfazer seguro para commits que já foram enviados para uma branch remota partilhada, porque não reescreve o histórico — estende-o.

Referência de Sintaxe

git revert HEAD                  # Revert the most recent commit
git revert <commit_hash>         # Revert a specific commit by hash
git revert HEAD~3..HEAD          # Revert a range of commits (creates multiple revert commits)
git revert -n <commit_hash>      # Stage the revert without committing (--no-commit)

O Sinalizador –no-commit: Agrupar Reverts

Ao reverter múltiplos commits, criar um commit de revert por commit original pode poluir o log. O sinalizador -n (ou --no-commit) coloca todas as reversões em staging sem fazer commit, permitindo agrupá-las num único commit de revert:

git revert -n HEAD~4..HEAD
git commit -m "Revert: roll back broken authentication refactor"

Reverts de Commits de Merge Requerem Cuidado Extra

Reverter um commit de merge requer especificar o pai principal com o sinalizador -m, porque o Git precisa de saber qual lado do merge tratar como o histórico "correto":

git revert -m 1 <merge_commit_hash>

Aqui, -m 1 designa o primeiro pai (tipicamente a branch para a qual fez merge) como a linha principal. Omitir este sinalizador num commit de merge fará com que o Git lance um erro. Este é um obstáculo comum ao reverter um merge de release incorreto em pipelines CI/CD.

Comparação Lado a Lado: Reset vs. Checkout vs. Revert

Critério`git reset``git checkout``git revert`
Modifica o histórico de commitsSimNãoNão (adiciona ao mesmo)
Afeta o diretório de trabalhoCom --hard ou --mixedSimSim (via novo commit)
Afeta a área de staging (índice)Sim (exceto --soft)Apenas com forma de ficheiroNão
Seguro em branches partilhadas/remotasNãoSim (apenas leitura)Sim
Cria um novo commitNãoNãoSim
ReversívelParcialmente (via ORIG_HEAD)SimSim
Lida com commits de mergeNãoSim (navegação)Sim (com -m)
Equivalente moderno Git 2.23+Igualgit switch / git restoreIgual

Quando Usar Cada Comando: Matriz de Decisão

Use git reset quando:

  • Está a trabalhar numa branch local, não enviada e quer limpar, fazer squash ou descartar commits.
  • Precisa de remover ficheiros do staging antes de um commit.
  • Quer desfazer um merge local que não foi partilhado.
  • É o único desenvolvedor numa branch de funcionalidade e precisa de reescrever o seu histórico antes de abrir um pull request.

Use git checkout quando:

  • Precisa de mudar entre branches durante o desenvolvimento ativo.
  • Quer inspecionar o estado do repositório num commit histórico sem alterar nada.
  • Precisa de restaurar um único ficheiro para o seu estado num commit específico.
  • Está a criar uma nova branch a partir de um ponto específico no histórico.

Use git revert quando:

  • Um commit já foi enviado para uma branch remota ou partilhada.
  • Está a trabalhar em equipa e precisa de manter um histórico transparente e auditável.
  • Precisa de desfazer um commit específico que não é o mais recente (revert não linear).
  • O seu projeto requer conformidade ou trilhas de auditoria onde a eliminação do histórico é proibida.

Cenário Avançado: Recuperar de git reset –hard

Se executou acidentalmente git reset --hard e perdeu commits, eles não desapareceram imediatamente. O reflog do Git regista cada posição para a qual HEAD apontou, mesmo após um hard reset:

git reflog
# Output example:
# a1b2c3d HEAD@{0}: reset: moving to HEAD~3
# 7e8f9a0 HEAD@{1}: commit: Add payment gateway integration
# ...

git reset --hard HEAD@{1}   # Restore to the commit before the accidental reset

As entradas do reflog expiram após 90 dias por padrão (gc.reflogExpire), portanto esta janela de recuperação não é infinita. Num servidor de produção ou num ambiente de VPS Hosting a executar um serviço Git auto-hospedado como Gitea ou GitLab, deve garantir que o seu diretório .git está incluído nas rotinas de backup regulares precisamente por causa desta expiração.

Hospedar a Sua Infraestrutura Git

Executar um servidor Git auto-hospedado — seja GitLab CE, Gitea ou Gogs — exige I/O de armazenamento fiável e tempo de atividade consistente. Um único ficheiro pack corrompido ou um push interrompido durante um ciclo git gc pode danificar a integridade do repositório. Para equipas que gerem múltiplos repositórios, um Servidor Dedicado fornece recursos isolados, acesso root completo para ajuste fino das configurações core.packedGitWindowSize e pack.threads do Git, e o throughput NVMe necessário para grandes monorepos.

Para equipas menores ou desenvolvedores individuais que precisam de um ambiente Linux limpo para executar hooks Git, scripts CI e pipelines de deployment, um VPS com cPanel oferece um plano de controlo gerido juntamente com acesso SSH completo — removendo a sobrecarga da administração manual do servidor enquanto mantém a flexibilidade para configurar hooks do lado do servidor Git e controlos de acesso.

Se o seu fluxo de trabalho envolve deployments automatizados acionados por git push, proteger o seu servidor com um Certificado SSL válido é inegociável — tanto para encriptar payloads de webhook como para autenticar remotos Git baseados em HTTPS sem desativar a verificação de certificados.

Principais Conclusões Técnicas

  • git reset --hard é o único comando entre os três que pode causar perda de dados permanente e irrecuperável se o reflog tiver expirado.
  • git revert é o único comando que é seguro usar após um git push sem requerer um force-push.
  • O estado de HEAD desanexado de git checkout <hash> não elimina commits — mas quaisquer novos commits feitos nesse estado ficarão órfãos a menos que execute imediatamente git checkout -b <new_branch>.
  • O sinalizador -n em git revert é crítico para manter um log limpo ao reverter múltiplos commits de uma vez.
  • O Git 2.23+ divide git checkout em git switch e git restore para maior clareza — compreender o comando original torna ambas as alternativas modernas imediatamente intuitivas.
  • Verifique sempre a sua branch atual com git status e git log --oneline -5 antes de executar qualquer operação de desfazer.
  • Em infraestrutura partilhada, aplique regras de proteção de branch (GitHub, GitLab, Gitea suportam todas isto) para bloquear git push --force nas branches main e release ao nível do servidor.

Perguntas Frequentes

Qual é a diferença entre git reset --soft e git reset --mixed?

Ambos movem HEAD para o commit especificado, mas --soft deixa as suas alterações em staging no índice, enquanto --mixed (o padrão) também limpa o índice, deixando as alterações apenas no diretório de trabalho. Nenhum toca nos ficheiros no disco.

Posso recuperar commits após git reset --hard?

Sim, dentro da janela de expiração do reflog (90 dias por padrão). Execute git reflog para encontrar o hash do commit do estado perdido, depois execute git reset --hard <hash> ou git checkout -b recovery <hash> para o restaurar.

Por que é que git revert num commit de merge requer o sinalizador -m?

Um commit de merge tem dois pais. O Git não consegue determinar qual diff do pai inverter sem que especifique a linha principal. -m 1 diz ao Git para tratar o primeiro pai como o tronco, revertendo as alterações introduzidas pela branch fundida.

git checkout -- <file> é o mesmo que git restore <file>?

Funcionalmente sim — ambos descartam alterações não staged no diretório de trabalho de um ficheiro, restaurando-o a partir do índice. git restore foi introduzido no Git 2.23 como um substituto menos ambíguo, mas git checkout -- <file> produz resultados idênticos em todas as versões do Git.

Quando é que nunca devo usar git reset numa branch?

Nunca use git reset (especialmente com --hard ou --mixed) em qualquer branch que outros desenvolvedores já tenham clonado ou obtido. Fazê-lo diverge o seu histórico local do remoto, exigindo que cada colaborador realize um reset forçado ou re-clone — e arrisca descartar silenciosamente commits que existem apenas nas suas máquinas.

15%

Poupe 15% em todos os serviços

Teste as suas habilidades e obtenha Desconto em qualquer plano

Utilizar o código:

Skills
Começar a trabalhar