CURSOS / TECNOLOGIA / TRILHA PRO GIT (ETAPA 3 DE 4)
Controle de Versão · Etapa 3 de 4 · Produtividade e Limpeza de Histórico

Ferramentas de
precisão cirúrgica.

Domine o Capítulo 7 de Pro Git: prepare apenas trechos escolhidos de um arquivo, recupere commits perdidos com reflog, reescreva o histórico antes de publicar, entenda as três árvores do git reset e cace bugs automaticamente com git bisect.

Bancada de engenharia com paquímetro de latão, lupa óptica sobre caderno de linhas de tempo e terminal
8 oficinascobrindo as 15 seções do Capítulo 7
Etapa 3 de 4na formação completa de Git
Seu cadernocom receitas de curadoria e resgate
Percurso completo em 4 cursos · Pro Git

Trilha de formação: Domínio do Git do básico à arquitetura interna

Você já domina o trabalho individual e em equipe. Agora está na Etapa 3: Produtividade e Limpeza de Histórico, dedicada integralmente às ferramentas avançadas do Capítulo 7.

Percurso da Etapa 3 (Capítulo 7 · Git Tools)

Este é o módulo de maior impacto na produtividade individual. Pratique cada comando em um repositório de laboratório para transformar operações que pareciam arriscadas em movimentos seguros.

Bandejas de madeira com fichas de arquivo numeradas e grampo de latão destacando uma revisão específica

“Antes de publicar seu trabalho, o histórico local é seu caderno de rascunho: você pode dividir, unir e reordenar commits até que contem uma história cristalina.”

No Capítulo 7 de Pro Git, Scott Chacon e Ben Straub ensinam a tratar a Staging Area e os commits locais como peças moldáveis — sem perder a rede de segurança do reflog.
Oficina 01 / 08

Aponte para qualquer commit com ancestralidade exata, intervalos e Reflog

Em um commit de merge que uniu duas branches, um colega digita git show HEAD~2 achando que é a mesma coisa que git show HEAD^2 — e acaba inspecionando o commit errado. Qual é a diferença exata entre o circunflexo (^) e o til (~)?

Reflog, ancestralidade (^ vs. ~) e intervalos de commits

Na Seção 7.1, os autores ensinam todas as maneiras de referenciar um commit sem precisar copiar os 40 caracteres do SHA-1. Você pode usar apenas os primeiros caracteres não ambíguos (no mínimo 4, geralmente 7 a 10 caracteres exibidos com `git log --abbrev-commit`), descobrir o hash exato para o qual qualquer branch aponta com git rev-parse main ou consultar o histórico silencioso que o Git mantém nos bastidores: o Reflog (git reflog).

Enquanto você trabalha, o Git grava em um diário exclusivamente local toda vez que a ponta do seu `HEAD` ou de uma branch muda (seja por commit, checkout, merge ou até um `reset --hard`). Com git reflog (ou `git log -g`), você pode inspecionar onde estava há cinco movimentos (git show HEAD@{5}) ou onde uma branch estava ontem (git show main@{yesterday}), tornando quase impossível perder um commit já gravado localmente.

Já nas referências de ancestralidade, entender a diferença entre ^ e ~ é essencial quando há commits de merge (que possuem mais de um pai):

Como o Git navega pelos pais de um commit e intervalos (Cap. 7.1)
NotaçãoNome técnicoPara qual commit ela aponta
HEAD^ (ou HEAD^1)Primeiro pai imediatoO commit pai na branch onde você estava quando fez o commit (ou merge).
HEAD^2Segundo pai do mergeO primeiro commit da outra branch que foi mesclada (só existe em merge commits!).
HEAD~2 (ou HEAD^^)Avô (2 níveis para trás)O primeiro pai do primeiro pai — desce dois degraus na linha principal.
git log A..BIntervalo de Ponto DuploCommits alcançáveis a partir de B que não existem em A (mesmo que ^A B ou B --not A).
git log --left-right A...BDiferença Simétrica (3 Pontos)Commits exclusivos de A (marcados com <) ou exclusivos de B (marcados com >), excluindo o que é comum.
Comando para experimentar
# 1. Crie um laboratório para a Etapa 3 com um merge commit para testar HEAD^2 vs. HEAD~2:
mkdir -p ~/lab-git-etapa3 && cd ~/lab-git-etapa3
git init -b main
echo "base 1" > app.txt && git add app.txt && git commit -m "C1: Commit avo na main"
echo "base 2" >> app.txt && git commit -a -m "C2: Commit pai na main"
git checkout -b lateral HEAD~1
echo "ramo lateral" > lateral.txt && git add lateral.txt && git commit -m "C3: Commit na branch lateral"
git checkout main && git merge lateral -m "C4: Merge da branch lateral"

# 2. Compare o avô na linha principal (HEAD~2) com o segundo pai vindo do merge (HEAD^2):
git show --oneline -s HEAD~2
git show --oneline -s HEAD^2

# 3. Veja o diário de movimentos locais no Reflog e use diferença simétrica com --left-right:
git reflog
git log --oneline --left-right main...lateral
Atenção ao digitar HEAD^ e HEAD@{0} no Windows (CMD e PowerShell)

Como destacam as notas da Seção 7.1, no prompt `cmd.exe` do Windows o caractere `^` é interpretado como escape, exigindo duplicá-lo (`HEAD^^`) ou colocar entre aspas (`"HEAD^"`). Já no PowerShell, tanto `{}` quanto `^` são caracteres especiais, então sempre envolva a referência entre aspas duplas: git show "HEAD@{0}" e git show "HEAD^2".

