Resolvendo Operações Lentas no Git: Armadilhas Comuns e Soluções

Diagnostique comandos Git lentos separando causas relacionadas a status, clone, fetch, push, hooks, sistema de arquivos, rede e tamanho do repositório.

Resolvendo Operações Lentas no Git: Armadilhas Comuns e Soluções

Um Git lento tem causas diferentes dependendo de qual comando está lento. Um git status lento geralmente é problema local de sistema de arquivos ou índice. Um git fetch lento geralmente é problema de rede, tamanho remoto ou negociação. Um git checkout lento pode ser devido à quantidade de arquivos, varredura de antivírus, problemas de sparse checkout ou arquivos gerados. Um git push lento pode ser devido a objetos grandes, hooks, compressão ou servidor remoto.

Portanto, a primeira correção não é git gc. A primeira correção é medir a operação exata.

No macOS ou Linux:

time git status
time git fetch --prune
time git checkout main

No PowerShell:

Measure-Command { git status }

Execute o comando duas vezes. A primeira execução pode ser mais lenta porque o cache do sistema operacional está frio. Se o primeiro git status levar dez segundos e o segundo levar um, você pode estar lidando com comportamento de cache de disco. Se ambos forem lentos, continue investigando.

O Git tem rastreamento interno que pode mostrar onde o tempo é gasto:

GIT_TRACE=1 git status
GIT_TRACE_PERFORMANCE=1 git status
GIT_TRACE_PACKET=1 GIT_TRACE=1 git fetch

GIT_TRACE_PACKET é barulhento, mas útil quando fetch ou push trava durante a negociação de protocolo. Não cole saída de rastreamento com URLs de repositório privado ou tokens em tickets públicos.

Quando git status Está Lento

git status verifica o índice e a árvore de trabalho. Fica lento quando o repositório tem um número enorme de arquivos, a árvore de trabalho está em um sistema de arquivos lento, os metadados do arquivo são caros para ler, ou outro programa escaneia cada arquivo que o Git toca.

Comece com o básico:

git status --short
git config --show-origin --get core.fsmonitor
git config --show-origin --get core.untrackedCache
git config --show-origin --get core.preloadIndex

Para árvores de trabalho grandes, essas configurações podem ajudar em muitos sistemas:

git config core.untrackedCache true
git config core.preloadIndex true

Use a configuração local primeiro para testar por repositório. Se ajudar, torne-a global depois.

O monitor de sistema de arquivos interno do Git pode acelerar o status evitando varreduras completas em plataformas e versões do Git suportadas:

git config core.fsmonitor true

Se o status se tornar incorreto ou estranho após ativá-lo, desligue-o e atualize o Git antes de tentar novamente:

git config --unset core.fsmonitor

Arquivos não rastreados podem ser um problema oculto. Saídas de build, diretórios de dependências, relatórios gerados e logs locais geralmente devem ser ignorados. Verifique o que o Git está escaneando:

git status --untracked-files=all --short | head -100

Se você vir node_modules/, dist/, .venv/, target/ ou diretórios gerados semelhantes, adicione os padrões corretos ao .gitignore. Não ignore arquivos fonte apenas para acelerar o status. Ignore arquivos que realmente não devem ser versionados.

No Windows, a varredura antivírus em tempo real é uma razão comum para o Git parecer lento. O Git lê muitos arquivos pequenos dentro de .git e da árvore de trabalho, e o software de segurança pode inspecionar cada acesso. Se sua organização permitir, exclua espaços de trabalho de desenvolvimento confiáveis da varredura em tempo real. Não exclua diretórios onde você executa código não confiável.

Evite também colocar repositórios ativos em pastas de sincronização em nuvem como OneDrive, Dropbox ou iCloud Drive. Ferramentas de sincronização podem bloquear arquivos, reescrever metadados e competir com as próprias operações de arquivo do Git.

Quando Clone ou Fetch Está Lento

Um clone lento pode significar um histórico grande, muitos blobs grandes, um remoto lento ou um caminho de rede com alta latência. Meça o tamanho do repositório após clonar:

git count-objects -vH
du -sh .git 2>/dev/null

Para jobs de CI e ambientes temporários, use um clone raso quando o histórico não for necessário:

git clone --depth 1 <url>

Para um build de branch:

git clone --depth 1 --branch main <url>

Clones rasos não são ideais para todos os fluxos de trabalho. Comandos que precisam de histórico, tags, bases de merge ou cálculos de versão podem falhar ou produzir respostas incompletas. Em CI, isso geralmente é aceitável. Em uma máquina de desenvolvedor, pode ser frustrante.

Clone parcial é útil quando o histórico do repositório é necessário, mas os blobs de arquivo podem ser baixados preguiçosamente:

