Quatro ferramentas respondem quase tudo sobre o passado de um código: blame diz quem tocou cada linha por último (e com -w -C atravessa reformatação e movimentação de arquivo); a pickaxe (log -S) encontra o commit em que um trecho apareceu ou sumiu; log -L segue a evolução de uma função específica ao longo do tempo; e log --diff-filter=D acha quando algo foi apagado. A armadilha universal é o blame apontar para o commit de reformatação em massa — resolvida com um arquivo de revisões ignoradas.
As perguntas que aparecem no legado
Você abre um arquivo de um sistema com dez anos e encontra:
// não remover — quebra o relatório da diretoriaif (cliente.getTipo() == 3 && !feriado) { aplicarDescontoLegado(pedido);}
Três perguntas imediatas: quando isso entrou? quem escreveu? qual era o problema? Sem essas respostas, você tem duas opções ruins — mexer no escuro ou não mexer nunca.
O repositório sabe. Só é preciso saber perguntar.
blame: quem tocou cada linha por último
git blame arquivo.javagit blame -L 40,60 arquivo.java # só um trechogit blame <commit> -- arquivo.java # como estava naquele ponto do tempo
Cada linha vem com o commit, o autor e a data. Daí você pega o hash e lê o contexto completo:
git show <hash> # a mudança inteira e a mensagemgit log --merges --ancestry-path <hash>..HEAD | tail -5 # por qual PR isso entrou
Essa segunda linha é o truque de investigação mais útil da nota: ela encontra o commit de merge que trouxe aquele commit para a linha principal. E, com a estratégia de merge do time, a mensagem daquele merge costuma trazer o número do PR — que leva à discussão, à revisão e à issue. É o caminho do código de volta ao por quê.
As três flags que fazem o blame funcionar de verdade
git blame -w -C -C -- arquivo.java
-w ignora mudanças de espaço em branco. Sem isso, uma reindentação faz o blame apontar para quem reindentou.
-C detecta linhas movidas ou copiadas de outro arquivo no mesmo commit. Repetido (-C -C), procura também em arquivos que o commit não tocou.
-M detecta movimentação dentro do mesmo arquivo.
Sem elas, num código que passou por refatorações, o blame mente com frequência — atribuindo tudo a quem reorganizou.
O commit de reformatação em massa que apaga a história
O que acontece: o time rodou um formatador automático em 2023 e commitou tudo de uma vez. Hoje o blame do projeto inteiro aponta para aquele commit e para aquela pessoa.
Por quê: a linha foi de fato modificada — o Git está sendo literal.
Como resolver: liste os commits a ignorar num arquivo e diga ao Git para pulá-los.
Versione esse arquivo — assim todo o time e as plataformas de hospedagem (que o reconhecem) passam a ignorar os mesmos commits. Fazer isso no mesmo dia de qualquer reformatação em massa é uma prática que poupa anos de blame inútil.
Pickaxe: quando este trecho apareceu ou sumiu
O blame responde sobre o estado atual. Ele não ajuda quando o que você procura não está mais lá — a função que foi removida, a constante que sumiu, o TODO que alguém apagou.
git log -S"aplicarDescontoLegado" --oneline # commits que MUDARAM a quantidade de ocorrênciasgit log -S"aplicarDescontoLegado" -p # com o diff juntogit log -G"desconto.*legado" --oneline # regex: commits cujo diff casa com o padrãogit log -S"CHAVE_API" --all # em todos os ramos
A diferença entre -S e -G importa:
-S conta ocorrências: mostra só os commits onde a quantidade daquele texto mudou — tipicamente onde ele nasceu e onde morreu. É preciso e silencioso.
-G casa contra o texto do diff: mostra todo commit cujo diff mencione o padrão, inclusive movimentações. É abrangente e ruidoso.
Comece com -S. Ele é a ferramenta certa para “quando esta coisa entrou no código?”, e costuma devolver dois ou três commits em vez de duzentos.
log -L: a evolução de uma função
git log -L :aplicarDescontoLegado:PedidoService.java # por nome de funçãogit log -L 40,60:arquivo.java # por faixa de linhas
Ele reconstrói a história daquele trecho ao longo do tempo, seguindo movimentações. É a visão que responde “como esta função chegou a este estado”, mostrando cada modificação em ordem — muito mais direto que ler o histórico do arquivo inteiro.
Quando foi apagado
git log --diff-filter=D --oneline -- caminho/do/arquivo.java # o commit que deletougit log --all --full-history -- caminho/do/arquivo.java # tudo que tocou o arquivo, mesmo removidogit show <hash>^:caminho/do/arquivo.java # o conteúdo antes de sumir
O --full-history é necessário porque, por padrão, o Git simplifica o histórico e pode omitir commits que ele julga irrelevantes para o estado atual — o que é exatamente o que você não quer numa investigação.
O ^ no último comando é “o pai do commit” (nota 18): você está pedindo o arquivo como estava imediatamente antes da remoção.
Outras perguntas úteis
# quem mais mexeu neste arquivo (candidatos a conversar)git shortlog -sn --no-merges -- src/pagamentos/# o que mudou entre duas versões publicadasgit log --oneline v1.4.0..v1.5.0# quando este arquivo foi renomeadogit log --follow --name-status --oneline -- arquivo.java# quais commits tocaram este arquivo E aquelegit log --oneline -- a.java b.java# o commit está em qual release?git tag --contains <hash>git branch -a --contains <hash>
O último par é ouro em investigação de incidente: git tag --contains responde em quais versões publicadas aquele commit está — ou seja, quem foi afetado.
Armadilhas comuns
Confiar no blame como atribuição de culpa
O que acontece: o nome que aparece é usado para responsabilizar alguém.
Por quê: além de o blame mostrar apenas quem tocou por último — que pode ter só reindentado, ou aplicado uma mudança decidida por outra pessoa —, o uso punitivo destrói a disposição do time em mexer em código antigo.
Como usar direito: o blame é para achar contexto e com quem conversar, não culpado. A pergunta útil é “você lembra por que isso ficou assim?”, nunca “por que você fez isso?“.
Investigar num clone raso
O que acontece:blame mostra tudo atribuído ao commit inicial, pickaxe não acha nada.
Por quê: a história não está lá (notas 27 e 30).
Como evitar: para investigação, clone completo ou parcial — nunca raso.
Parar no commit e não chegar ao contexto
O que acontece: você acha o commit e a mensagem diz “ajustes”.
Por quê: a disciplina da nota 14 não existia naquele time.
Como contornar: suba um nível — ache o merge (--ancestry-path), o PR, a issue. E, se nada disso existir, olhe os commits vizinhos no tempo: git log --since=<data-1dia> --until=<data+1dia> --all costuma revelar o que estava acontecendo naquela semana, o que dá contexto mesmo sem mensagem.
Resumo em uma frase
blame responde “quem tocou por último”, pickaxe responde “quando isto nasceu ou morreu”, -L responde “como isto evoluiu” — e o caminho do commit até o PR é o que devolve o porquê.
Vídeo — blame do jeito que um mantenedor usa
how I use git blame (anthonywritescode, 6 min) mostra o gesto real: nunca é um blame só, é blame → pegar o hash → show → subir mais um nível quando o commit é de reformatação. Ver o encadeamento é o que separa “conhecer o comando” de usá-lo para investigar.
Pratique
Pegue um repositório grande e antigo que você não conhece — o do próprio Git serve — e responda três perguntas usando só a linha de comando:
Quando a opção --force-with-lease foi introduzida? (git log -S"force-with-lease" --oneline | tail -3)
Quem escreveu a linha 100 de um arquivo qualquer, ignorando reformatações? (git blame -w -C -L 100,100 <arquivo>)
Aquele commit está em quais versões publicadas? (git tag --contains <hash>)
Fazer isso num código alheio, sem contexto nenhum, é exatamente o exercício do capstone — e a sensação de conseguir responder é o argumento deste nível inteiro.
O que vem a seguir
A pickaxe encontra o commit quando você sabe o que procurar. E quando você só sabe que algo quebrou entre a versão que funcionava e a de hoje, sem ideia de onde? Aí entra a busca binária no grafo.
32 — bisect: achar o commit que quebrou — inclusive automatizado.