Desembarace um Histórico Git Bagunçado
Pegue um repositório cheio de erros reais — um segredo commitado, um merge ruim, um commit errado — e conserte com segurança usando restore, revert, reset, rebase interativo e reflog. O complemento prático do Git Essentials.
Problema
Todo mundo consegue commitar num dia tranquilo. A habilidade que separa um desenvolvedor confiante é o que você faz quando o histórico está uma bagunça: você commitou uma senha, o último commit estava errado, um merge deu ruim e você tem medo de que "consertar" vá destruir trabalho. Neste lab você vai criar de propósito um repositório com esses exatos problemas e então reparar cada um com a ferramenta certa — aprendendo a diferença crucial entre `restore`, `revert` e `reset`, resolvendo um conflito de merge real na mão, limpando commits locais com um rebase interativo e resgatando trabalho "perdido" com `reflog`. Você vai terminar sabendo que, no Git, quase nada é de fato irrecuperável — se você souber onde procurar.
Objetivos
- Ler o estado verdadeiro de um repositório com
git status,git log --oneline --graphegit reflog - Desfazer mudanças não commitadas com
git restore, escolhendo entre--stagede o worktree - Reverter um commit ruim de duas formas:
git revert(seguro, mantém o histórico) vsgit reset(reescreve o histórico) — e saber quando cada um é o certo - Resolver um conflito de merge real editando os marcadores de conflito e completando o merge
- Limpar commits locais com um rebase interativo (
squash,reword) antes de compartilhar - Recuperar um commit que você "perdeu" via
git reflog
Pré-requisitos
- Git 2.30+ instalado (
git --version) e uma identidade básica configurada (git config --global user.name/user.email) - Um terminal (PowerShell, bash ou zsh) e qualquer editor de texto
- Familiaridade com os comandos do dia a dia do curso Git Essentials:
add,commit,branch,merge - Nenhuma conta remota/GitHub necessária — tudo acontece num repo local que você cria no Passo 1
O que você vai construir
Você vai construir um repositório quebrado de propósito e então consertá-lo. Cada problema mapeia
para uma habilidade de recuperação do Git, então no fim você terá uma árvore de decisão mental:
"a mudança não foi commitada → restore; o commit ruim já foi enviado → revert; é só local → reset
ou rebase; perdi um commit → reflog."
A regra de ouro de reescrever o histórico
Uma ideia governa tudo abaixo:
Reescreva livremente o que você não compartilhou. Nunca reescreva o que outros possam ter puxado.
reset e rebase reescrevem o histórico — mudam os IDs dos commits. Isso é perfeito para limpar
seu próprio trabalho local antes de enviar, e perigoso num branch compartilhado (força todo mundo a
conflitos). revert é o oposto: adiciona um novo commit que desfaz um antigo, então o histórico é
preservado e seguro de compartilhar. Manter essa linha clara é todo o ponto do lab.
Como trabalhar neste lab
Rode cada comando e leia a saída — especialmente git status e git log entre os passos, para
você ver cada reparo fazer efeito. Faça os passos em ordem; cada um monta o cenário para o próximo.
Passos
-
Construa o repositório quebrado
Você vai criar a bagunça você mesmo para ela ser reproduzível. Rode estes comandos numa pasta vazia.
mkdir git-rescue && cd git-rescue git init echo "# Notes App" > README.md git add README.md && git commit -m "Initial commit" echo "print('hello')" > app.py git add app.py && git commit -m "Add app" # Erro A: commitar um segredo echo "API_KEY=sk-live-supersecret-123" > config.env git add config.env && git commit -m "Add config" # Erro B: uma mensagem de commit ruim + arquivo lixo echo "temporary junk" > scratch.tmp git add scratch.tmp && git commit -m "asdfasdf" echo "print('goodbye')" >> app.py # uma mudança não commitada que você vai decidir o que fazerVeja a bagunça
git log --oneline --graph git statusVocê agora tem quatro commits e uma mudança não commitada. Os problemas:
-
config.envcom um segredo está no histórico. - O último commit tem uma mensagem lixo (
asdfasdf) e adiciona um arquivo lixoscratch.tmp. - Há uma edição não commitada em
app.pysobre a qual você ainda não decidiu.
Mantenha esta janela aberta — você vai consertar isso um de cada vez.
Entregável deste passo: a saída de
git log --onelinemostrando a bagunça de quatro commits. -
-
Desfaça mudanças não commitadas com restore
O caso mais fácil primeiro: uma mudança que você não commitou e agora quer descartar.
Descarte uma mudança do worktree
Você adicionou
print('goodbye')aoapp.pye desistiu. Ela não está no stage, então:git status # app.py aparece como "modified", fora do stage git restore app.py # descarta a mudança na árvore de trabalho git status # limpo de novo; app.py voltou ao estado commitadogit restore <arquivo>sobrescreve o arquivo com sua versão do último commit. Isso é
destrutivo para trabalho não commitado — não há desfazer, porque o Git nunca gravou aquela
edição. Use só quando você realmente quer a mudança fora.Tire do stage sem perder trabalho
A outra metade do
restorecuida de arquivos no stage. Suponha que você adiciona algo por acidente:echo "draft" > draft.txt git add draft.txt # agora no stage git restore --staged draft.txt # tira do stage, mas MANTÉM o arquivo e o conteúdo git status # draft.txt voltou a "untracked", não sumiuA distinção para lembrar:
-
git restore <arquivo>→ descarta mudanças do worktree (destrutivo). -
git restore --staged <arquivo>→ tira um arquivo da área de stage, mantendo suas edições.
Apague o arquivo solto para a árvore ficar limpa:
rm draft.txt.Entregável deste passo: a saída de
git statusmostrando a árvore limpa após o restore. -
-
Reverta um commit ruim: revert vs reset
Agora os erros já commitados. Há duas filosofias, e escolher certo é a habilidade central.
revert — seguro, mantém o histórico (use para commits compartilhados)
O segredo em
config.envé um erro commitado. Se este branch já tivesse sido enviado, você
não pode reescrever o histórico.git revertcria um novo commit que desfaz o alvo:git log --oneline # ache o hash de "Add config" git revert <hash-do-add-config> # abre um editor para a mensagem do revert; salve git log --oneline # o commit original continua lá + um novo commit "Revert ..."O arquivo
config.envagora é removido pelo novo commit, mas o commit antigo ainda existe no
histórico. (Na vida real um segredo vazado também precisa ser rotacionado — reverter o
esconde do HEAD, não do passado do repo. Mais sobre isso na reflexão.)reset — reescreve o histórico (só para commits locais, não compartilhados)
O commit lixo
asdfasdf(comscratch.tmp) ainda é só local, então você pode reescrevê-lo
para fora com limpeza. Mova o ponteiro do branch para trás, mantendo as mudanças fora do stage:git reset --soft HEAD~1 # desfaz o último commit, MANTÉM as mudanças no stage # ou: git reset HEAD~1 # (mixed, o padrão) desfaz o commit, mantém as mudanças FORA do stageApós um reset mixed,
scratch.tmpvolta como arquivo untracked — apague-o e ele some do
histórico pretendido:rm scratch.tmp git status # limpo; o commit ruim sumiu git log --onelineOs três sabores do reset, que vale memorizar:
Comando Commit desfeito Stage Árvore de trabalho reset --softsim mantido no stage intocada reset(mixed)sim fora do stage intocada reset --hardsim descartado sobrescrita (destrutivo) Regra prática: revert em tudo que já foi enviado; reset só em commits locais.
Entregável deste passo:
git log --onelineapós o revert + reset, mostrando o segredo revertido e o commit lixo fora. -
Resolva um conflito de merge real
Conflitos assustam porque interrompem um merge no meio. Crie um de propósito e finalize-o.
Monte dois branches divergentes
git switch -c feature-title # mude a mesma linha que o branch main também vai mudar echo "print('hello from feature')" > app.py git commit -am "Feature: muda a saudação" git switch main echo "print('hello from main')" > app.py git commit -am "Main: muda a saudação"Os dois branches editaram a mesma linha de
app.py. Agora faça o merge:git merge feature-titleO Git para com
CONFLICT (content): Merge conflict in app.py. Isso não é um erro — é o Git
pedindo que você decida, porque ele não tem como saber qual versão está certa.Leia e resolva os marcadores
Abra
app.py. Você verá os marcadores de conflito:<<<<<<< HEAD print('hello from main') ======= print('hello from feature') >>>>>>> feature-title-
<<<<<<< HEAD…=======→ seu branch atual (main). -
=======…>>>>>>> feature-title→ o branch que está entrando.
Edite o arquivo para o conteúdo final que você quer — mantenha um lado, o outro, ou combine — e
apague as três linhas de marcador. Por exemplo:print('hello from main and feature')Depois marque como resolvido e complete o merge:
git add app.py git commit # completa o merge (a mensagem padrão de merge serve) git log --oneline --graph # veja o commit de merge juntando as duas linhas de históricoSe quiser desistir no meio do conflito,
git merge --abortte devolve ao estado antes do merge.Entregável deste passo: o conteúdo resolvido de
app.pye ogit log --graphmostrando o merge. -
-
Limpe commits locais com rebase interativo, e recupere com reflog
Duas ferramentas poderosas para fechar: polir o histórico local antes de compartilhar, e resgatar trabalho perdido.
Rebase interativo — arrume seus próprios commits
Faça alguns commits pequenos e bagunçados num branch:
git switch -c cleanup-demo echo "line 1" > notes.txt && git commit -am "wip" echo "line 2" >> notes.txt && git commit -am "more wip" echo "line 3" >> notes.txt && git commit -am "fix typo"Três commits barulhentos que deveriam ser um só. Reescreva-os antes de alguém ver:
git rebase -i HEAD~3Um editor abre listando os três commits com
pickna frente de cada. Para combiná-los, mantenha
o primeiro comopicke mude os outros parasquash(ous):pick <hash> wip squash <hash> more wip squash <hash> fix typoSalve e feche; o Git então te deixa escrever uma mensagem combinada limpa (ex.:
Add notes).
Userewordno lugar desquashse você só quer corrigir uma mensagem. Confirme:git log --oneline # os três commits agora são umLembre da regra de ouro: isso reescreve os IDs dos commits, então só faça em commits que você não enviou.
reflog — recupere um commit "perdido"
Rebase e reset movem ponteiros de branch; os commits antigos não são deletados na hora.
reflogé o diário privado do Git de todo lugar por onde o HEAD passou. Simule um desastre e recupere:git reset --hard HEAD~1 # "ops, destruí meu último commit" git log --oneline # sumiu do branch git reflog # cada movimento do HEAD, cada um com um hash # ache a linha do commit que você perdeu, então: git reset --hard <hash-do-reflog> # ou: git cherry-pick <hash> git log --oneline # recuperadoEssa é a rede de segurança que torna o Git perdoável: enquanto um commit existiu, o
reflog
geralmente consegue te levar de volta a ele por semanas, mesmo depois de um reset--hard.Entregável deste passo: o
git log --onelineapós o squash, mais um trecho degit reflogmostrando a recuperação. -
Entregue: documente seu resgate
Capture o antes/depois e seu raciocínio num
SUBMISSION.mddentro do repo.Monte sua entrega
# Lab de Resgate Git — <seu nome> ## 1. A bagunça (Passo 1) <cole o `git log --oneline` inicial com os quatro commits ruins> ## 2. restore O que descartei e a árvore limpa no `git status` depois. ## 3. revert vs reset - O commit do segredo que revertí (hash) e por que usei revert. - O commit lixo que resetei e por que o reset foi seguro aqui. <cole `git log --oneline` após os dois> ## 4. Conflito de merge A linha final resolvida de app.py, e como eu a escolhi. ## 5. Rebase + reflog <cole `git log --oneline` após o squash> <cole a linha do `git reflog` que usei para recuperar> ## Reflexão (3-4 frases) Nas minhas palavras: quando usar revert vs reset, e por que o reflog significa que quase nada no Git é de fato perdido.Entregue
git add SUBMISSION.md git commit -m "Entrega do lab de resgate Git" # faça push para um repo público ou gist, depois entregue essa URLCritério de submissão (autoverificação)
- Você usou
git restore(worktree) egit restore --stagede sabe explicar a diferença - Você reverteu o commit do segredo com
git revert(histórico preservado) - Você removeu o commit lixo com
git resete explicou por que foi seguro (só local) - Você resolveu um conflito de merge real editando marcadores e completando o merge
- Você fez squash de 3 commits em 1 com
git rebase -i - Você recuperou um commit resetado com
--hardusandogit reflog - Sua reflexão diz corretamente a regra revert-vs-reset
O que vem a seguir
Você já consegue reparar quase qualquer problema de histórico local com confiança. No Git
Advanced você leva isso além —stash,worktrees,hookse recuperação de conflitos complexos. - Você usou