Git advanced

Escopo: transversal. Aplica-se a qualquer linguagem ou stack do projeto. Pré-requisito: git.md: nomenclatura de branches, commits convencionais e PRs.

Rotina prática, limpeza de histórico e recuperação de erros comuns.

Conceitos fundamentais

ConceitoO que é
Rebase (reorganização de commits)Reaplica commits de uma branch sobre outro ponto do histórico, produzindo um histórico linear sem commits de merge (mesclagem) extras
Squash (compactação de commits)Agrupa vários commits em um único antes do merge (mesclagem), mantendo o histórico limpo e navegável
Reflog (registro local de movimentos)Histórico interno de todas as posições que HEAD ocupou; usado para recuperar commits aparentemente perdidos
Stash (área temporária)Área temporária que guarda alterações locais sem criar commit, liberando a branch para outra tarefa
Interactive rebase (rebase interativo)Modo de rebase que permite editar, reordenar, compactar ou descartar commits individualmente antes de enviar para review
git bisect (busca binária de regressão)Percorre o histórico em busca do commit que introduziu um bug; funciona bem apenas com histórico linear e commits atômicos

Rotina convencional

O ciclo correto parte da main atualizada, usa uma branch com um único propósito e termina com o PR (Pull Request, Pedido de Integração) mergeado e a branch removida.

pull main → nova branch → commits atômicos → fetch origin/main → merge origin/main → PR → squash and merge → deletar branch
PassoO que faz
pull mainAtualiza a main local antes de criar a branch; garante que você parte do estado mais recente
nova branchIsola o trabalho em uma branch com um único propósito: uma feature, uma correção ou uma refatoração
commits atômicosRegistra cada mudança lógica separadamente durante o desenvolvimento
fetch origin/mainBaixa as atualizações do remoto sem aplicar nada ainda; permite inspecionar antes de agir
merge origin/mainIncorpora as mudanças da main na branch sem reescrever histórico; conflitos ficam explícitos aqui
PREnvia a branch para review; checks de CI/CD validam antes do merge
squash and mergeCompacta todos os commits da branch em um único commit limpo na main
deletar branchRemove a branch após confirmar que o merge chegou na main e o deploy está estável
❌ Ruim
# trabalhar direto na main
git add .
git commit -m "changes"
git push origin main
✅ Bom
# 1. atualizar main antes de começar
git checkout main
git pull origin main

# 2. criar branch com um único propósito
git checkout -b feat/user-email-verification

# 3. commits atômicos durante o trabalho
git add src/auth/email.js
git commit -m "feat(auth): add email verification token generation"

git add src/auth/email.test.js
git commit -m "test(auth): cover token expiry and reuse scenarios"

# 4. incorporar atualizações da main antes do PR
git fetch origin
git merge origin/main
# sem conflitos: merge commit criado automaticamente
# com conflitos: ver Troubleshooting → Conflitos com a main

# 5. enviar para review
git push origin feat/user-email-verification

# 6. após o merge: aguardar estabilização em produção antes de deletar
git log origin/main --oneline -3

# só então deletar: -d rejeita se a branch não foi mergeada
git branch -d feat/user-email-verification
git push origin --delete feat/user-email-verification

Trocando de branch no meio da tarefa

Quando uma tarefa de maior prioridade interrompe o trabalho em andamento, use Stash para guardar o estado sem criar um commit de WIP no histórico.

stash → trocar de branch → trabalhar → voltar → stash pop → continuar
❌ Ruim
# commit de WIP polui o histórico e precisa ser limpo antes do PR
git add .
git commit -m "WIP"
git checkout feat/other-priority-task
✅ Bom
# guardar o estado atual sem commitar
git stash push -m "wip: email verification form"

# trocar para a tarefa prioritária
git checkout feat/other-priority-task

# ... trabalhar, commitar, abrir PR ...

# voltar para a tarefa original
git checkout feat/user-email-verification
git stash pop