Entrega da etapa

Combine as duas notações no seu repositório de teste (por exemplo, o que significa HEAD~3^2?) e use git log origin/main..HEAD para listar exatamente quais commits locais estão aguardando envio para o servidor.

Conexão com o livro · Capítulo 7 (Seção 7.1) · Short SHA-1, git rev-parse, git reflog (@{n}, @{yesterday}), ancestralidade (^ vs. ~) e intervalos (.., --not, ... com --left-right).

Oficina 02 / 08

Prepare trechos de arquivos com git add -p, use Stash criativo e limpe com git clean

Você se empolgou codificando e resolveu dois bugs diferentes dentro do mesmo arquivo `pagamento.py`. Como colocar apenas as linhas do primeiro bug no commit de agora e deixar as linhas do segundo bug para o próximo commit sem precisar apagar código?

Staging Interativo por Hunks (git add -p)

Na Seção 7.2, Chacon e Straub apresentam uma das ferramentas favoritas de quem preza por commits limpos: o Staging Interativo (git add -i ou diretamente git add -p / --patch). Em vez de colocar o arquivo inteiro na Staging Area de uma só vez, o comando `git add -p` analisa o arquivo modificado e pergunta trecho por trecho (hunk by hunk): Stage this hunk [y,n,q,a,d,s,e,?]?.

Você digita y para preparar aquele trecho, n para deixá-lo fora do stage, s (split) para dividir um trecho grande em blocos menores ainda ou e para editar manualmente as linhas que entrarão no stage! E o mesmo modo `--patch` funciona para desfazer partes do arquivo: git reset -p (ou `git restore -p --staged`) tira trechos específicos da Staging Area, e `git checkout -p` (ou `git restore -p`) descarta trechos específicos na Working Tree.

Stash avançado (--index, --keep-index, -u) e limpeza segura (git clean)

Na Seção 7.3, quando você precisa trocar de branch imediatamente sem commitar o trabalho pela metade, o git stash (ou `git stash push`) guarda as modificações da Working Tree e da Staging Area em uma pilha local e deixa seu diretório limpo. Mas o livro vai muito além do básico com cinco variações decisivas:

Opções avançadas de git stash e git clean para o dia a dia (Cap. 7.3)
ComandoO que ele resolve na prática
git stash apply --indexReaplica o stash restaurando também o que já estava na Staging Area (sem --index, tudo volta como Unstaged).
git stash --keep-indexGuarda no stash apenas o que está fora do stage, deixando na Working Tree somente o que você já preparou com git add (perfeito para rodar os testes apenas no que vai ser commitado!).
git stash -u (ou --include-untracked)Inclui também os arquivos novos (Untracked) no stash (por padrão, o stash guarda apenas arquivos já rastreados).
git stash branch <nova-branch>Cria uma branch nova a partir do commit em que o stash foi criado, aplica o stash com --index e já remove-o da pilha se aplicar sem conflitos.
git clean -d -n → git clean -d -fSimula (-n dry-run) e depois remove de verdade (-f) arquivos não rastreados e subdiretórios vazios (-d). Adicione -x para apagar também arquivos ignorados pelo .gitignore (como builds antigos).
Comando para experimentar
# 1. Pratique o staging parcial por trechos (digite y para o primeiro trecho e n para o segundo):
#    git add -p app.txt
#    git diff --staged
#    git diff

# 2. Crie um arquivo novo não rastreado e guarde tudo na pilha com git stash -u:
cd ~/lab-git-etapa3
echo "trabalho em andamento" >> app.txt
echo "rascunho novo" > novo.txt
git stash push -u -m "WIP: ajuste parcial + arquivo novo"
git stash list

# 3. Restaure o trabalho preservando o estado do índice ou crie uma branch direto do stash:
git stash apply --index
git stash drop

# 4. Sempre simule com -n (dry-run) antes de limpar arquivos untracked com git clean:
git clean -d -n
Por que git stash --keep-index evita commits que quebram o build no servidor?

Quando você edita dez arquivos na Working Tree e usa `git add -p` para separar apenas três alterações para o primeiro commit, rodar os testes automatizados localmente testará os dez arquivos misturados — podendo passar apenas porque um dos arquivos que ficou de fora continha uma função auxiliar! Se você rodar git stash --keep-index, o Git esconde temporariamente tudo o que não foi para o stage, você roda sua suíte de testes exatamente no código que vai entrar no commit e depois recupera o resto com git stash pop.

Entrega da etapa

Faça duas alterações em partes distantes do mesmo arquivo, use git add -p para colocar apenas a primeira na Staging Area, guarde o restante com git stash --keep-index, faça o commit da primeira parte e recupere a segunda com git stash pop.

Conexão com o livro · Capítulo 7 (Seções 7.2 e 7.3) · git add -i / -p, git stash (list, apply --index, pop, --keep-index, -u, --patch, branch) e git clean (-d, -n, -f, -x, -i).

Oficina 03 / 08

Assine seu trabalho com GPG e investigue funções com git grep e git log -L

Como provar criptograficamente que um commit ou lançamento não foi adulterado, e como ver no terminal toda a evolução histórica de uma única função `calcular_imposto` sem ler mudanças no resto do arquivo?

Assinatura criptográfica GPG em Tags, Commits e Merges

