Como trabalhar com submodulos git
Os submódulos no Git permitem adicionar e gerenciar repositórios Git externos em um repositório Git existente. Isso é útil, por exemplo, se o repositório Git atual depender ou exigir o código-fonte em outro repositório Git.
A presença de um arquivo .gitmodules na raiz do repositório indica que esse repositório usa submódulos. Este arquivo lista os URLs externos do repositório Git, o caminho local dentro do repositório onde eles existirão e seu nome.
Trabalhar com submódulos no Git pode ser um pouco confuso e doloroso, especialmente quando há uma hierarquia de submódulos no módulo principal. No entanto, a organização de submódulos dessa maneira é encontrada em muitos repositórios do Github. Portanto, você precisará saber como trabalhar com essa configuração. Muitas pessoas usam clientes Git, como o SmartGit, que facilitam o trabalho com submódulos. No entanto, mais cedo ou mais tarde, a complexidade que o Git expõe irá diminuir, exigindo que alguém volte ao shell para operar.
Aqui estão as práticas e encantamentos do Git que achei úteis para trabalhar com submódulos:
Clone
Quando você clona um repositório Git que possui submódulos, os submódulos não são clonados.
Para clonar o módulo principal e todos os seus submódulos:
$ git clone --recursive [email protected]:jpe/some_repo.git
Eu prefiro usar o clone normal, seguido de inicializar e atualizar todos os submódulos:
$ git clone [email protected]:jpe/some_repo.git
$ git submodule update --init --recursive
Fetch
Cada um dos submódulos geralmente está vinculado a repositórios remotos (como Github ou NFS). Para buscar mudanças para controles remotos do módulo principal e todos os controles remotos dos submódulos:
$ git fetch --all --recurse-submodules
Update
Depois de verificar uma ramificação (ou revisão) no módulo principal, você também precisa atualizar os submódulos:
$ git checkout foo_local_branch
$ git submodule update --recursive --force
Observe que isso é semelhante ao comando usado no clone acima, exceto que não usamos --init
. O --force
é opcional, mas eu gosto de usar desde que alguns arquivos ou diretórios foram removidos ou adicionados entre checkouts. Se você não usar --force
, a atualização não será concluída e será encerrada com um aviso como este:
⚠️ incapaz de rmdir foobar: Diretório não vazio
Pulling
Depois de configurar os submódulos, você pode atualizar o repositório com fetch/pull como faria normalmente. Para puxar tudo, incluindo os submódulos, use os --recurse-submodules
e o parâmetro --remote
no comando git pull
.
# puxar todas as alterações no repositório, incluindo alterações nos submódulos
$ git pull --recurse-submodules
# puxar todas as alterações para os submódulos
$ git submodule update --remote
Status
Para listar todos os submódulos e suas revisões:
$ git submodule status --recursive
Remotes
Se você estiver alterando qualquer um dos submódulos, lembre-se de bifurcado(fork) esses repositórios (no Github) e adicionar esses controles remotos. Observe que você precisará fazer isso mesmo se tiver bifurcado(fork) o módulo principal e clonado desse repositório bifurcado(forked).
Para diretórios fruit
e fruit/apple
que são submódulos aninhados no módulo principal:
$ cd fruit
$ git remote add joe_fruit_remote [email protected]:joe/fruit_repo.git
$ cd apple
$ git remote add joe_apple_remote [email protected]:joe/apple_repo.git
Commit
Uma revisão ou ramificação do módulo principal será vinculada a revisões específicas de seus submódulos. Ou seja, os submódulos aparecem como revisões HEAD desanexadas (detached HEAD). Além disso, é a revisão de um submódulo que está comprometido com um módulo pai. Tudo isso fica confuso quando você tem uma hierarquia de submódulos.
Portanto, quando você faz check-out de uma ramificação local no módulo principal e precisa modificar arquivos em uma hierarquia de submódulos, o fluxo de trabalho típico é:
- Crie ramificações locais do HEAD desanexado nos submódulos que você alterará.
- Faça o commit de baixo para cima: comece a partir dos submódulos folha que você alterou e vá até o módulo raiz. Todos os módulos no caminho do submódulo alterado para o módulo raiz precisam ser confirmados. Isso ocorre porque o commit do módulo pai precisa da revisão do commit do módulo filho.
Aqui está um fluxo de trabalho de amostra em que os diretórios de fruit
e fruit/apple
são submódulos aninhados no módulo principal:
$ git checkout -b foo_local_branch origin/foo_remote_branch
$ git submodule update --recursive
$ cd fruit
$ git checkout -b fruit_local_branch
$ vim change_some_files_in_fruit_submodule.sh
$ cd apple
$ git checkout -b apple_local_branch
$ vim change_some_files_in_apple_submodule.sh
$ git add change_some_files_in_apple_submodule.sh
$ git commit -m "Changes in fruit/apple go first"
$ cd ..
$ git add change_some_files_in_fruit_submodule.sh
$ git commit -m "Changes in fruit go next"
$ cd ..
$ git add -u
$ git commit -m "Commit new fruit submodule revision to main module"
Push
Cada um dos submódulos geralmente está vinculado a repositórios remotos (como Github ou NFS). É uma boa ideia enviar as ramificações locais que você criou em submódulos (veja acima) para ramificações remotas em seu fork em seu controle remoto. Isso torna mais fácil dar pull request mais tarde (veja a próxima seção abaixo).
Continuando com nosso exemplo acima:
$ cd fruit
$ cd apple
$ git push joe_apple_remote apple_remote_branch
$ cd ..
$ git push joe_fruit_remote fruit_remote_branch
$ cd ..
$ git push origin foo_remote_branch
Pull request
Depois que suas alterações são feitas, você normalmente precisa fazer um pull request (no Github) do seu branch para um branch master que normalmente pertence a outra pessoa. Para fazer isso:
Envie as ramificações(branches) locais de todos os submódulos alterados para seus controles remotos. (Consulte a seção Push
acima).
Forneça várias pull requests
, uma de cada repositório de submódulo que foi alterado.