git clone --filter=blob:none <url>

Isso funciona melhor com servidores Git modernos que suportam bem clone parcial. Teste com seu host antes de torná-lo a recomendação oficial da equipe.

Se você precisa apenas de uma parte de um monorepo, combine sparse checkout com um clone normal ou parcial:

git clone --filter=blob:none --sparse <url>
cd repo
git sparse-checkout set services/api shared/lib

Sparse checkout reduz o tamanho da árvore de trabalho. Não torna mágicamente todas as operações Git baratas, mas ajuda quando a quantidade de arquivos é o principal problema.

Para fetches com muitas branches remotas deletadas, remova referências obsoletas:

git fetch --prune

Para tornar isso o padrão:

git config --global fetch.prune true

Quando Push Está Lento

A velocidade do push depende de quantos novos dados de objeto você envia, quão caro é o empacotamento local, se hooks são executados e quão rapidamente o remoto aceita o pacote.

Verifique se você acidentalmente cometeu arquivos grandes:

git rev-list --objects --all | sort -k 2 | tail

Esse comando é grosseiro porque não mostra tamanhos. Para inspeção mais profunda, use ferramentas como git-sizer ou comandos de análise do git filter-repo, se disponíveis. O ponto prático é simples: se um vídeo, dump de banco de dados, arquivo ou artefato de build entrou no histórico, cada clone pode pagar por isso até que o histórico seja reescrito ou o projeto mude para um padrão de armazenamento melhor.

Git LFS é a resposta usual para ativos binários grandes que pertencem ao projeto, mas não devem viver como blobs Git normais:

git lfs install
git lfs track "*.psd"
git lfs track "*.mp4"
git add .gitattributes

Git LFS ajuda mais quando adotado antes que arquivos grandes entrem no histórico. Migrar o histórico existente é possível, mas reescreve commits e precisa de coordenação da equipe.

Tenha cuidado com conselhos antigos de aumentar http.postBuffer. É frequentemente sugerido para problemas de push, mas raramente corrige lentidão geral no Git moderno. Se pushes falharem com erros HTTP específicos, verifique o erro exato, proxy, limites do servidor e versão do Git antes de aplicar configurações aleatórias de buffer.

Manutenção do Repositório: git gc, Gráficos de Commit e Repack

O Git armazena objetos em packfiles. Com o tempo, repositórios locais podem acumular objetos soltos e packs ineficientes. O Git executa manutenção automaticamente em muitos fluxos de trabalho, mas a manutenção manual ainda pode ajudar repositórios mais antigos ou movimentados.

Comece com um comando de manutenção seguro:

git maintenance run

Ou o comando mais antigo:

git gc

Evite fazer de git gc --prune=now seu primeiro movimento casual. Podar imediatamente remove objetos inalcançáveis que de outra forma seriam recuperáveis por um tempo. Pode ser bom quando você sabe o que está fazendo, mas não é um botão de velocidade inofensivo.

Para repositórios com históricos grandes, gráficos de commit podem melhorar as caminhadas de histórico usadas por comandos como log, merge-base e negociação de fetch:

git commit-graph write --reachable

A manutenção moderna do Git pode lidar com isso para você. Verifique sua versão:

git --version

Manter o Git atualizado é uma das correções de desempenho menos dramáticas. Versões mais novas melhoram regularmente sparse checkout, clone parcial, monitoramento de sistema de arquivos e comportamento de manutenção.

Repositórios Grandes e Monorepos

Se um repositório é lento porque é genuinamente grande, ajustes locais só vão até certo ponto. Você precisa de mudanças no fluxo de trabalho.

Para repositórios com muitos binários, mova ativos grandes para Git LFS ou um armazenamento de artefatos. Para arquivos gerados, pare de commitar saídas que podem ser reconstruídas. Para monorepos, use sparse checkout e ferramentas de build que entendam limites de projeto. Para CI, evite clones de profundidade total a menos que o job precise do histórico completo.

Uma configuração útil de monorepo para um desenvolvedor trabalhando em um serviço pode ser:

git clone --filter=blob:none --sparse <url>
cd repo
git sparse-checkout set services/billing packages/common

Uma configuração útil de CI para um job de teste simples pode ser:

git fetch --depth 50 origin main

A profundidade certa depende do job. Se sua ferramenta de versionamento usa tags de meses atrás, uma profundidade de 1 vai quebrá-la.

Hooks e Ferramentas Externas

O Git pode não ser a parte lenta. Um hook pre-commit pode executar formatadores, linters, testes, varreduras de segredos ou verificações de dependências. Um hook post-checkout pode reconstruir arquivos. Um auxiliar de credenciais pode pausar enquanto tenta desbloquear um chaveiro.