Na Seção 7.4, aprendemos que, embora o Git garanta integridade com SHA-1, qualquer pessoa pode configurar qualquer nome e e-mail no `git config`. Para provar autenticidade criptográfica, você configura sua chave privada GPG (`git config --global user.signingkey `) e passa a assinar:

  1. Tags assinadas (git tag -s v1.5 -m "..."): substitui o `-a` por `-s` para anexar a assinatura PGP ao objeto da tag, verificável por qualquer colega que tenha sua chave pública com git tag -v v1.5.
  2. Commits assinados (git commit -S -m "..."): no Git 1.7.9+, assina o próprio commit. Você audita as assinaturas com git log --show-signature -1 ou inclui o indicador %G? no formato do log (`G` para assinatura válida, `B` para inválida, `U` para confiabilidade desconhecida e `N` para sem assinatura).
  3. Exigir assinatura no Merge (git merge --verify-signatures -S): recusa mesclar uma branch se os commits recebidos não possuírem assinatura GPG válida e já assina o commit de merge resultante.

Busca instantânea com git grep (-p) e evolução de função com git log -L

Na Seção 7.5, em vez de usar um `grep` comum que varre até arquivos ignorados e pastas `.git`, use git grep, que busca apenas nos arquivos rastreados pelo seu repositório (ou até em uma tag antiga sem fazer checkout dela!). Combine -n para ver o número da linha, --count (`-c`) para resumir quantas ocorrências há por arquivo e a flag especial git grep -p <termo> (`--show-function`), que exibe automaticamente o cabeçalho da função ou método onde a linha foi encontrada!

E quando quiser ver como uma função específica ou um intervalo de linhas mudou desde que foi criado, use o Line Log Search (git log -L): executar git log -L :nome_da_funcao:caminho/arquivo.py (ou git log -L 10,25:caminho/arquivo.py) rastreia todos os commits e diffs que alteraram especificamente aquela função ao longo do tempo.

Comando para experimentar
# 1. Crie um arquivo de código no laboratório e busque com número de linha (-n) e contexto de função (-p):
cd ~/lab-git-etapa3
cat << 'EOF' > calculo.py
def calcular_frete(peso):
    taxa_base = 15
    return peso * 2 + taxa_base
EOF
git add calculo.py && git commit -m "Adiciona funcao calcular_frete"
sed -i 's/taxa_base = 15/taxa_base = 20/' calculo.py
git commit -a -m "Reajusta taxa_base do frete para 20"

# 2. Encontre o termo exibindo em qual função ele mora (-p) e busque até em uma revisão específica:
git grep -n -p "taxa_base"
git grep --count "taxa_base"

# 3. Veja a história evolutiva EXCLUSIVA da função calcular_frete com git log -L:
git log -L :calcular_frete:calculo.py

# 4. Formato de auditoria de assinaturas GPG no log (%G?):
git log --pretty="format:%h [%G?] %aN - %s"
Como fazer buscas booleanas complexas (--and / --not) no git grep?

Quando você quer encontrar apenas as linhas que contenham dois termos simultaneamente (por exemplo, uma constante que defina `LINK` ou `BUF_MAX`), o `git grep` aceita operadores lógicos diretos: git grep --break --heading -n -e '#define' --and \( -e LINK -e BUF_MAX \) v1.8.0, pesquisando diretamente dentro da árvore da tag `v1.8.0` sem alterar sua Working Tree.

Entrega da etapa

Use git grep -n -p em um projeto real para localizar todas as chamadas de uma função importante e em seguida execute git log -L :nome_da_funcao:arquivo (ou por intervalo de linhas -L inicio,fim:arquivo) para inspecionar todos os commits que modificaram aquela função.

Conexão com o livro · Capítulo 7 (Seções 7.4 e 7.5) · GPG (user.signingkey, tag -s/-v, commit -S, --show-signature, %G?, --verify-signatures), git grep (-n, -c, -p, --and) e git log -L.

Oficina 04 / 08

Curadoria de histórico com Rebase Interativo (git rebase -i)

Enquanto desenvolvia uma funcionalidade local, você criou quatro commits: o primeiro com a lógica principal, o segundo mexendo no README + outro módulo junto, o terceiro corrigindo um erro de digitação do primeiro e o quarto com mensagem provisória. Como transformar esses quatro rascunhos em dois commits impecáveis antes do git push?

Como funciona o roteiro do git rebase -i HEAD~3

Na Seção 7.6, Chacon e Straub ensinam a editar commits locais antes de compartilhá-los. Para alterar apenas o último commit, basta `git commit --amend` (ou `git commit --amend --no-edit` para somar arquivos esquecidos mantendo a mensagem). Mas para editar, reordenar, fundir ou dividir múltiplos commits anteriores, você usa o Rebase Interativo: git rebase -i HEAD~3 (para revisar os últimos 3 commits).

Atenção a um detalhe fundamental que evita erros: enquanto o `git log` lista os commits do mais novo no topo para o mais antigo embaixo, o editor do git rebase -i lista os commits na ordem cronológica de execução (do mais antigo no topo para o mais recente embaixo)! Ao mudar a palavra à esquerda do hash de cada linha, você comanda o que o Git fará na reaplicação:

