Monorepos com Git: estratégias e ferramentas

1. Introdução aos Monorepos no Contexto do Git

Um monorepo é um repositório Git único que armazena múltiplos projetos relacionados, enquanto um multirepo mantém cada projeto em seu próprio repositório. No ecossistema Git, monorepos oferecem vantagens significativas: visibilidade completa do código, refatorações atômicas entre projetos e compartilhamento simplificado de dependências. Os desafios incluem aumento no tempo de clone, histórico de commits misturado e necessidade de ferramentas especializadas. Um monorepo é ideal quando os projetos compartilham bibliotecas, padrões de código ou precisam de coordenação frequente — como em microsserviços fortemente acoplados ou SDKs multiplataforma.

2. Estruturação de Diretórios e Nomes de Branches

A organização do diretório é crucial para a manutenção de um monorepo. Um padrão comum:

monorepo/
├── apps/
│   ├── web-app/
│   └── mobile-app/
├── libs/
│   ├── shared-utils/
│   └── ui-components/
├── packages/
│   ├── sdk-core/
│   └── cli-tool/
├── .gitignore
└── README.md

Convenções de branches ajudam a identificar o escopo das alterações:

feature/web-app/user-auth
fix/mobile-app/crash-on-load
release/v2.1.0
hotfix/shared-utils/security-patch

Para .gitignore modulares, coloque arquivos específicos em cada subdiretório:

# apps/web-app/.gitignore
node_modules/
dist/
.env

# libs/shared-utils/.gitignore
*.log
__pycache__/

3. Estratégias de Merge e Histórico de Commits

Em monorepos, a escolha da estratégia de merge impacta diretamente a clareza do histórico. O squash merge é preferível para branches de feature, pois condensa múltiplos commits em um único commit atômico:

git checkout main
git merge --squash feature/web-app/login
git commit -m "feat(web-app): implementa fluxo de login com OAuth2"

O rebase interativo mantém um histórico linear, útil para revisão de código:

git checkout feature/mobile-app/push-notifications
git rebase -i main

Políticas de commit atômico exigem que cada commit represente uma mudança completa e funcional. Mensagens descritivas seguem o formato tipo(escopo): descrição. Para conflitos entre projetos, use git log com filtros de caminho:

git log --oneline -- apps/web-app/ libs/shared-utils/

4. Submodules e Subtrees: Alternativas Nativas do Git

Os submodules permitem incluir outros repositórios Git dentro do monorepo:

git submodule add https://github.com/exemplo/external-lib.git libs/external-lib
git submodule update --init --recursive

Vantagens: versionamento independente e isolamento de dependências externas. Desvantagens: comandos adicionais para sincronização e riscos de referências órfãs.

O subtree incorpora o histórico de outro repositório diretamente:

git subtree add --prefix=packages/vendor-lib https://github.com/exemplo/vendor-lib.git main --squash
git subtree pull --prefix=packages/vendor-lib https://github.com/exemplo/vendor-lib.git main

Vantagens: histórico unificado e sem necessidade de comandos extras para contribuidores. Desvantagens: aumento do tamanho do repositório e conflitos em merges complexos. Para monorepos internos, subtrees são geralmente mais práticas que submodules.

5. Ferramentas Complementares para Monorepos

O git worktree permite trabalhar em múltiplos branches simultaneamente sem trocar de diretório:

git worktree add ../monorepo-hotfix hotfix/shared-utils/security
git worktree list
git worktree remove ../monorepo-hotfix

Ferramentas como Lerna, Nx e Turborepo gerenciam dependências entre pacotes e otimizam builds:

# Exemplo com Nx
nx affected:test --base=main
nx build web-app

Hooks personalizados validam alterações em escopos específicos:

# .git/hooks/pre-commit (exemplo parcial)
#!/bin/sh
CHANGED=$(git diff --cached --name-only)
if echo "$CHANGED" | grep -q "^apps/web-app/"; then
    cd apps/web-app && npm run lint
fi

6. Otimização de Performance em Monorepos Grandes

Shallow clone limita o histórico baixado:

git clone --depth 1 https://github.com/empresa/monorepo.git

Sparse checkout restringe os diretórios visíveis no working tree:

git sparse-checkout init --cone
git sparse-checkout set apps/web-app libs/shared-utils

Para reduzir o tamanho do repositório, execute periodicamente:

git gc --aggressive --prune=now
git repack -a -d --depth=250 --window=250

O git maintenance automatiza a otimização:

git maintenance start
git maintenance run --task=gc

7. CI/CD e Automação em Monorepos

Triggers seletivos por caminho evitam builds desnecessários:

# GitHub Actions (exemplo)
on:
  push:
    paths:
      - 'apps/web-app/**'
      - 'libs/shared-utils/**'

Build incremental baseado em mudanças detecta dependências afetadas:

# Comando hipotético para ferramenta de monorepo
affected-projects --base=main --target=build

Versionamento semântico usa tags no formato pacote@versão:

git tag web-app/v2.1.0
git tag libs/shared-utils/v1.3.2
git push --tags

8. Boas Práticas e Anti-Padrões

Evite commits gigantes — prefira alterações pequenas e focadas. Dependências circulares entre pacotes devem ser eliminadas com refatoração. Para revisão de código, configure CODEOWNERS por diretório:

# .github/CODEOWNERS
/apps/web-app/ @time-web
/libs/shared-utils/ @time-core

Documente a estrutura e crie scripts de inicialização:

#!/bin/bash
# setup.sh
git clone --depth 1 https://github.com/empresa/monorepo.git
cd monorepo
git sparse-checkout set apps/web-app libs/shared-utils
npm install

Anti-padrões comuns incluem misturar lógicas não relacionadas no mesmo commit, ignorar a limpeza de branches obsoletos e não estabelecer limites claros entre projetos.

Referências