Squash no PR

Um PR com 30 commits fragmentados dificulta debug e git bisect. O squash compacta todo o trabalho da branch em um único commit descritivo na hora do merge (mesclagem).

WIP → fix typo → esqueci de salvar → arrumei → squash → feat(auth): add email verification (#42)
❌ Ruim: histórico fragmentado no merge
WIP
fix typo
esqueci de salvar
arrumei
mais um ajuste
agora vai
feat: email
✅ Bom: GitHub (padrão)

No PR aberto, clique no dropdown ao lado de "Merge pull request" e selecione Squash and merge. O GitHub abre um editor para ajustar a mensagem antes de confirmar; edite antes de clicar em "Confirm squash and merge".

feat(auth): add email verification on signup (#42)

DX no PR (experiência do reviewer)

Pequenos cuidados antes de abrir o PR reduzem o tempo de review e aumentam a chance de aprovação rápida.

PráticaMotivo
Um propósito por PRReviewer entende o escopo sem precisar deduzir o que está relacionado
Título no formato Conventional CommitsDeixa claro o tipo de mudança antes de abrir o diff
Descrição com contexto e decisões não óbviasReviewer não precisa perguntar o que você já sabe
Squash antes do mergeHistórico limpo facilita git bisect e git blame no futuro
Checks verdes antes de pedir reviewNão transfere trabalho de validação para o reviewer
PR pequeno (menos de 400 linhas como referência)Diffs grandes cansam e geram reviews superficiais
❌ Ruim
título: update
descrição: (vazia)
commits: WIP, fix, arrumei, agora vai, update 2
checks: falhando
linhas alteradas: 1.200
✅ Bom
título: feat(auth): add email verification on signup

descrição:
Adiciona verificação de e-mail no cadastro. Token expira em 24h.
Optei por gerar o token no backend para evitar previsibilidade no cliente.

commits: 1 commit limpo via squash
checks: todos verdes
linhas alteradas: 180

Troubleshooting (diagnóstico)

O que não fazer

AçãoConsequência
git push --force origin mainSobrescreve o histórico da main para toda a equipe; perda permanente
git reset --hard sem git stash antesDescarta alterações locais não commitadas sem chance de recuperação
git clean -fd sem revisar com -n antesRemove arquivos não rastreados que podem não estar no .gitignore
git rebase em branch compartilhadaReescreve o histórico que outros já baixaram; gera divergências irreconciliáveis

Inspecionando antes de agir

✅ Bom
# ver o que mudou nos últimos 3 commits antes de resetar
git diff HEAD~3

# histórico resumido da branch atual
git log --oneline -10

# ver quais arquivos mudaram em cada commit
git log --stat -5

# simular limpeza sem executar (dry-run)
git clean -n -fd

Guardando trabalho temporário

✅ Bom
# salvar alterações locais antes de trocar de contexto
git stash push -m "wip: email verification form"

# listar stashes guardados
git stash list

# restaurar o mais recente e removê-lo da lista
git stash pop

# restaurar um stash específico sem removê-lo
git stash apply stash@{1}

Conflitos com a main

Se a main avançou enquanto você trabalhava, incorpore as mudanças com um merge commit na branch. O squash no merge do PR limpa tudo depois.

main avança → merge main na branch → conflito resolvido → PR → squash

❌ Ruim: rebase de rotina
# rebase força --force-push depois e reescreve histórico já publicado
git rebase origin/main
git push --force origin feat/user-email-verification
✅ Bom: forward-only
# incorporar main na branch com um merge commit
git fetch origin
git merge origin/main

# sem conflitos: Git cria o merge commit automaticamente, pronto para o PR

# com conflitos: resolver cada arquivo, marcar como resolvido e commitar
git status
git add .
git commit -m "chore: merge main into feat/user-email-verification"

Recuperando commits perdidos

O Reflog registra todas as posições que HEAD ocupou. Funciona como um histórico de desfazer local, independente de push.

✅ Bom
# listar todas as posições recentes do HEAD
git reflog

# saída:
# abc1234 HEAD@{0}: rebase: feat(auth): add email verification
# def5678 HEAD@{1}: commit: test(auth): cover token expiry
# ghi9012 HEAD@{2}: commit: feat(auth): add email verification token

# restaurar para um estado anterior
git checkout HEAD@{2}

# criar branch a partir de um commit perdido
git checkout -b recovery/email-verification ghi9012

Rebase como ferramenta de recuperação

Use rebase apenas em branches locais (não publicadas) ou em situações pontuais de recuperação. Nunca em branches compartilhadas.

✅ Bom: limpar commits antes do primeiro push
# compactar os 4 últimos commits locais em um antes de publicar
git rebase -i HEAD~4

# no editor: manter 'pick' no primeiro, trocar os demais por 's'
# pick abc1234 feat(auth): add email verification token
# s   def5678 fix typo
# s   ghi9012 WIP
# s   jkl3456 forgot test file
✅ Bom: remover commit com dado sensível (branch local)
# remover um commit específico do histórico antes de publicar
git rebase -i HEAD~3

# no editor: trocar 'pick' por 'd' (drop) no commit com a senha
# d   abc1234 chore: add config (senha exposta aqui)
# pick def5678 feat(auth): add email verification token

Corrigindo um problema em produção

Quando um bug é identificado em produção, o primeiro caminho é criar uma branch de fix a partir da main e entregar a correção via PR. É mais seguro que reverter: mantém o histórico avançando e não desfaz mudanças de outros devs que chegaram junto.

bug identificado → fix/ branch da main → correção → PR → squash and merge → deploy

✅ Bom
# 1. partir da main atualizada
git checkout main
git pull origin main

# 2. criar branch de fix focada no problema
git checkout -b fix/user-email-token-expiry

# 3. corrigir e commitar
git add src/auth/email.js
git commit -m "fix(auth): correct token expiry on email verification"

# 4. verificar se a main avançou antes do PR
git fetch origin
git log origin/main --oneline -3

# 5. PR → squash and merge → confirmar deploy → deletar branch

Se o tempo for crítico e não houver janela para review e deploy de um novo PR, o caminho é reverter; veja a próxima seção.

Revertendo um deploy com problema

git revert não apaga o commit: cria um novo commit que desfaz o efeito. O commit original permanece no histórico e pode ser inspecionado pelo hash mesmo após a branch ser deletada.

main: estado estável → feat(auth): add email verification → revert: feat(auth) → produção restaurada
PontoO que representa
estado estávelMain antes do seu deploy; produção funcionando
feat(auth): add email verificationSeu squash commit mergeado; introduziu o bug
revert: feat(auth)Novo commit criado pelo git revert que desfaz o efeito do anterior
produção restauradaMain volta ao comportamento do estado estável; seu commit original permanece no histórico

Com a main revertida, o caminho para entregar o fix é:

✅ Bom
# 1. reverter o commit problemático na main
git revert <hash-do-squash-commit>
git push origin main

# 2. inspecionar o que estava no commit original (branch já deletada)
git show <hash-do-squash-commit>

# 3. criar branch de fix a partir da main já revertida
git checkout main
git pull origin main
git checkout -b fix/user-email-verification

# 4. corrigir o problema e commitar
git add src/auth/email.js
git commit -m "fix(auth): correct token expiry on email verification"

# 5. incorporar main antes do PR (forward-only)
git fetch origin
git merge origin/main

# 6. PR → squash and merge → aguardar produção → deletar branch

Se o GitHub ainda mostrar o botão "Restore branch", vale restaurar antes de recriar manualmente; o histórico completo fica disponível por um período após a deleção.


Branches, commits e PRs: git.md. Deploy, release e fix-forward: ci-cd.md.

Desenvolvido por @thiagocajadev · Fork baseado no repositório pmndrs/docs · Poimandres.