Comandos do roteiro de Rebase Interativo (git rebase -i)
Verbo no editorO que o Git executa naquele commitUso típico na limpeza de histórico
pick (p)Aplica o commit exatamente como está, na ordem em que a linha aparece.Manter commits bons (ou inverter a ordem das linhas para reordená-los).
reword (r)Aplica o commit, mas abre o editor para você corrigir a mensagem.Melhorar mensagens antigas sem alterar o código daquele commit.
squash (s)Funde este commit no commit da linha imediatamente acima e abre o editor para unir as mensagens.Condensar 3 commits parciais em um único commit bem explicado.
fixup (f)Igual ao squash, mas descarta a mensagem deste commit, mantendo só a do commit de cima.Incorporar silenciosamente pequenos commits do tipo “corrige typo”.
edit (e)Aplica o commit e pausa o rebase para você alterar arquivos ou dividir o commit em dois.Separar um commit que misturou duas tarefas usando git reset HEAD^.
drop (d)Descarta o commit inteiro (o mesmo que apagar a linha no editor).Remover um commit experimental quebrado antes de enviar a branch.
Comando para experimentar
# 1. Veja os últimos 3 commits na ordem normal do log vs. a ordem que o rebase -i usará:
cd ~/lab-git-etapa3
git log --pretty=format:"%h %s" -3

# 2. Como DIVIDIR um commit em dois quando o rebase -i parar em uma linha marcada com "edit":
#    (Depois de marcar o commit com "edit" no git rebase -i HEAD~3 e salvar):
#    git reset HEAD^
#    git add README.md && git commit -m "Atualiza formatacao do README"
#    git add lib/app.py && git commit -m "Adiciona funcao de auditoria"
#    git rebase --continue

# 3. A opção nuclear para apagar um arquivo de senhas que entrou em TODOS os commits da história:
#    git filter-branch --tree-filter 'rm -f senhas.txt' HEAD
Por que git reset HEAD^ dentro de um passo "edit" permite dividir um commit em dois?

Quando você marca um commit com edit no `git rebase -i`, o Git aplica aquele commit e para exatamente nele. Ao rodar git reset HEAD^ nesse instante, você desfaz aquele commit movendo o `HEAD` um passo para trás e devolvendo os arquivos modificados para o estado Unstaged no disco! Aí basta fazer `git add` + `git commit` para a primeira parte, outro `git add` + `git commit` para a segunda parte e rodar git rebase --continue para o Git reaplicar o restante da fila.

Entrega da etapa

Em uma branch local de laboratório, crie três commits seguidos (sendo o terceiro um pequeno ajuste do segundo), abra git rebase -i HEAD~3, use fixup ou squash para fundir o terceiro no segundo e reword para polir a mensagem do primeiro.

Conexão com o livro · Capítulo 7 (Seção 7.6) · reescrita de histórico, git commit --amend (--no-edit), git rebase -i (pick, reword, edit, squash, fixup, drop), divisão de commits e filter-branch.

Oficina 05 / 08

Desmistifique o git reset nas Três Árvores e simule o ganho na triagem

De comando temido a ferramenta previsível de manipulação de árvores

Na Seção 7.7 — uma das mais famosas de todo o livro Pro Git —, Chacon e Straub mostram que o comando `git reset` deixa de dar medo assim que você enxerga o Git gerenciando três árvores de arquivos distintas: o HEAD (o snapshot do último commit), o Index (o próximo snapshot proposto na Staging Area) e o Working Directory (os arquivos físicos no seu disco, que funcionam como uma bancada de edição).

Os três passos sequenciais do git reset (--soft, --mixed e --hard)

Quando você executa `git reset` apontando para um commit anterior (por exemplo, `HEAD~1`), ele sempre executa até três passos em ordem, parando onde a flag que você escolheu mandar parar:

  1. Passo 1 · Mover a branch para onde o HEAD aponta (--soft para aqui): `git reset --soft HEAD~1` apenas move o ponteiro da branch atual um commit para trás. Ele não toca no Index nem no Working Directory! Isso significa que todas as mudanças daquele commit desfeito continuam prontas na Staging Area: se você rodar `git commit` agora, recriará exatamente o mesmo snapshot. É uma maneira rapidíssima de compactar os últimos 3 commits em um só (`git reset --soft HEAD~3 && git commit`).
  2. Passo 2 · Atualizar o Index com o conteúdo do novo HEAD (--mixed para aqui — é o padrão!): quando você roda `git reset HEAD~1` (sem flag ou com `--mixed`), ele executa o Passo 1 e também atualiza a Staging Area para ficar igual ao commit anterior, mas não toca no Working Directory. Suas linhas de código continuam salvas no disco, apenas fora do stage.
  3. Passo 3 · Sobrescrever o Working Directory (--hard vai até o fim — CUIDADO): `git reset --hard HEAD~1` executa os Passos 1 e 2 e, por último, sobrescreve os arquivos do seu disco para ficarem idênticos ao commit apontado. É a única variante do `reset` que destrói alterações locais ainda não commitadas.
Quadro definitivo da Seção 7.7: como reset e checkout afetam as Três Árvores
ComandoMove o HEAD / Branch?Atualiza o Index?Altera o Working Directory?É seguro para o disco?
git reset --soft [commit]Move a branch atualNãoNãoSim (tudo fica no stage)
git reset [--mixed] [commit]Move a branch atualSimNãoSim (tudo fica no disco)
git reset --hard [commit]Move a branch atualSimSim (sobrescreve!)Não (apaga não-commitados)
git reset [commit] <arq>Não move HEADSim (apenas o arquivo)NãoSim (tira do stage)
git checkout [commit]Move apenas o HEADSimSim (sem apagar modificados)Sim (aborta se colidir)
git checkout [commit] <arq>Não move HEADSimSim (sobrescreve!)Não (sobrescreve o arquivo)
Comando para experimentar
# 1. Veja como compactar os últimos 2 commits em 1 usando apenas git reset --soft:
cd ~/lab-git-etapa3
echo "parte A" >> app.txt && git commit -a -m "Adiciona parte A"
echo "parte B" >> app.txt && git commit -a -m "Adiciona parte B"

