Git submodules: o que são, como usar e quando vale a pena
O que são os submódulos do Git, por que existem, exemplos de uso com comandos, prós e contras, projetos reais no GitHub e um quiz para decidir se vale a pena.
O GIT é um mundo e a gente usa as funções mais básicas dele no dia a dia (e tá tudo bem!). Mas de vez em quando eu gosto de revisar algo e descobrir o que posso estar melhorando no meu operacional.
Já escrevi aqui no blog sobre outras partes do Git:
- Em abril de 2020, o git bare, para automatizar o deploy das minhas aplicações.
- No mês seguinte, os estágios do Git. Lá eu admiti que, por muito tempo, utilizava certos comandos sem entender o que realmente estava acontecendo.
- Em setembro de 2023, o
git stash.
Esses dias fui procurar na minha máquina quais repositórios usam submódulos. Achei um arquivo .gitmodules em um projeto pessoal e em alguns dos projetos da empresa onde eu trabalho.
Ver o arquivo é fácil. Explicar de cabeça por que ele está ali, nem tanto. Era a curiosidade batendo outra vez.
Por isso fui ler a documentação e rodei os comandos principais abaixo em um repositório de teste, com o Git 2.43, antes de escrever.
Neste artigo eu cubro seis coisas:
- O que são os submódulos.
- Por que o Git ganhou esse recurso.
- Exemplos de uso, com os comandos.
- Um quiz para você decidir se vale a pena no seu caso.
- Os prós e os contras.
- Projetos no GitHub que usam submódulos.
Bora lá.
O que são submódulos
O Pro Git, o livro oficial, define assim: submódulos permitem manter um repositório Git como subdiretório de outro, com os commits separados.
Na prática, o repositório de fora (o superprojeto) não guarda os arquivos do de dentro. Ele guarda duas coisas: o endereço, no arquivo .gitmodules, e um commit exato do repositório de dentro.
git submodule add https://github.com/exemplo/lib vendor/libgit status --shortA .gitmodulesA vendor/libO .gitmodules é versionado junto com o resto, então todo mundo que clonar sabe de onde buscar:
[submodule "vendor/lib"] path = vendor/lib url = https://github.com/exemplo/libE o commit fica registrado como um item especial, de modo 160000:
git ls-files -s vendor/lib160000 7ee8d1e49243cb53e518db1bbf8c3af03c082513 0 vendor/libO Git não guarda os arquivos. Guarda o endereço e o commit.
Repositório dentro de repositório não é, por si só, submódulo. O meu blog, por exemplo, tem uma pasta iagofrota-blog/ que é um repositório próprio, mas o .gitignore do repositório de fora a ignora. Ele nem sabe que ela existe. Submódulo é quando o Git de fora registra a relação.
Por que existem
O Pro Git abre o assunto com um problema comum. Você está construindo um site e quer usar uma biblioteca que já existe. Se pegar pelo gerenciador de pacotes, fica difícil customizar. Se copiar o código para dentro do projeto, suas alterações viram uma dor de cabeça quando a biblioteca lançar versão nova.
O submódulo é a resposta do Git para isso: você clona o outro repositório dentro do seu e mantém os commits de cada um separados.
Quanto à história, o Git ganhou o comando git submodule na versão 1.5.3, lançada em setembro de 2007. As notas dessa versão dizem que o suporte era mínimo, e que isso era de propósito. Uma das decisões de desenho foi esta: as operações no superprojeto não entram nos submódulos por padrão.
Isso explica o comportamento que mais confunde quem chega: você clona o projeto e a pasta do submódulo vem vazia. Não é bug. É decisão de 2007.
Na prática
Clonar um projeto com submódulos
Sem nenhuma flag, o clone cria a pasta e deixa ela vazia. No meu teste, ls na pasta devolveu zero arquivos, e o git submodule status mostrou o hífen que marca um submódulo não inicializado:
git clone https://github.com/exemplo/appcd appgit submodule status-7ee8d1e49243cb53e518db1bbf8c3af03c082513 vendor/libDois caminhos resolvem. O primeiro é pedir tudo já no clone:
git clone --recurse-submodules https://github.com/exemplo/appO segundo é corrigir um clone que já existe:
git submodule update --init --recursiveAtualizar para a versão nova
Para trazer o commit mais novo da branch remota do submódulo (por padrão, a HEAD do remoto, ou a branch definida em branch no .gitmodules):
git submodule update --remotegit status --shortgit diff --submodule M vendor/libSubmodule vendor/lib 7ee8d1e..739f438: > lib: v2Repare que o status marca a pasta como modificada, mas não há diff de arquivo. O que mudou foi o ponteiro. Falta commitar o novo commit de referência no superprojeto, e só então o resto do time recebe a atualização.
Outro detalhe: um git pull no superprojeto traz o ponteiro novo, mas não atualiza os arquivos do submódulo. Para isso é preciso rodar git submodule update de novo. Quem se cansa disso pode ligar a configuração submodule.recurse:
git config --global submodule.recurse trueMexer dentro do submódulo
Depois do update, o submódulo fica em detached HEAD, sem branch. É o comportamento padrão do checkout; com --merge ou --rebase ele é outro. No meu teste:
HEAD detached at 739f438Se você commitar assim, o commit fica sem nenhuma branch apontando para ele, e depois é difícil de achar. Entre na pasta e crie uma branch a partir do commit atual:
cd vendor/libgit switch -c minha-alteracao# ...edite, commite...cd ../..Prefira criar a branch a dar um git checkout main. A main local pode estar num commit mais antigo, e o checkout devolveria o submódulo para ele, desfazendo a atualização que você acabou de fazer.
Publicar na ordem certa
Aqui mora o erro mais caro. Se você commitar no submódulo, atualizar o ponteiro no superprojeto e publicar só o superprojeto, o ponteiro aponta para um commit que só existe na sua máquina. Quem clonar depois vai tropeçar.
O Git avisa, se você pedir. Com a opção check, o push falha, e a mensagem do meu teste foi esta:
git push --recurse-submodules=check origin mainThe following submodule paths contain changes that cannot be found on any remote: vendor/lib
Please try
git push --recurse-submodules=on-demandO check só confirma que o commit existe em algum remoto do submódulo. Não garante que seja o remoto dos seus colegas, nem que eles tenham acesso a ele.
Com on-demand, o Git publica o submódulo primeiro e o superprojeto depois. Se você quer isso sempre, configure uma vez:
git config push.recurseSubmodules on-demandRemover um submódulo
Remover dá mais trabalho do que adicionar. O caminho seguro, no meu teste, foram dois comandos:
git rm vendor/libgit commit -m "remove o submódulo vendor/lib"O git rm tira a pasta e a entrada do .gitmodules. Se o submódulo tiver alteração local, ele recusa e avisa. Resolva o trabalho antes, em vez de forçar com -f: o -f descarta o que não foi commitado.
Sobra uma cópia do repositório do submódulo em .git/modules/vendor/lib. O Git deixa isso de propósito, para você conseguir voltar a versões antigas do projeto. Só apague com rm -rf se tiver certeza de que não há commit seu guardado apenas ali, porque esse rm -rf não tem volta.
No CI
Pipeline também clona. O actions/checkout, do GitHub Actions, aceita a opção submodules, com true ou recursive. Sem ela, o job roda com a pasta vazia e a falha aparece longe da causa.
Quando usar
Antes do quiz, a regra em quatro linhas:
- Se o código já está publicado em um gerenciador de pacotes (npm, Maven Central, PyPI, Packagist) e a versão publicada atende, use o gerenciador. Ele já fixa a versão para você.
- Se nada exige os repositórios separados (outro time, outro ciclo de release, outro controle de acesso), considere juntar em um só.
- Se é código de terceiro que você dificilmente vai devolver, e o clone precisa ser simples, olhe o
git subtree. - Se você precisa de um commit exato de outro repositório, com histórico separado, submódulo.
A Opensource.com separa os dois assim: submódulo para um repositório externo que é seu e para o qual você vai empurrar mudanças, subtree para código de terceiro que você provavelmente não vai devolver.
A regra tem uma zona cinzenta, e vale ser honesto: o PyTorch, que você vai ver mais abaixo, fixa dezenas de projetos de terceiros como submódulos. O .gitmodules mostra a fixação, e não o motivo. Minha hipótese é o commit exato, mas é só hipótese.
Teste com o seu caso
Montei as mesmas perguntas em um quiz de sim ou não. Responda na ordem. O veredito aparece assim que houver resposta suficiente para decidir.
Prós e contras
Os prós:
- Commit exato. O superprojeto aponta para uma versão específica do código, o que ajuda a reproduzir o build.
- Repositório pequeno. Ele guarda só o ponteiro, e não o código nem o histórico do outro projeto.
- Histórico separado. Cada repositório mantém os próprios commits, releases e permissões.
- Devolver mudanças é simples. Você entra na pasta, commita e publica no repositório de origem. Num subtree isso dá mais trabalho.
Os contras:
- O clone ganha um passo. Sem
--recurse-submodules, a pasta vem vazia. - Detached HEAD. Quem não sabe disso deixa commits sem branch e depois não consegue achá-los.
- É fácil esquecer de publicar o submódulo. O ponteiro vai para o servidor e o commit, não.
- Depende de um servidor acessível. O repositório de origem precisa estar no ar, e se o commit referenciado sumir de lá, o checkout quebra.
- Conflito de ponteiro. Quando duas pessoas movem o mesmo submódulo para commits que divergem, em branches diferentes, o merge pede que você resolva na mão.
- Curva de aprendizado. O artigo da Opensource.com diz que iniciantes costumam achar submódulos difíceis.
- Remover dá trabalho, e o
-fpode custar trabalho não commitado, como vimos acima.
Projetos no GitHub
Conferi o .gitmodules de cada um em 9 de outubro de 2026. Os números mudam com o tempo.
- PyTorch. Tinha 36 entradas, quase todas em
third_party/: googletest, pybind11, benchmark e outras. - gRPC. Tinha 20, também em
third_party/: abseil-cpp, protobuf, re2, zlib e googletest, entre outras. - Prezto. Um framework de configuração para Zsh. Tinha 10 entradas, em
modules/*/external: temas e plugins de terceiros como powerlevel10k, pure e agnoster. - Hugo. O quick start manda adicionar o tema Ananke como submódulo, em
themes/ananke. O repositório do Hugo em si não usa. - Um projeto na minha máquina. O
meeting-minutesapontabackend/whisper.cpppara um fork do whisper.cpp, na branchdevelop.
Na minha leitura, o padrão se repete: bibliotecas C++, onde não há um gerenciador de pacotes dominante, e temas ou plugins de terceiros que você quer atualizar no seu ritmo.
Conclusão
Por fim, submódulo é uma ferramenta de commit exato, e não de conveniência. Quem escolhe por achar que é “o jeito do Git de importar biblioteca” costuma sentir o atrito no primeiro clone.
No fim do dia, a pergunta é quanto atrito o seu time aguenta em troca de ter um commit fixado e um histórico separado. Se a resposta for “pouco”, o gerenciador de pacotes ou o subtree provavelmente servem melhor.
Escrever sobre git bare, estágios, stash e agora submódulos me lembrou de uma coisa: o Git que eu uso todo dia é uma parte pequena do Git que existe. (Sei que ainda tem muita função que eu não conheço.) É por isso que eu continuo curioso.
E você, qual foi a última função do Git que fez você abrir a documentação só por curiosidade?
Referências
Continue lendo
Como criar skills para agentes de IA, do zero
O que é uma skill, como crio as minhas, quando ela vira script e a escada do prompt à orquestração, com o que as fontes corrigem.
Gotcha Lembrado é Gotcha Esquecido
A regra de virar script todo passo determinístico virou hábito. Quatro sinais de alerta, dois exemplos reais e quanto isso pesa em token, de verdade.
Determinismo vira script: o teste que uso para saber quando parar de pedir para a IA
Se duas execuções corretas dão a mesma saída, é script, não trabalho para a IA. O teste que uso, três exemplos práticos e o que isso economiza em token.