Guide
Algoritmos de diff de texto: como Git, patches e ferramentas de diff funcionam
Um diff é um script de edição válido entre muitos — o algoritmo escolhe qual você precisa ler.
By Buğra SözeriPublished
Toda ferramenta de revisão de código, toda saída de git diff, todo arquivo de patch que você já leu remonta a um pequeno número de algoritmos inventados entre 1976 e 2009. Saber qual o seu tool usa — e que suposição ele faz — é a diferença entre ler um diff em 30 segundos e ficar olhando para um confuso por dez minutos, imaginando se a refatoração realmente preservou o comportamento.
O que um diff realmente é
Um diff é um script de edição: uma sequência mínima de inserções e exclusões que transforma o arquivo A no arquivo B. Para dois arquivos de comprimento total N com D diferenças, existem muitos scripts possíveis. Um algoritmo de diff escolhe um de acordo com algum critério de otimização — geralmente o menor D, mas nem sempre.
Dois corolários seguem. Primeiro, a mesma mudança de arquivo pode legitimamente produzir diffs com aparências diferentes de ferramentas diferentes; todos estão corretos no sentido de que aplicá-los reconstrói o arquivo B. Segundo, “diff legível” é uma restrição mais fraca do que “diff menor”, e os dois frequentemente discordam. Você pode comparar dois arquivos com nossa ferramenta de diff de texto e ver a saída unificada lado a lado.
Diff Myers — o padrão em toda parte
O artigo de 1986 de Eugene Myers An O(ND) Difference Algorithm and Its Variations definiu o algoritmo que GNU diff, Git, Mercurial, SVN e a maioria dos editores ainda usam por padrão. Ele enquadra o problema de diff como encontrar o caminho mais curto por um grafo de edição: cada passo diagonal é uma linha correspondente, cada passo horizontal exclui uma linha de A, cada passo vertical insere uma linha de B.
A fraqueza do Myers é que ele minimiza o tamanho do diff sem nenhum senso de estrutura. Após mover um bloco de código, Myers frequentemente emparelha a chave de fechamento de uma função com a de outra diferente porque isso produz menos linhas alteradas no total.
Diff Patience — projetado para legibilidade
Bram Cohen (o autor do BitTorrent) introduziu o Patience diff em 2007 especificamente para corrigir o problema de legibilidade de refatoração do Myers. O algoritmo:
- Encontra todas as linhas que aparecem exatamente uma vez em ambos os arquivos. Essas são linhas-âncora únicas.
- Calcula a subsequência comum mais longa das âncoras únicas.
- Faz o diff recursivamente em cada região entre âncoras consecutivas, recorrendo ao Myers quando não há âncoras únicas restantes.
O resultado alinha o diff em torno das linhas que um humano reconheceria como “a mesma coisa” — assinaturas de função, comentários únicos, declarações de classe. Ative no Git com git diff --patience ou globalmente com git config diff.algorithm patience.
Diff Histogram — Patience, refinado
O Histogram diff foi introduzido pelo JGit (a implementação Java do Git) e depois portado para o Git principal. Ele melhora o Patience sendo mais inteligente sobre quais linhas-âncora escolher: em vez de exigir unicidade, ele escolhe as linhas raras com a menor contagem de ocorrências em ambos os arquivos. Ative globalmente com git config --global diff.algorithm histogram.
Lendo um hunk de diff unificado
O diff unificado (-u) é o formato de troca de facto. Um hunk se parece com:
@@ -42,7 +42,9 @@ class UserController {
const user = await User.findById(id);
if (!user) {
- return res.status(404).end();
+ logger.warn({ id }, "user not found");
+ return res.status(404).json({ error: "not found" });
}
return res.json(user);
}A linha @@ é o cabeçalho do hunk. Ela diz: o arquivo antigo começa na linha 42 e abrange 7 linhas; o novo arquivo começa na linha 42 e abrange 9 linhas. Linhas sem prefixo são contexto inalterado. Linhas começando com - existem apenas no arquivo antigo; linhas começando com + existem apenas no novo.
Espaços em branco e terminações de linha
- Espaço em branco à direita. Configure
core.whitespaceegit diff --ignore-space-at-eol. - Indentação mista. Use
git diff -wpara ignorar todas as diferenças de espaço em branco. - Terminações de linha. CRLF vs LF. Configure
.gitattributescom* text=autopara normalizar no commit. - Nova linha final. Arquivos sem nova linha final mostram
\ No newline at end of fileno diff.
Merge de três vias e por que ocorrem conflitos
Quando você faz merge do branch B no branch A, o Git examina três versões: o ancestral comum C, a versão A e a versão B. O merge procede linha por linha:
- Linha inalterada de C para A, mas modificada em B → pega B.
- Linha modificada em A, mas inalterada em B → pega A.
- Linha modificada de forma idêntica em ambos → pega qualquer uma; sem conflito.
- Linha modificada de forma diferente em A e B → conflito.
Conflitos são sinalizados no arquivo de trabalho com <<<<<<<, ======= e >>>>>>> mostrando ambas as versões.
Escolhendo um algoritmo na prática
Para o trabalho diário, Histogram é o melhor padrão e o que a maioria das equipes deve configurar globalmente. Patience produz resultados muito similares e permanece útil para arquivos muito grandes. Myers é a escolha certa para pipelines máquina-a-máquina que consomem diffs programaticamente.
A conclusão honesta
Mude seu algoritmo global do Git para Histogram e nunca pense nisso novamente para a maioria dos repositórios. Quando um diff confundir você após uma refatoração, renderize novamente com um algoritmo diferente antes de assumir que a mudança está errada. Normalize espaços em branco e terminações de linha no nível do repositório para que os revisores gastem seu tempo com lógica, não com formatação.
Frequently asked questions
- Por que meus diffs do Git às vezes parecem estranhos após uma refatoração?
- O diff Myers — padrão do Git — minimiza o número total de linhas alteradas, o que não é o mesmo que produzir o diff mais legível para humanos. Após mover uma função ou renomear uma variável, Myers frequentemente emparelha chaves ou linhas em branco não relacionadas. Mudar para `--patience` ou `--histogram` geralmente produz um resultado mais sensato para refatorações.
- Qual é a diferença entre os formatos diff unificado e de contexto?
- Ambos mostram linhas alteradas em seu contexto circundante. O formato unificado (`diff -u`, usado pelo Git) intercala linhas antigas e novas em um bloco, prefixadas por `-` e `+`. O formato de contexto (`diff -c`) mostra o bloco antigo e o novo separadamente. Unificado é mais compacto e é o que toda ferramenta moderna de revisão de código espera.
- Como o Git lida com arquivos binários em um diff?
- Ele não tenta fazer o diff. O Git detecta conteúdo binário (procurando bytes nulos nos primeiros 8 KB) e emite `Binary files a/x and b/x differ`. Você pode forçar um diff textual com `--text`, mas o resultado raramente é útil. Para imagens e PDFs, use `git diff --binary` para produzir um patch que possa ser aplicado sem o arquivo original.
- Por que houve conflito de merge se eu só alterei espaços em branco?
- O merge de três vias compara ambos os branches com um ancestral comum e tenta combinar as mudanças. Se ambos os branches tocaram a mesma linha — mesmo com edições não semânticas como espaços em branco, terminações de linha ou novas linhas finais — a ferramenta de merge sinaliza um conflito.
- Dois diffs diferentes podem ser ambos corretos para a mesma mudança de arquivo?
- Sim. Um diff é um script de edição válido que transforma o arquivo A no arquivo B; muitos scripts igualmente curtos frequentemente existem. Myers prefere o com menos linhas alteradas; Patience prefere o ancorado por linhas de correspondência únicas. Ambos produzem um resultado correto — o arquivo de destino é idêntico — mas os hunks parecem diferentes.
- O que é um merge de três vias e por que o Git o usa?
- O merge de três vias examina ambos os branches e seu ancestral comum mais recente. Se uma linha foi modificada em apenas um branch, a versão desse branch vence automaticamente; se ambos os branches modificaram a mesma linha, um conflito é gerado. O terceiro ponto (o ancestral) é o que permite ao Git distinguir “ambos os lados adicionaram a mesma linha” (sem conflito) de “ambos os lados alteraram a linha de forma diferente” (conflito).
Related
Published May 31, 2026