# 2. Volte 2 commits mantendo todas as alterações de A e B já preparadas no Index (--soft):
git reset --soft HEAD~2
git status -s
git commit -m "Adiciona partes A e B consolidadas em um unico commit"

# 3. Se você fizer um reset --hard por engano em um commit já gravado, resgate pelo reflog:
git reflog -5

Simule o ganho operacional de usar as ferramentas avançadas do Capítulo 7

Na equipe de engenharia de plataforma coordenada por Bianca em Recife, cada ciclo de lançamento e diagnóstico resolvido com histórico limpo (`git add -p`, `git reset --soft` e `git bisect`) economiza R$ 260 em tempo de engenharia por sprint. O custo operacional de revisão e assinatura de commits consome R$ 65 por ciclo. Treinar a equipe nas ferramentas do Capítulo 7 e automatizar o script de busca binária custou R$ 520 iniciais. Quando alguém faz um reset destrutivo sem checar ou mistura alterações não relacionadas que exigem reconstrução manual da branch, cada incidente custa R$ 95.

Sua decisão ao recuperar ou compactar commits

Um desenvolvedor da equipe de Bianca fez 5 commits locais cheios de pequenos testes na própria branch e quer transformá-los em um único commit limpo antes de abrir o Pull Request, sem correr risco de apagar nenhuma linha do disco. Qual comando de duas linhas da Seção 7.7 resolve isso de forma mais simples ainda que abrir o editor do `rebase -i`?

Conferir uma decisão possível

Basta rodar git reset --soft HEAD~5 seguido de git commit -m "Mensagem consolidada da funcionalidade". Como `--soft` move apenas o ponteiro da branch 5 commits para trás sem tocar no Index nem no Working Directory, todo o resultado líquido dos 5 commits já fica preparado na Staging Area pronto para um único commit novo.

Três casos em que entender as Três Árvores salva o dia

Passar um caminho de arquivo para o git reset (git reset HEAD~1 app.py)

Quando você passa o nome de um arquivo para o `git reset`, ele pula completamente o Passo 1 (pois não é possível mover o ponteiro da branch para apenas um arquivo!) e atualiza apenas o Index com a versão daquele arquivo no commit indicado. Se você rodar `git reset HEAD~1 app.py`, o Index passa a ter a versão antiga de `app.py` enquanto o seu disco mantém a versão nova — bastando rodar `git commit` para reverter aquele arquivo sem precisar abri-lo no editor.

Por que git checkout <branch> é seguro para o Working Directory e git reset --hard não é?

Enquanto `git reset --hard` sobrescreve cegamente qualquer arquivo no Working Directory, o comando `git checkout ` tenta preservar modificações triviais e aborta com aviso se algum arquivo modificado por você fosse ser sobrescrito pela troca de branch.

Inspecionar o que está salvo em HEAD e no Index com comandos plumbing

No início da Seção 7.7, os autores mostram dois comandos internos que revelam exatamente o que mora nas duas primeiras árvores: git cat-file -p HEAD mostra o conteúdo do último commit e git ls-files -s lista todos os arquivos, permissões e hashes SHA-1 que estão atualmente no Index (Staging Area).

Simule o retorno operacional das ferramentas de precisão do Git

Os valores iniciais reproduzem o piloto da equipe de Bianca. Altere um parâmetro por vez para avaliar o impacto de eliminar incidentes de retrabalho.

Saldo = ciclos × (economia − custo de revisão) − investimento inicial − incidentes × custo por incidente. O ponto de equilíbrio exibido considera o investimento inicial antes dos incidentes.

Conexão com o livro · Capítulo 7 (Seção 7.7) · Reset Demystified, The Three Trees (HEAD, Index, Working Directory), --soft, --mixed, --hard, reset com caminho e comparação com checkout.

Oficina 06 / 08

Domine Merge Avançado, estilo diff3, reversão de merges (-m 1) e Subtree

Como desatar conflitos complexos e reverter um merge público sem perder a branch

Nas Seções 7.8 e 7.9, os autores abordam situações de merge em que o básico não basta: um colega converteu quebras de linha de Unix (LF) para Windows (CRLF) no arquivo inteiro gerando conflito em todas as linhas; você precisa ver o que havia no ancestral comum dentro do marcador de conflito; ou você precisa reverter na `main` um merge que já subiu para o servidor.

