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
- Geração inicial: Execute
git commit-graph write --reachable --changed-pathsapós clonar - Manutenção automática: Configure
git maintenancecom tarefas diárias - Atualização incremental: Use
--appendpara evitar regeneração completa - 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
- Documentação Oficial do Git - commit-graph — Referência completa do comando
git commit-graphcom todas as opções e flags disponíveis - Git Blog - Commit Graph Part I — Artigo técnico detalhado explicando a arquitetura e motivação por trás do commit-graph
- Git Blog - Commit Graph Part II — Continuação com detalhes sobre generation numbers e Bloom filters
- Microsoft Dev Blogs - Supercharging Git with commit-graph — Caso de uso real no Azure DevOps mostrando ganhos de performance em repositórios massivos
- Git SCM Wiki - Commit Graph Design — Especificação técnica do formato binário do commit-graph para implementadores e curiosos