No artigo anterior, escrevi sobre uma extensão chamada git bigjobbies; os propósitos dela eram armazenar binários grandes em um repositório sem que fossem parte do branch principal (e, portanto, não aparecendo no histórico).
A razão para alguém querer fazer isso é evitar um checkout do servidor levando um período significativo de tempo, ou ocupar um espaço adicional, uma vez que o clone foi feito. Embora o Git forneça uma boa codificação delta de arquivos existentes, estes tendem a só funcionar se os dados binários forem relativamente semelhantes. Normalmente, grandes recursos binários (como arquivos de áudio, filmes ou mesmo imagens se tiverem sido salvas em um formato compactado) compartilham partes pequenas dos dados binários nos bastidores.
O Git também possui uma variável de configuração, core.bigFileThreshold, que pode ser usada para definir o limite em que os arquivos são armazenados, já que estão sem executar qualquer comparação delta. Os arquivos acima de 512 Mb (por padrão) são armazenados sem compressão delta para versões anteriores (embora eles sejam deflacionados em tempo de armazenamento).
A solução óbvia para esse problema é armazenar o código-fonte (e outros bens comprimíveis) em um repositório Git, e depois armazenar grandes recursos de mídia (efeitos de som, vídeos etc.) em outro repositório Git. O histórico de um, portanto, não afeta o do outro.
Submódulos
Se você está armazenando estes como repositórios git separados, como você pode garantir que sejam mantidos em sincronia uns com os outros? Bem, você pode usar tags e confiar na convenção para garantir que você possa adquirir a mesma versão dos recursos. No entanto, tags podem mudar (embora não devessem), e as convenções podem ser contornadas.
Outra maneira de fazer isso é armazenar um ponteiro para os recursos. (Isso é semelhante ao arquivo .bigjobbies sugerido antes.) Uma vez que eles são referenciados por hash, desde que você possa adquirir o hash, então você será capaz de restaurar o recurso.
Os submódulos Git trabalham com esses dois conceitos juntos, tratando um submódulo como um diretório logicamente verificado em outro repositório, mas referindo-se a ele por um ponteiro em vez de uma checagem completa. O submódulo (sub-repositório) pode evoluir no seu próprio ritmo, com seus próprios checkouts, e o pai pode se referir a ele por um hash fixo.
Trabalhando com submódulos
Para adicionar um submódulo a um projeto existente, execute git submodule add para definir um diretório local correspondente ao conteúdo do projeto Git remoto. Por exemplo, se você quiser adicionar o projeto BigJobbies anteriormente como um sub-módulo, você pode fazer:
$ git init parent Initialized empty Git repository in parent/.git/ $ cd parent (master) $ git submodule add http://github.com/alblue/BigJobbies/ Cloning into BigJobbies... done. (master) $ ls -AF .git/ .gitmodules BigJobbies/ (master) $ cat .gitmodules [submodule "BigJobbies"] path = BigJobbies url = http://github.com/alblue/BigJobbies/ (master) $ git status # On branch master # # Initial commit # # Changes to be committed: # (use "git rm --cached ..." to unstage) # # new file: .gitmodules # new file: BigJobbies
Observe que ele configurou um arquivo .gitmodules e criou um diretório BigJobbies, correspondente aos dados BigJobbies clonados. No entanto, no git status, ele aparece como um arquivo. O que está havendo com ele?
Se somarmos os conteúdos, fizermos um commit, e depois olharmos para a tree, nós vamos chegar à nossa resposta:
(master) $ git commit -m "Added BigJobbies submodule" [master (root-commit) f34f140] Added BigJobbies submodule 2 files changed, 4 insertions(+), 0 deletions(-) create mode 100644 .gitmodules create mode 160000 BigJobbies (master) $ git ls-tree HEAD 100644 blob 8041b87daf8e7ed034c669c6c5af9d63367dcd78 .gitmodules 160000 commit e9ed329101157ce9be5dc1c2639096bd82d3fa05 BigJobbies (master) $ (cd BigJobbies; git rev-parse HEAD) e9ed329101157ce9be5dc1c2639096bd82d3fa05
Em vez de um simples modo 100644, que é usado para armazenar um arquivo com permissões rw-r — r–, 160000 é usado no lugar. Entretanto, isso aponta para um commit, ao contrário da tree ou do blob, que já vimos antes. Os commit aponta para a versão atual de HEAD no submódulo de saída, como pode ser visto aqui e9ed329101157ce9be5dc1c2639096bd82d3fa05.
O repositório pai é agora bastante reduzido e contém o arquivo .gitmodules, e nada mais. No entanto, tem também versões em lock-step com o repositório BigJobbies. Qualquer um que quer clonar esse repositório vai descobrir que pode encontrar o repositório, embora com um passo separado:
(master) $ cd .. $ git clone parent clone Cloning into clone... done. $ cd clone (master) $ ls BigJobbies/ (master) $ git submodule sync Synchronizing submodule url for 'BigJobbies' (master) $ ls BigJobbies/ (master) $ git submodule update Cloning into BigJobbies... done. Submodule path 'BigJobbies': checked out 'e9ed329101157ce9be5dc1c2639096bd82d3fa05' (master) $ ls BigJobbies/ LICENSE.txt Movies README.md git-bigjobbies
Em outras palavras, podemos clonar o pai sem adquirir qualquer um de seus filhos. No entanto, para preencher os submódulos child, é preciso executar um comando git submodule update, que traz o novo código. (Você também precisa executar update quando o repositório remoto mudar o conteúdo que você também deseja adquirir.).
Relações pai-filho
Às vezes, você quer ser capaz de acoplar dois repositórios juntos, como um projeto de desenvolvimento de jogos com seus recursos de mídia, ou um conjunto de versões binárias com um projeto de origem. É tentador pensar que essas relações com os binários sendo parte do projeto de origem (ou um submódulo), ou os recursos de mídia como parte da fonte de jogo (ou um submódulo).
No entanto, muitas vezes é melhor reverter os laços de dependência entre esses tipos de dependências do repositório. Em outras palavras, em vez de um ter um repositório de origem com um submódulo filho dos recursos binários, é melhor ter um com um submódulo da fonte.
Lançar o relacionamento dessa forma permite tratar o repositório de origem como uma unidade standalone, que não precisa fazer referências aos grandes binários, mas permite uma checagem completa do repositório pai (que tem os binários).
Para projetos em que a fonte não possui qualquer necessidade para os binários (como nos pacotes pré-compilados para projetos open-source), essa distinção pode salvar referências a repositórios binários upstream que possam ficar acidentalmente check-out (especialmente se forem utilizados outros submódulos).
É possível também colocar a fonte e os binários em dois repositórios completamente independentes, em seguida, ligá-los com um repositório Git de nível maior Git (com dois submódulos). O pai pode, então, ser usado como um repositório de release de nível superior, enquanto ainda permite que os binários e o código-fonte sejam adquiridos de forma independente.
Finalmente, uma vantagem de ter o repositório binário (maior) sendo o pai é que ele ainda vai funcionar se você cloná-lo com git clone –depth 1. Quando você usa a flag –depth 1, você está basicamente dizendo que não quer nenhum histórico, apenas o último commit nesse branch. O último commit vai ter um ponteiro para o branch do código fonte (que terá o histórico completo), e isso permite que você veja uma versão (mais recente) única do binário com acesso ao histórico completo do código fonte.
***
Texto original disponível em http://alblue.bandlem.com/2011/11/git-tip-of-week-git-submodules.html