Cinco ferramentas para merges difíceis

  1. Abortar ou ignorar mudanças de espaço em branco: se um merge deu errado e você quer voltar tudo ao estado anterior imediatamente, execute git merge --abort. Se o conflito veio apenas de diferenças de indentação ou quebra de linha (CRLF vs. LF), rode git merge -Xignore-space-change <branch> (ou `-Xignore-all-space`) para que a estratégia recursiva ignore espaçamento ao mesclar.
  2. Ativar o estilo diff3 nos marcadores de conflito: por padrão, o conflito mostra apenas o que está em `HEAD` (`ours`) e o que veio da outra branch (`theirs`). Ao configurar git config --global merge.conflictstyle diff3 (ou passar `git checkout --conflict=diff3 `), o Git adiciona uma terceira seção central delimitada por ||||||| base mostrando como o trecho era no ancestral comum antes de qualquer um dos dois editar!
  3. Inspecionar os estágios `:1:`, `:2:` e `:3:` e o Combined Diff (`--cc`): durante um conflito, o Index guarda três cópias de cada arquivo conflitante: estágio 1 (ancestral comum), estágio 2 (`ours` / `HEAD`) e estágio 3 (`theirs` / `MERGE_HEAD`), acessíveis via `git show :1:arquivo`, `:2:arquivo` e `:3:arquivo` (e comparáveis com `git diff --ours`, `--theirs` e `--base`). Já rodar `git diff` (ou `git log --cc -p -1`) exibe o formato Combined Diff com duas colunas de sinais `+` e `-`, mostrando apenas o que você alterou para resolver o conflito.
  4. Reverter um commit de merge já publicado (git revert -m 1 HEAD) e a armadilha do re-merge: se um merge problemático já subiu para o servidor, você não deve usar `reset --hard`, e sim criar um commit inverso com git revert -m 1 HEAD (onde `-m 1` indica que o pai 1, a `main`, é a linha principal a ser mantida). Atenção à armadilha do livro: depois de reverter o merge, o histórico da `main` continua tendo os commits daquela branch como ancestrais! Se você corrigir o bug na branch `topic` e tentar rodar `git merge topic` de novo, o Git dirá Already up-to-date e ignorará todo o código que tinha sido revertido. Para trazer a branch de volta no futuro, você deve primeiro reverter o revert (`git revert `) e só então fazer `git merge topic`!
  5. Preferência automática (`-Xours` / `-Xtheirs`) e Subtree Merging: se você quiser que o Git mescle tudo normalmente, mas escolha automaticamente o seu lado apenas nas linhas que derem conflito, use `git merge -Xours ` (diferente da estratégia radical `-s ours`, que descarta 100% do código da outra branch para fingir que já foi mesclada!). E com `git read-tree --prefix=pasta/ -u branch` + `git merge -Xsubtree=pasta`, você embute outro projeto inteiro dentro de uma subpasta.
Comando para experimentar
# 1. Ative o estilo diff3 globalmente para sempre ver o ancestral comum (||||||| base) em conflitos:
git config --global merge.conflictstyle diff3

# 2. Durante qualquer conflito de merge, veja apenas os commits que tocaram nos arquivos em conflito:
#    git log --oneline --left-right --merge

# 3. Como reverter um merge público mantendo a main (pai 1) e como destravar o re-merge futuro:
#    git revert -m 1 HEAD
#    (Quando a branch topic estiver corrigida, reverta o commit de revert antes de mesclar de novo):
#    git revert 
#    git merge topic
Qual a diferença perigosa entre git merge -Xours e git merge -s ours?

A opção -Xours é um parâmetro passado para a estratégia normal de merge: ela integra todas as mudanças pacíficas da outra branch e só escolhe a sua versão nos trechos em que houver conflito direto. Já -s ours troca a própria estratégia de merge pela estratégia *ours*, que ignora absolutamente todas as alterações da outra branch e grava um commit de merge mantendo a árvore da `main` intocada (usada apenas quando você quer registrar no histórico que uma branch antiga foi encerrada sem trazer o código dela).

Configure seu ambiente contra conflitos cegos

Ative merge.conflictstyle diff3 e rerere.enabled true na sua máquina. Em seguida, anote no seu caderno o procedimento exato de 3 passos para quando alguém precisar reverter um Pull Request mesclado na `main` hoje e reabri-lo corrigido amanhã (usando `git revert -m 1` e revertendo o revert antes do novo merge).

Conexão com o livro · Capítulo 7 (Seções 7.8 e 7.9) · git merge --abort, -Xignore-space-change, estágios :1:/:2:/:3:, conflictstyle diff3, --cc, git revert -m 1, -Xours vs. -s ours, Subtree e Rerere.

Oficina 07 / 08

Rastreie autoria com git blame -C e cace bugs automaticamente com git bisect

O sistema funcionava na versão `v1.0` há quinhentos commits, mas hoje está quebrado e ninguém sabe qual commit introduziu o defeito. Em vez de testar quinhentos commits um por um, como encontrar o culpado exato testando apenas nove commits — ou deixar um script encontrá-lo sozinho em segundos?

Anatomia de linha com git blame (-L e -C) e Busca Binária com git bisect

Na Seção 7.10, quando você sabe exatamente em qual arquivo e linha um bug está acontecendo e quer entender o contexto em que aquela linha foi escrita, use git blame -L 69,82 arquivo (restringindo a análise às linhas 69 a 82). Se o código daquele arquivo tiver sido refatorado ou movido de outro arquivo mais antigo, adicione a flag -C (git blame -C -L 141,153 arquivo): o Git analisa a base de código para descobrir de qual arquivo original aquele bloco de linhas foi copiado ou recortado, mostrando o autor e o commit originais da criação da lógica, e não apenas quem moveu o código de pasta!

Já quando você não faz ideia de qual arquivo quebrou o sistema, entra o git bisect. Ele executa uma busca binária entre um commit que você sabe que está quebrado (normalmente `HEAD`) e um commit antigo onde tudo funcionava (como `v1.0`). O Git faz checkout do commit exatamente na metade do caminho; você testa e diz `git bisect good` (se funcionou) ou `git bisect bad` (se já estava quebrado). A cada passo, o Git corta o número de suspeitos pela metade: em 1.000 commits, bastam no máximo 10 passos ($\log_2 1024 = 10$) para apontar o primeiro commit defeituoso! Ao terminar, sempre rode git bisect reset para voltar o `HEAD` à branch original.

Comando para experimentar
# 1. Inspecione quem alterou um intervalo de linhas e de onde o bloco foi movido (-C):
cd ~/lab-git-etapa3
git blame -C -L 1,3 calculo.py

