Git range-diff: comparando séries de commits entre branches

1. Introdução ao git range-diff

O git range-diff é um comando Git introduzido na versão 2.19 que permite comparar duas séries de commits entre si, identificando correspondências e diferenças entre elas. Diferentemente do git diff (que compara o conteúdo de arquivos) e do git log (que exibe o histórico de commits), o range-diff opera em um nível mais alto: ele compara conjuntos de commits como sequências, detectando quais commits foram mantidos, modificados, reordenados ou removidos.

Os cenários típicos de uso incluem:

  • Revisão de rebase: verificar o que mudou após um git rebase -i
  • Validação de cherry-pick: comparar commits originais com os cherry-pickados
  • Análise de squash: entender como commits foram combinados
  • Revisão de merge: comparar a série de commits de uma feature branch antes e depois de um merge

2. Sintaxe básica e modos de operação

O formato fundamental do comando é:

git range-diff <range1> <range2>

Onde cada range pode ser especificado de duas formas:

Comparação direta entre séries:

git range-diff main~5..main topic~5..topic

Isso compara os últimos 5 commits de main com os últimos 5 commits de topic.

Uso com ranges simétricos:

git range-diff A...B C...D

O operador ... (três pontos) cria um range simétrico, incluindo commits que estão em A mas não em B, e vice-versa. Isso é útil para comparar branches que divergiram.

Exemplo prático:

# Comparar os últimos 3 commits de develop com os de feature
git range-diff develop~3..develop feature~3..feature

3. Entendendo a saída do range-diff

A saída do range-diff é estruturada em seções que mostram:

  1. Commits correspondentes: identificados por números de sequência
  2. Commits não correspondentes: exibidos com indicadores especiais
  3. Diferenças no patch: mostradas linha a linha

Os indicadores de mudança incluem:

  • -: linha removida no commit correspondente
  • +: linha adicionada no commit correspondente
  • : linha inalterada
  • !: indica que o commit não tem correspondente (aparece apenas em um dos ranges)

Exemplo de saída:

1:  123abcd ! 2: 456efgh
    @@ -12,6 +12,7 @@
     função antiga() {
   +    nova funcionalidade
     }

2:  789ijkl ! 3: 012mnop
    @@ -5,3 +5,5 @@
     - linha removida
     + linha adicionada

A primeira linha 1: 123abcd ! 2: 456efgh indica que o commit 123abcd (primeiro range) corresponde ao commit 456efgh (segundo range), mas com diferenças. O patch abaixo mostra exatamente o que mudou.

4. Aplicações práticas: revisão de rebase interativo

O uso mais comum do range-diff é verificar o resultado de um git rebase -i. Suponha que você tenha:

# Antes do rebase (branch feature)
git log --oneline feature
a1b2c3d Commit 3
d4e5f6g Commit 2
h7i8j9k Commit 1

# Após rebase interativo com squash e reword
git rebase -i main
# Resultado:
l0m1n2o Commit 3 (modificado)
p3q4r5s Commit 1+2 (squash)

Para verificar o que mudou:

git range-diff main~3..main feature~3..feature

A saída mostrará:

  • O commit h7i8j9k (Commit 1) foi combinado com d4e5f6g (Commit 2) no novo commit p3q4r5s
  • O commit a1b2c3d (Commit 3) foi modificado para l0m1n2o
  • As diferenças específicas de cada commit são exibidas

Isso permite identificar rapidamente:

  • Commits removidos: aparecem sem correspondente
  • Commits reordenados: a numeração muda
  • Commits modificados: mostram o patch diff
  • Squashes: múltiplos commits antigos mapeiam para um novo

5. Aplicações práticas: validação de cherry-pick e merge

Cherry-pick de múltiplos commits:

# Branch origem: feature
git checkout main
git cherry-pick feature~3..feature

# Verificar se o cherry-pick foi fiel
git range-diff feature~3..feature main~3..main

Se houver conflitos resolvidos manualmente, o range-diff mostrará exatamente quais linhas foram alteradas durante a resolução, permitindo verificar se a resolução foi correta.

Revisão de merge de feature branches:

# Antes do merge
git checkout main
git merge feature

# Comparar commits da feature antes e depois
git range-diff feature~5..feature main~5..main

Isso é particularmente útil quando o merge gera um commit de merge com conflitos resolvidos. O range-diff mostrará se algum commit da feature foi alterado durante o processo.

Detectando alterações acidentais:

# Após um rebase, verificar se apenas o esperado mudou
git range-diff ORIG_HEAD..HEAD@{1} HEAD~3..HEAD

6. Dicas avançadas e flags úteis

--creation-factor: Controla a sensibilidade na correspondência entre commits. O valor padrão é 60 (percentual). Valores mais baixos (ex: 30) permitem corresponder commits mais diferentes; valores mais altos (ex: 90) exigem maior similaridade.

# Permitir correspondência mesmo com mudanças significativas
git range-diff --creation-factor=30 A..B C..D

--no-color e --no-stat: Úteis para scripts e automação:

# Saída limpa para processamento em script
git range-diff --no-color --no-stat A..B C..D

Combinando com git log:

# Ver apenas os hashes dos commits correspondentes
git range-diff A..B C..D | grep "^[0-9]:" | cut -d' ' -f2

Com git format-patch:

# Gerar patches e comparar
git format-patch -o patches_old A~3..A
git format-patch -o patches_new B~3..B
git range-diff patches_old/* patches_new/*

Isso permite comparar séries de commits mesmo que os ranges não estejam mais disponíveis no repositório atual.

7. Limitações e boas práticas

Quando range-diff não é adequado:

  • Grandes reescritas: se um rebase reescreve completamente a lógica, o algoritmo de correspondência pode falhar
  • Squash total: quando múltiplos commits são combinados em um único, a correspondência individual se perde
  • Histórias lineares muito longas: a comparação pode se tornar lenta

Impacto em performance:

O range-diff compara cada commit de um range com cada commit do outro range, calculando similaridade. Para ranges com mais de 50 commits, o comando pode demorar significativamente. Estratégias para mitigar:

  • Limitar o range com ~N (ex: main~10..main)
  • Usar --creation-factor mais alto para reduzir comparações

Boas práticas para code review:

  1. Sempre use range-diff após rebase interativo para verificar se nenhuma alteração foi perdida
  2. Documente o comando usado em mensagens de commit ou PRs
  3. Combine com git log --oneline para ter contexto visual antes/depois
  4. Em pipelines CI, use --no-color e capture a saída para análise automatizada

Exemplo de workflow completo:

# 1. Antes do rebase, salve a referência
git branch backup_feature

# 2. Execute o rebase
git rebase -i main

# 3. Verifique o resultado
git range-diff backup_feature~5..backup_feature feature~5..feature

# 4. Se tudo correto, remova o backup
git branch -D backup_feature

O git range-diff é uma ferramenta essencial para qualquer desenvolvedor que trabalhe com Git em projetos colaborativos, especialmente quando operações de reescrita de histórico estão envolvidas. Dominá-lo significa ter controle total sobre a integridade do histórico de commits.

Referências