Verifique hooks:

git config --get core.hooksPath
ls -l .git/hooks .githooks 2>/dev/null

Compare temporariamente com hooks desabilitados apenas se você entender o risco:

git commit --no-verify

Para comandos que não são commit, mova ou desabilite o hook em uma cópia de teste do repositório em vez de deletar hooks da equipe do seu checkout principal.

Se uma IDE torna o Git lento, mas o terminal é rápido, inspecione as integrações Git da IDE. Algumas ferramentas executam git status repetidamente, escaneiam arquivos não rastreados ou atualizam o estado da branch em segundo plano.

Verificações de Rede e Remoto

Para operações remotas, separe o Git do caminho de rede. Tente:

GIT_TRACE_PERFORMANCE=1 git ls-remote <url>
GIT_TRACE_PERFORMANCE=1 git fetch

Se git ls-remote é lento, o atraso acontece antes que muitos dados do repositório sejam transferidos. Pense em DNS, proxy, VPN, autenticação SSH, disponibilidade remota ou prompts de credenciais. Se ls-remote é rápido, mas fetch é lento, o tamanho dos dados do repositório e a negociação são mais prováveis.

Para remotos SSH, teste SSH diretamente:

ssh -T [email protected]

Use seu host Git real. Para remotos HTTPS, prompts do gerenciador de credenciais podem estar ocultos atrás de janelas GUI. Um fetch travado pode estar esperando autenticação.

Uma Árvore de Decisão Curta

Se git status é lento, inspecione arquivos não rastreados, diretórios gerados, antivírus, pastas de sincronização em nuvem, monitor de sistema de arquivos e configurações de índice.

Se clone é lento, considere clone raso, clone parcial, sparse checkout, Git LFS e se o histórico do repositório contém blobs grandes.

Se fetch é lento, remova referências obsoletas, atualize o Git, inspecione rastreamentos de rede e verifique se o remoto tem muitas branches ou tags.

Se push é lento, procure por novos objetos grandes, hooks lentos, verificações do lado do servidor e problemas de rede ou proxy.

Se todos os comandos Git são lentos, verifique saúde do disco, espaço livre, software de segurança, versão do Git e se o repositório está em um ponto de montagem de rede.

A melhor correção é aquela que corresponde ao gargalo medido. O trabalho de desempenho do Git fica confuso quando todas as sugestões são aplicadas de uma vez. Mude uma coisa, meça novamente e mantenha a mudança apenas se realmente ajudar.

Correções em Nível de Equipe Superam Ajustes Pessoais

Se apenas um desenvolvedor tem Git lento, configurações locais e saúde da máquina são bons lugares para começar. Se todos têm Git lento, o repositório precisa de atenção. Ajustes pessoais vão esconder a dor por um tempo, mas novos desenvolvedores e jobs de CI continuarão pagando o custo.

Procure por objetos grandes que nunca deveriam ter sido commitados, diretórios gerados que pertencem ao .gitignore e branches antigas que mantêm histórico desnecessário vivo. Antes de reescrever o histórico, converse com a equipe. Reescrever o histórico afeta todos os clones e todas as branches abertas. Pode valer a pena, mas precisa de coordenação.

Para repositórios com ativos grandes legítimos, defina uma política em vez de confiar na memória. Por exemplo: código fonte no Git, exportações de design no Git LFS, artefatos de build no repositório de artefatos, dumps de banco de dados em armazenamento controlado e arquivos locais temporários ignorados. Coloque essas regras em .gitattributes e .gitignore para que o Git possa impor a forma do repositório.

O CI merece sua própria revisão. Muitos pipelines clonam o histórico completo porque era o padrão copiado anos atrás. Se o job só executa testes unitários, pode não precisar de todas as tags e todas as branches. Se o job constrói um release, pode precisar de tags, mas não de todos os blobs em um monorepo. Meça o tempo de clone separadamente do tempo de build para que o custo do repositório seja visível.

Uma auditoria simples de CI pergunta:

Este job precisa do histórico completo?
Precisa de tags?
Precisa de todos os submódulos?
Precisa de todos os diretórios no monorepo?
Ele busca arquivos LFS que nunca lê?

Responder a essas perguntas honestamente geralmente economiza mais tempo do que ajustar opções obscuras do Git.

Finalmente, documente o comando de clone recomendado para o projeto. Se novos desenvolvedores devem usar clone parcial e sparse checkout, diga isso no README. Se eles precisam de Git LFS antes do checkout, diga também. Diretrizes de desempenho que vivem apenas no histórico de shell de um desenvolvedor sênior não ajudam a próxima pessoa.