# 2. Fluxo manual do git bisect (iniciar -> marcar bad e good -> testar -> resetar):
#    git bisect start
#    git bisect bad HEAD
#    git bisect good C1
#    (teste o código e digite "git bisect good" ou "git bisect bad" até achar o culpado)
#    git bisect reset

# 3. Fluxo 100% AUTOMATIZADO com git bisect run:
#    Crie um comando ou script que retorne 0 quando o código está bom e 1 quando está ruim:
git bisect start HEAD C1
git bisect run grep -q "taxa_base = 15" calculo.py
git bisect reset

Teste de raciocínio: Automatizando o git bisect run

Para que o comando git bisect run ./testar-bug.sh funcione sem intervenção humana, o script precisa respeitar os códigos de saída (exit codes) esperados pelo Git. O código `0` informa que o commit atual é bom (`good`), e a maioria dos códigos entre `1` e `127` informa que o commit atual contém o bug (`bad`). Mas o que acontece se, no meio da busca binária, o Git cair em um commit antigo que nem sequer compila por outro motivo alheio ao bug que você está procurando? Qual código de saída especial o script deve retornar para mandar o Git pular (`skip`) aquele commit?

Consultar uma pista

É um número específico entre 1 e 127 reservado pela especificação do `git bisect run` exclusivamente para commits não testáveis.

Conferir a solução comentada

O script deve retornar o código de saída 125 (ou, no modo manual, você digita git bisect skip). Quando o script sai com código `125`, o Git entende que aquele snapshot específico não pôde ser testado e escolhe automaticamente um commit vizinho para continuar a busca binária sem falsos positivos.

Para testar a transferência do conceito: se uma versão lançada há um mês teve 256 commits desde então e um teste automatizado leva 5 segundos para rodar, quanto tempo no máximo um `git bisect run` levará para apontar o commit exato que quebrou a funcionalidade?

Conferir a transferência

Como $\log_2(256) = 8$, a busca binária precisará testar apenas cerca de 8 commits (40 segundos no total!), contra mais de 21 minutos se alguém testasse os 256 commits linearmente.

Entrega da etapa

Execute o experimento automatizado do git bisect run no seu repositório de laboratório para encontrar exatamente em qual commit a variável `taxa_base` deixou de ser `15` e depois garanta o retorno à branch normal com git bisect reset.

Conexão com o livro · Capítulo 7 (Seção 7.10) · Debugging with Git, git blame (-L, -C para código movido/copiado) e git bisect (start, bad, good, skip, reset e bisect run com exit code 0/1/125).

Oficina 08 / 08

Gerencie Submódulos, transfira commits offline com Bundle e armazene credenciais

Seu projeto principal depende de uma biblioteca interna que vive em outro repositório Git, ou você precisa enviar dez commits para um servidor isolado de fábrica que está sem acesso à rede externa. Como encerrar o Capítulo 7 dominando Submódulos, Bundles, Replace e Credenciais?

Submódulos sem armadilhas: clone recursivo, update --remote e push seguro

Na Seção 7.11, quando você precisa manter um repositório Git independente dentro de uma subpasta de outro repositório, você o adiciona com git submodule add <url>. Isso cria o arquivo de mapeamento .gitmodules e grava no projeto pai uma entrada especial de modo 160000 (que registra apenas o hash exato do commit em que o submódulo está apontado, e não os arquivos internos dele).

Quatro práticas recomendadas no livro evitam 99% das dores de cabeça com submódulos:

  1. Clonar já trazendo os submódulos: use git clone --recurse-submodules <url> (ou, se já tiver clonado sem a flag, rode git submodule update --init --recursive).
  2. Enxergar mudanças nos submódulos no status e no diff: configure git config --global diff.submodule log e git config status.submodulesummary 1 para que `git diff` e `git status` mostrem os commits novos do submódulo em vez de apenas hashes secos.
  3. Atualizar e trabalhar dentro de um submódulo: por padrão, `git submodule update` deixa a subpasta em estado detached HEAD. Antes de editar código dentro do submódulo, entre na pasta dele e faça checkout de uma branch (`git checkout main`), e use git submodule update --remote --merge (ou `--rebase`) no projeto pai para puxar e integrar novidades.
  4. Nunca esquecer de fazer push do submódulo antes do projeto pai: se você commitar no projeto pai apontando para um commit do submódulo que ainda só existe na sua máquina, mais ninguém conseguirá baixar o projeto! Use sempre git push --recurse-submodules=check (que bloqueia o push principal se faltar enviar o submódulo) ou git push --recurse-submodules=on-demand (que envia o submódulo automaticamente antes do projeto pai), além de rodar comandos em lote com git submodule foreach 'git stash'.
Comando para experimentar
# 1. Empacote todo o seu repositório (ou uma faixa de commits) em um único arquivo binário (.bundle)
#    para transferir via pendrive/e-mail quando a rede ou o servidor estiver fora do ar (Cap. 7.12):
cd ~/lab-git-etapa3
git bundle create ~/meu-pacote.bundle HEAD main

# 2. Verifique a integridade do bundle e veja que é possível dar git clone ou git fetch direto do arquivo!
git bundle verify ~/meu-pacote.bundle
git bundle list-heads ~/meu-pacote.bundle
# git clone ~/meu-pacote.bundle ~/clone-do-bundle

# 3. Configure melhorias de visibilidade para submódulos e cache de credenciais HTTPS (Cap. 7.11 e 7.14):
git config --global diff.submodule log
# git config --global credential.helper 'cache --timeout=3600'

