Git Avançado: Rebase, Cherry-pick, Bisect e Recuperação de Histórico
Introdução ao Workflow Avançado
Quando você trabalha em equipes grandes ou em projetos complexos, git básico (commit, push, pull) não é suficiente. Você precisa dominar ferramentas que permitem reescrever histórico, selecionar commits específicos, encontrar bugs e recuperar trabalho perdido. Este artigo é para quem já sabe usar git diariamente e quer evoluir para um nível profissional, dominando as operações que separam desenvolvedores que apenas usam git daqueles que realmente o dominam.
O conhecimento dessas técnicas não é opcional em empresas que levam qualidade a sério. Elas resolvem problemas reais: um commit foi feito na branch errada? Use cherry-pick. O histórico está bagunçado? Use rebase. Qual commit introduziu o bug? Use bisect. Deletou a branch por acidente? Recupere com reflog. Vamos começar.
Rebase: Reorganizando e Limpando Histórico
O que é Rebase e Por que Importa
Rebase é o conceito mais mal compreendido de git. Não é merge. Enquanto merge cria um commit de fusão que preserva ambas as histórias, rebase reaplica seus commits em cima de outra branch, criando um histórico linear e limpo. Pense em rebase como "mover sua base de trabalho para um novo ponto".
A primeira razão para usar rebase é estética: um histórico linear é mais fácil de ler e debugar. A segunda é prática: quando você trabalha em uma feature branch por dias enquanto main recebe outros commits, rebase garante que sua feature está construída em cima da versão mais recente do código. Isso é especialmente importante antes de fazer merge.
Rebase Interativo: O Poder está aqui
# Você está na branch 'feature' com 5 commits
# Quer reorganizar, combinar ou editar 3 últimos commits
git rebase -i HEAD~3
O comando acima abre um editor com algo assim:
pick a1b2c3d Adiciona validação de email
pick d4e5f6g Corrige bug de formatação
pick h7i8j9k Adiciona testes unitários
Você pode modificar para:
pick a1b2c3d Adiciona validação de email
squash d4e5f6g Corrige bug de formatação
reword h7i8j9k Adiciona testes unitários
pick: mantém o commit como estásquash(ous): combina este commit com o anterior e permite editar a mensagemreword(our): mantém as mudanças mas permite editar a mensagemdrop(oud): remove o commit completamentefixup(ouf): como squash mas descarta a mensagem do commit
# Exemplo real: você tem 3 commits e quer deixar apenas 1
git rebase -i HEAD~3
# Edita: pick primeiro, squash segundo, squash terceiro
# Resultado: 1 commit com todas as mudanças
Rebase sobre outra Branch
Cenário comum: você criou feature a partir de main, mas main evoluiu enquanto você trabalhava. Você quer seus commits em cima da versão atual de main.
# Você está em 'feature'
git rebase main
# Isso reaplica seus commits em cima de main
# Se houver conflitos:
# 1. Resolva manualmente os arquivos
# 2. git add .
# 3. git rebase --continue
# Se quiser cancelar:
git rebase --abort
Regra de Ouro: Nunca faça rebase em commits que já foram pusheados para um repositório compartilhado. Rebase reescreve histórico. Se alguém já fez pull desses commits, você criará problemas. Use rebase apenas em branches locais ou branches que você controla sozinho.
Cherry-pick: Selecionando Commits Específicos
Quando e Por que Usar
Cherry-pick é simples: você copia um commit específico de uma branch e o aplica em outra. Diferente de rebase que move uma sequência inteira, cherry-pick permite seletividade cirúrgica.
Casos de uso reais: um bug foi corrigido em develop mas precisa estar urgentemente em production; você commitou em feature por engano e precisa em main; uma hotfix foi feita em uma branch e precisa em várias outras. Em todos esses casos, cherry-pick é sua ferramenta.
Cherry-pick Prático
# Você está em 'main' e quer trazer o commit abc123d de 'develop'
git cherry-pick abc123d
# Se houver conflitos:
git cherry-pick --continue
# ou desistir:
git cherry-pick --abort
# Cherry-pick múltiplos commits em sequência
git cherry-pick abc123d def456e ghi789f
Exemplo realista: seu repositório tem main (production) e develop. Um bug crítico foi corrigido em develop no commit a3b2c1d. Você precisa desse fix em production hoje.
# Em main
git cherry-pick a3b2c1d
# O commit é agora parte de main com um novo hash (porque mudou a parent)
# Isso é esperado e correto
Cuidado com Cherry-pick
# Ruim: fazer cherry-pick de 50 commits de uma vez
git cherry-pick develop~50..develop
# Bom: usar rebase se precisa de uma sequência inteira
git rebase develop
Cherry-pick deixa rastros claros no histórico (o hash muda, mas a mensagem e o autor são preservados), o que é bom para auditoria. Mas se você está movendo muitos commits de uma vez, rebase é a ferramenta correta.
Bisect: Encontrando o Commit que Quebrou Tudo
Binary Search no Histórico
Imagine: seu código estava funcionando há 50 commits atrás, agora está quebrado. Você não sabe qual commit introduziu o bug. Procurar manualmente é insano. Aqui entra git bisect: uma busca binária que reduz 50 commits para descobrir em ~6 iterações.
Bisect funciona assim: você marca um commit como "bom" (sem o bug) e outro como "ruim" (com o bug), e git divide o espaço no meio, pedindo para você testar. Baseado na resposta, ele elimina metade dos commits suspeitos. Repete até encontrar o commit exato.
Bisect Manual
# Iniciar bisect
git bisect start
# Marcar o commit atual como ruim (tem o bug)
git bisect bad
# Marcar um commit antigo como bom (não tem o bug)
git bisect good abc123d
# Git agora te coloca em um commit no meio
# Você testa se tem o bug ou não
# Se este commit tem o bug:
git bisect bad
# Se não tem:
git bisect good
# Repita até git encontrar o commit exato
# Quando terminar:
git bisect reset
Exemplo com números. Digamos que você tem 64 commits. Bisect funciona assim:
Commits: [bom] ........ [meio=commit 32] ........ [ruim]
Você testa: ainda é ruim
Commits: [bom] .... [meio=commit 16] .... [ruim]
Você testa: é bom
Commits: [bom] [meio=commit 24] [ruim]
... em ~6 testes você encontra o exato
Bisect Automatizado
# Se você tem um script/teste que pode detectar o bug automaticamente
git bisect start
git bisect bad HEAD
git bisect good abc123d
# Comando automático que executa seu teste
git bisect run ./test_script.sh
# test_script.sh retorna:
# 0 = sucesso (commit é bom)
# 1 = falha (commit é ruim)
# 125 = skip este commit
#!/bin/bash
# test_script.sh
npm test
# Retorna 0 se passar, 1 se falhar
Recuperação de Histórico: Reflog e Dangling Commits
Reflog: Seu Seguro contra Perdas
Reflog (reference log) é um log de todas as mudanças nos ponteiros das branches localmente. Deletou uma branch por acidente? Fez um reset errado? Reflog tem tudo. É diferente do histórico normal de commits — é um histórico de "onde você esteve" no seu repositório.
# Ver reflog
git reflog
# Saída algo assim:
# a1b2c3d HEAD@{0}: commit: Adiciona feature X
# d4e5f6g HEAD@{1}: rebase -i (finish): returning to refs/heads/feature
# h7i8j9k HEAD@{2}: rebase -i (start): checkout main
# l0m1n2o HEAD@{3}: checkout: moving from main to feature
Cada linha representa um movimento. HEAD@{0} é o estado atual, HEAD@{1} é um estado atrás, etc. Você pode usar reflog para ir para qualquer estado anterior.
Recuperando uma Branch Deletada
# Você acidentalmente fez
git branch -D feature
# Use reflog para ver onde estava
git reflog
# Encontre algo como:
# a1b2c3d HEAD@{5}: commit: Último commit da feature
# Recupere criando uma nova branch no mesmo ponto
git branch feature a1b2c3d
# Pronto, a branch está recuperada
Dangling Commits
Commits não referenciados (não estão em nenhuma branch) aparecem como "dangling". Reflog mantém referências a eles por padrão durante 90 dias.
# Ver commits orfãos
git fsck --lost-found
# Buscar commits perdidos
git log --all --graph --oneline --decorate
# Se quiser recuperar um commit específico
git show a1b2c3d # veja o conteúdo
git branch recover-branch a1b2c3d # crie uma branch nele
Reset, Revert e Restore: Diferenças Críticas
Essas três operações parecem fazer a mesma coisa, mas não fazem:
# git reset: move o HEAD (reescreve histórico local)
git reset --soft HEAD~1 # desfaz commit, mantém mudanças staged
git reset --mixed HEAD~1 # desfaz commit, mantém mudanças unstaged (padrão)
git reset --hard HEAD~1 # desfaz commit, descarta mudanças (CUIDADO!)
# git revert: cria um NOVO commit que desfaz as mudanças (seguro)
git revert abc123d # cria novo commit que inverte as mudanças do abc123d
# git restore: descarta mudanças em arquivos específicos
git restore arquivo.txt # descarta mudanças não commitadas
git restore --source=abc123d arquivo.txt # restaura arquivo de um commit
Quando usar cada um:
reset: você quer desfazer commits locais não pusheadosrevert: você quer desfazer commits que foram pusheados (seguro para histórico compartilhado)restore: você quer desfazer edições em arquivos específicos
# Exemplo: você commitou credenciais por acidente em abc123d
# Opcão 1 (se ninguém fez pull):
git reset --hard HEAD~1
# Opção 2 (se foi pusheado):
git revert abc123d
git push
# Opção 3 (remover apenas um arquivo de um commit passado):
git restore --source=abc123d~1 credenciais.env
git add credenciais.env
git commit --amend # adiciona à mensagem anterior
Casos Práticos Integrados
Cenário 1: Limpando uma Feature Branch Bagunçada
# Você tem 8 commits em 'feature' mas alguns são fixups/ajustes
# Quer limpar antes de fazer merge em main
git checkout feature
git rebase -i main
# Editor abre:
# pick a1b2c3d Implementa login
# pick d4e5f6g Adiciona validação email
# pick h7i8j9k Corrige typo em login
# pick l0m1n2o Testa login com mobile
# ...
# Você edita para:
# pick a1b2c3d Implementa login
# squash h7i8j9k Corrige typo em login
# pick d4e5f6g Adiciona validação email
# squash l0m1n2o Testa login com mobile
# pick ...
# Resultado: 4 commits bem organizados em vez de 8 bagunçados
git push -f # força push (apenas em branches suas!)
Cenário 2: Hotfix em Production que precisa voltar para Develop
# main (production) tinha um bug urgente
# Criou hotfix, commitmou fix abc123d, pushou para main
# Agora precisa voltar o fix para develop também
git checkout develop
git cherry-pick abc123d
# Se houver conflitos (código evoluiu diferente):
# Resolve manualmente
git add .
git cherry-pick --continue
git push
Cenário 3: Encontrando qual deploy quebrou
# Sabia que breaks entre v1.2.3 e v1.3.0
# v1.2.3 é bom, v1.3.0 é ruim
git bisect start
git bisect bad v1.3.0
git bisect good v1.2.3
# Git coloca você em um commit no meio
# Você testa sua aplicação
# Se funciona:
git bisect good
# Se quebra:
git bisect bad
# Repete até descobrir o commit exato
# Quando encontra:
# Commit abc123d é o primeiro bad commit
# Você pode revert ou cherry-pick um fix para ele
Conclusão
Você aprendeu três lições fundamentais que elevam seu git de "usuário" para "profissional":
-
Rebase e cherry-pick são ferramentas de poder responsável: eles reescrevem histórico (rebase) ou selecionam commits cirurgicamente (cherry-pick). Ambos deixam um repositório mais limpo e legível, mas exigem cuidado com branches compartilhadas. A regra é simples: rebase local, revert compartilhado.
-
Bisect transforma "encontrar o bug" de uma tarefa manual exaustiva em um problema de busca binária que se resolve em minutos, mesmo com centenas de commits. É especialmente poderoso quando combinado com scripts automatizados que detectam o problema.
-
Reflog, reset, revert e restore formam uma rede de segurança contra erros. Quase nada é permanentemente perdido em git — reflog segura seus commits órfãos, revert permite desfazer publicamente sem reescrever histórico, e reset/restore lidam com situações locais. Memorize a diferença entre essas operações; é exatamente onde mais gente erra.
O domínio dessas técnicas não é vanidade — é profissionalismo. Projetos reais têm histórico bagunçado, bugs misteriosos e situações onde alguém commitou algo na branch errada às 3 da manhã. Você será a pessoa que resolve isso em 5 minutos em vez de 5 horas.