Commit-graph: acelerando operações em históricos grandes

1. O Problema de Performance em Históricos Grandes

Repositórios Git com milhares ou milhões de commits sofrem de degradação significativa de performance. Operações como git log, git merge-base e git blame exigem percorrer o grafo de commits inteiro, commit por commit, lendo cada objeto do armazenamento de objetos. Em repositórios monorepo ou projetos longevos, essa travessia pode levar minutos.

Sem cache, cada commit requer:
- Descompressão do objeto commit
- Parsing do conteúdo
- Caminhada recursiva pelos pais

Para um histórico com 500 mil commits, git log --oneline pode levar mais de 10 segundos. git merge-base entre branches distantes pode consumir minutos. Esse custo computacional é linear ao número de commits, tornando-se inviável em escala.

2. O que é o Commit-graph?

O commit-graph é uma estrutura de dados binária armazenada em .git/objects/info/commit-graph. Diferente dos objetos commit tradicionais (texto, comprimidos, individuais), o commit-graph armazena metadados em formato otimizado para leitura sequencial:

  • SHA-1 dos commits
  • Datas de autor e committer
  • Arestas do grafo (pais)
  • Distânções geracionais (generation numbers)
  • Posições no arquivo para acesso direto

O formato é fixo e permite que o Git leia centenas de commits em uma única operação de I/O sequencial, em vez de abrir e descomprimir objetos individuais.

text
# Estrutura simplificada de um commit-graph
[HEADER]
  Número de commits: 500.000
  Número de arestas: 1.200.000

[COMMIT DATA]
  Commit A: SHA-1, data, posição, 2 pais
  Commit B: SHA-1, data, posição, 1 pai
  ...

[GENERATION NUMBERS]
  Commit A: 450
  Commit B: 320
  ...

3. Como o Commit-graph Acelera Operações

Leitura Sequencial vs. Objetos Individuais

Sem commit-graph, git log precisa:
1. Ler o commit atual (descompressão + parsing)
2. Seguir para cada pai (repetir passo 1)
3. Para 500 commits, ~500 operações de I/O aleatórias

Com commit-graph:
1. Ler bloco contínuo de dados binários (1 operação de I/O)
2. Extrair metadados diretamente
3. Para 500 commits, ~1 operação de I/O

Cálculo Rápido de Merge Bases

O generation number permite podar a busca de ancestrais comuns. Se o commit A tem generation 450 e o commit B tem generation 320, o Git sabe imediatamente que A não é ancestral de B, sem percorrer o grafo.

Aceleração de git log --graph

O commit-graph pré-computa as arestas, permitindo que --graph desenhe o histórico sem reabrir objetos. Filtros por data ou autor também se beneficiam, pois as datas estão no formato binário e podem ser comparadas sem parsing.

Exemplo prático de ganho:

text
# Antes do commit-graph (500k commits)
$ time git log --oneline -1000 > /dev/null
real    0m12.345s

# Depois do commit-graph
$ time git log --oneline -1000 > /dev/null
real    0m0.823s

4. Geração e Manutenção do Commit-graph

Comando git commit-graph write

Para gerar o commit-graph, execute:

text
$ git commit-graph write --reachable

Opções importantes:
- --reachable: inclui apenas commits alcançáveis a partir de branches e tags
- --stdin-packs: lê lista de packs do stdin (útil para integração com gc)
- --changed-paths: ativa Bloom filters para acelerar git log -- <caminho>

Atualização Incremental

O commit-graph pode ser atualizado incrementalmente:

text
$ git commit-graph write --reachable --append

Isso adiciona novos commits sem regenerar o arquivo inteiro.

Integração com git maintenance

O Git 2.30+ oferece git maintenance que gerencia automaticamente o commit-graph:

text
$ git maintenance start
$ git config maintenance.commit-graph.auto true

Isso cria tarefas agendadas que atualizam o commit-graph periodicamente.

5. Integração com Outros Recursos de Otimização

Sinergia com Multi-Pack Index

O multi-pack-index (MIDX) otimiza a busca de objetos em múltiplos packs. Combinado com commit-graph, acelera tanto a localização de commits quanto de blobs/trees. Em repositórios com muitos packs, a dupla reduz drasticamente o tempo de operações mistas.

Uso em Partial Clones

Partial clones baixam apenas commits e trees essenciais, mantendo blobs sob demanda. O commit-graph acelera a navegação local mesmo sem todos os objetos, pois os metadados dos commits estão disponíveis localmente.

Compatibilidade com Replace Objects e Shallow Clones

O commit-graph respeita objetos substituídos (replace objects) e funciona em shallow clones (histórico truncado). Para shallow clones, o commit-graph contém apenas os commits disponíveis localmente, mantendo a aceleração.

6. Verificação e Diagnóstico

Inspeção do Commit-graph

Verifique a integridade:

text
$ git commit-graph verify
commit-graph has 500000 commits and 1200000 parents

Obtenha informações detalhadas:

text
$ git commit-graph info
commit-graph has 500000 commits
commit-graph has 1200000 parent edges
commit-graph has generation numbers
commit-graph has Bloom filters for changed paths

Identificação de Problemas

Se o commit-graph estiver corrompido ou desatualizado, operações podem falhar ou retornar dados incorretos. Execute git commit-graph verify regularmente. Se detectar corrupção, remova o arquivo e regenere:

text
$ rm .git/objects/info/commit-graph
$ git commit-graph write --reachable

Métricas de Desempenho

Compare tempos de execução para validar ganhos:

text
$ time git merge-base branch-a branch-b
# Sem commit-graph: 45.2s
# Com commit-graph: 0.3s

$ time git log --oneline --graph --all | head -100
# Sem commit-graph: 8.7s
# Com commit-graph: 0.1s

7. Boas Práticas e Configuração Recomendada

Ativação Global

Habilite o commit-graph permanentemente:

text
$ git config --global core.commitGraph true

Estratégias para Repositórios Grandes

  1. Geração inicial: Execute git commit-graph write --reachable --changed-paths após clonar
  2. Manutenção automática: Configure git maintenance com tarefas diárias
  3. Atualização incremental: Use --append para evitar regeneração completa
  4. Monitore o tamanho: Commit-graphs muito grandes (>1GB) podem precisar de regeneração periódica

Combinação com gc.auto

Ajuste o garbage collection para regenerar o commit-graph:

text
$ git config gc.auto 5000
$ git config gc.commitGraph true

Isso garante que o commit-graph seja atualizado durante operações de gc.

Exemplo de configuração completa:

text
$ git config core.commitGraph true
$ git config gc.commitGraph true
$ git config maintenance.commit-graph.auto true
$ git maintenance start

Com essa configuração, o commit-graph é mantido automaticamente, garantindo performance consistente mesmo em repositórios com milhões de commits.


O commit-graph transforma operações que levavam minutos em operações subsegundo. Para equipes que trabalham com repositórios grandes ou monorepos, é uma otimização essencial que deve ser ativada por padrão.

Referências