Git Bundle (Sneakernet), Git Replace e Credential Storage

As três últimas seções do Capítulo 7 (7.12 a 7.14) fecham a caixa de ferramentas:

Com git bundle create arquivo.bundle HEAD main (Seção 7.12), você empacota tudo o que normalmente passaria pelo cabo no `git push` dentro de um único arquivo binário portátil! Outro computador sem acesso ao servidor pode validar o arquivo com `git bundle verify` e executar `git clone arquivo.bundle` ou `git fetch ../commits.bundle main:outra-main` (podendo inclusive empacotar apenas uma faixa incremental de commits com `git bundle create commits.bundle main ^9a466c5`).

Já com git replace <commit-recente> <commit-historico> (Seção 7.13), você pode enxertar um repositório de histórico antigo na base de um repositório novo e truncado sem precisar reescrever os hashes SHA-1 dos commits recentes! E na Seção 7.14, para quem usa repositórios via HTTPS, o Credential Storage oferece os modos `cache` (guarda a senha apenas na memória RAM por 15 minutos ou pelo `--timeout` escolhido), `store` (grava em texto puro no disco, indicado apenas para ambientes protegidos) e os gerenciadores criptografados nativos do sistema (`osxkeychain` no macOS e `manager` no Windows).

Entrega da etapa

Gere um pacote `.bundle` do seu repositório de laboratório, valide-o com `git bundle verify` e clone-o em uma nova pasta temporária para comprovar que um arquivo `.bundle` funciona como um repositório remoto completo offline.

Conexão com o livro · Capítulo 7 (Seções 7.11 a 7.15) · Submódulos (--recurse-submodules, foreach), git bundle (create, verify), git replace e Credential Storage.

Projeto de aplicação · Etapa 3

Monte seu kit de resgate, limpeza de histórico e caça a bugs.

Consolide as 15 seções do Capítulo 7 em um protocolo prático para os momentos mais delicados do desenvolvimento: quando o histórico precisa ser limpo antes do Pull Request, quando um commit parece ter sumido ou quando um bug misterioso entra em produção.

Roteiro de prática de uma semana

  1. Dia 1. Pratique a navegação por ancestralidade (`HEAD^2` vs. `HEAD~2`), inspecione o `git reflog` e configure `merge.conflictstyle diff3` e `diff.submodule log`.
  2. Dias 2 e 3. Em uma tarefa real, use `git add -p` para separar alterações do mesmo arquivo em dois commits distintos e use `git stash --keep-index` para testar isoladamente o que está na Staging Area.
  3. Dias 4 e 5. Antes de publicar sua branch, limpe os commits locais usando `git rebase -i` (`reword`, `squash`, `fixup` e `edit`) e pratique compactar commits rápidos com `git reset --soft HEAD~N`.
  4. Dia 6. Use `git grep -n -p` e `git log -L :funcao:arquivo` para auditar um módulo crítico do seu projeto, e rode um teste automatizado de busca binária com `git bisect run`.
  5. Dia 7. Empacote uma entrega portátil com `git bundle create`, verifique com `git bundle verify` e documente seu protocolo de reversão segura de merges com `git revert -m 1`.

Decisões para praticar

Escolha uma resposta em cada situação avançada do Capítulo 7. A correção detalha o funcionamento interno das ferramentas e permite tentar novamente.

1. Em um commit de merge que uniu uma branch de funcionalidade na "main", qual é a diferença entre referenciar "HEAD^2" e "HEAD~2"?
2. Você executou "git reset --soft HEAD~2" na sua branch local. O que aconteceu com o ponteiro HEAD, com o Index (Staging Area) e com o seu Working Directory?
3. Você precisou reverter na "main" um commit de merge público usando "git revert -m 1 HEAD". Na semana seguinte, após corrigir o bug na branch "topic", o que acontece se você rodar apenas "git merge topic" direto?
4. Qual comando rastreia e imprime todos os commits e diffs que alteraram especificamente a função "validar_token" dentro do arquivo "auth.py" ao longo de toda a história do projeto?

Minha síntese da Etapa 3

Checklist de domínio da Etapa 3

Consultar anotações das oito oficinas
Suas anotações ficam neste navegador.
Último passo na Trilha Pro Git · Etapa 4 de 4

Pronto para abrir o capô do Git e dominar sua arquitetura interna e administração?

Na Etapa 4: Administração e Arquitetura Interna (Capítulos 4, 8, 9 e 10), você vai configurar servidores Git, criar Git Hooks e atributos (.gitattributes), e construir objetos plumbing (blobs, trees, commits, packfiles e refspecs) diretamente no banco `.git`.

Continuar para a Etapa 4 →
Livro-base e mapa das 4 etapas do curso

Scott Chacon e Ben Straub. Pro Git, 2ª edição. Apress, 2014 (com atualizações contínuas da comunidade). Esta Etapa 3: Produtividade e Limpeza de Histórico cobre integralmente todas as 15 seções do Capítulo 7 (Git Tools) em uma adaptação didática e prática em português brasileiro. Edição oficial aberta em git-scm.com.

Mapa completo das 4 etapas desta formação: Etapa 1: O Básico Essencial (Caps. 1 e 2) · Etapa 2: Trabalho em Equipe e Fluxos (Caps. 3, 5 e 6) · Etapa 3: Produtividade e Limpeza de Histórico (Cap. 7 — Atual) · Etapa 4: Administração e Arquitetura Interna (Caps. 4, 8, 9 e 10).