Articles

Dominar o Git submódulos

Submódulos, passo a passo

vamos agora explorar cada passo do uso de submódulos em um projeto colaborativo, torna-se destacar comportamentos padrão, armadilhas e disponível melhorias.

A fim de facilitar o seu seguinte ao longo, eu juntei alguns exemplos de repos com seus “remotes” (na verdade, apenas diretórios)., Você pode descompactar o arquivo para onde desejar, em seguida, abra um shell (ou Git Bash, se você estiver no Windows) no git-subs directory cria:

faça o Download do exemplo de recompra

Você vai encontrar três pastas em:

  • principais atos como o recipiente de recompra, local para o primeiro colaborador,
  • plugin funciona como central de manutenção repo para o módulo, e
  • controles remotos contém o sistema de arquivos remotos para os dois anteriores acordos de recompra.

nos comandos de exemplo abaixo, a linha de comandos mostra sempre qual o Acordo de recompra em que estamos.,

adicionando um submódulo

vamos começar por adicionar o nosso plugin como um submódulo dentro do nosso recipiente (que está no principal). O plugin em si tem uma estrutura simples:

por isso vamos para o principal e usar o submodule git adicionar comando. Ele leva a URL do remoto e um subdiretório no qual “instanciar” o submódulo.

porque usamos caminhos em vez de URLs aqui para os nossos comandos, atingimos um obstáculo estranho, embora bem conhecido: os caminhos relativos para comandos são interpretados em relação ao nosso principal remoto, não ao directório raiz do nosso repo., Isto é super estranho, não descrito em lado nenhum, mas já vi isso acontecer todas as vezes. Então, em vez de dizer ../ remotes / plugin, apenas dizemos ../Forum.

main (master u=) $ git submodule add ../plugin vendor/plugins/demo
Cloning into 'vendor/plugins/demo'…
done.
main (master + u=) $

isto adicionou algumas configurações na nossa configuração local:

main (master + u=) $ cat .git/config


url = ../remotes/plugin

E isto também encenou dois ficheiros:

Huh?! O que é isto?ficheiro gitmodules? Vejamos:

main (master + u=) $ cat .gitmodules

path = vendor/plugins/demo
url = ../plugin

isto assemelha-se furiosamente à nossa configuração local… então, porquê a duplicação? Bem, precisamente porque a nossa config local é … local., Nossos colaboradores não vão ver (o que é perfeitamente normal), então eles precisam de um mecanismo para obter as definições de todos os submódulos que precisam para montar em seus próprios repos. É isto .gitmodules is for; it will be read later by the git submodule init command, as we’ll see in a moment.

enquanto estamos em estado, observe como é minimalista quando se trata de nosso submódulo: ele só vai com um arquivo novo excessivamente genérico em vez de nos dizer mais sobre o que está acontecendo dentro dele., Nosso submódulo foi realmente injetado no subdiretório:


└── vendor
└── plugins
└── demo
├── .git
├── README.md
├── lib
│ └── index.js
└── plugin-config.json

Status, como logs e diffs, está limitado ao repo ativo (agora, o container), não a submódulos, que são repos aninhados. Isto é muitas vezes problemático (é super fácil perder uma regressão quando limitado a esta vista), por isso recomendo que configure um estado consciente submódulo de uma vez por todas:

git config --global status.submoduleSummary true

E agora:

Aaaah, isto é muito melhor., O estado amplia sua base de informações para acrescentar que o submódulo presente no vendor/plugins/demo tem 3 notícias compromete-se (como acabamos de criar ele, significa que o ramo remoto tinha apenas três compromete-se), a última sendo uma adição (nota a direita apontando parêntese em ângulo >) com uma primeira mensagem de commit linha que lê “Correção de recompra nome…”.

A fim de realmente trazer para casa que lidamos com dois repos separados aqui, vamos entrar no diretório do submódulo:

o repo ativo mudou, porque um novo .,o git assume o comando: na pasta actual (demonstração, a pasta do submódulo), A.o git existe de fato, um único arquivo também, não um diretório. Vamos olhar para dentro:

novamente, uma vez que Git 1.7.8, Git não deixa diretórios de repo dentro do Diretório de trabalho do recipiente, mas centraliza estes no container .pasta git (dentro .git / modules), e usa uma referência gitdir em submódulos.,

a lógica por trás disto é simples: ele permite que o repo container tenha ramos sem submódulos, sem ter que raspar o repo do submódulo do Diretório de trabalho e restaurá-lo mais tarde.

naturalmente, ao adicionar o submódulo, você pode optar por usar um ramo específico, ou mesmo um commit específico, usando a opção-B CLI (como de costume, o padrão é master). Note que não estamos, agora, em uma cabeça separada, ao contrário do que vai acontecer mais tarde: isso é porque Git checou mestre, não um SHA1 específico. Teríamos de especificar um SHA1 A-b para obter uma cabeça separada do get-go.,

Então, de volta para o recipiente de recompra, e vamos finalizar o submódulo do além e levar isso para o controlo remoto:

demo (master u=) $ cd -
main (master + u=) $ git commit -m "Ajout submodule plugin demo"
main (master u+1) $ git push

Agarrando a um acordo de recompra que usa submódulos

a fim de ilustrar as questões com a colaboração em um repositório que usa submódulos, nós vamos dividir personalidades e agir como a nossa colega, que clones do recipiente remoto para começar a trabalhar com a gente. Vamos clonar isso numa lista de colegas, para podermos dizer imediatamente qual é a personalidade que temos a qualquer momento.,

A primeira coisa a notar é que o nosso submódulo está em falta na pasta de trabalho; apenas a sua pasta de base está aqui:

vendor
└── plugins
└── demo

como é que isso aconteceu? Isto é simplesmente devido ao fato de que, até agora, nosso novo repo (colega) ainda não está ciente de nosso submódulo: a informação para ele não está em nenhuma parte em sua configuração local (confira seu .git / config se você não acredita em mim). Vamos precisar de preencher isso, com base no quê .gitmodules tem que dizer, que é precisamente o que o sub-módulo git init faz:

nosso .o git / config está agora ciente do nosso submódulo., No entanto, ainda não o recolhemos do seu remoto, para não dizer que esteja presente no nosso directório de trabalho. E, no entanto, a nossa situação mostra-se limpa!veja, precisamos pegar os commits relevantes manualmente. Não é algo que o nosso clone inicial fez, temos de o fazer em cada puxão. Voltaremos a isso em um minuto, pois este é um clone comportamental que pode automatizar, quando chamado corretamente.,

na prática, ao lidar com repos submódulos usando, normalmente agrupamos os dois comandos (init e update) em um:

colleague (master u=) $ git submodule update --init

ainda é uma pena que o Git tenha que fazer tudo isso sozinho. Imagine, em projetos de fio dental maiores, quando submódulos têm seus próprios submódulos, e assim por diante… isso rapidamente se tornaria um pesadelo.

acontece que o Git oferece uma opção CLI para clonar automaticamente o sub — módulo update-init do git recursivamente logo após a clonagem: a opção bastante apropriadamente chamada-recursiva.,então vamos tentar tudo de novo: agora é melhor! Note que estamos agora em uma cabeça separada dentro do submódulo (como vamos ser a partir de agora):

git-subs $ cd colleague/vendor/plugins/demo
demo ((master)) $

ver o conjunto duplo de parêntesis na minha linha de comandos, em vez de um único conjunto?, Se o seu pedido não é configurado como o meu, para apresentar destacado cabeça, como descreve (com Git incorporado na linha de comandos de script, você teria que definir o GIT_PS1_DESCRIBE_STYLE=ramo variável de ambiente), você irá, ao invés de ver algo assim:

demo ((fe64799...)) $

Em qualquer taxa, o estado confirma onde estamos:

demo ((master)) $ git status
HEAD detached at fe64799
nothing to commit, working directory clean

como Obter uma actualização a partir do submódulo remoto

OK, agora que temos o nosso próprio acordo de recompra (principal) e o nosso “colega” (colega) tudo pronto para colaborar, vamos entrar na pele de uma terceira pessoa: aquele que mantém o plugin., Aqui, vamos passar para ele:

Agora, vamos adicionar dois pseudo-compromete-se e publique estes remoto:

Finalmente, vamos colocar o nosso “primeiro desenvolvedor” cap novamente:

plugin (master u=) $ cd ../main
main (master u=) $

Suponha agora queremos receber estes dois compromete-se dentro de nossos submódulo. Para conseguir isso, precisamos atualizar seu repo local, começando por mover – se em seu diretório de trabalho para que ele se torne nosso repo ativo.

numa nota lateral, eu não recomendaria usar o pull para este tipo de atualização., Para obter corretamente as atualizações no diretório de trabalho, este comando requer que você esteja no ramo ativo adequado, o que você normalmente não é (você está em uma cabeça separada na maioria das vezes). Teria de começar com uma saída daquele ramo. Mas mais importante ainda, o ramo remoto poderia muito bem ter ido mais longe desde o commit que você deseja ativar, e um pull iria injetar commits que você pode não querer em sua base de código local.,

Portanto, eu recomendo dividir o processo manualmente: primeiro git fetch para obter todos os novos dados do remoto em cache local, em seguida, log para verificar o que você tem e checkout no SHA1 desejado. Para além do controlo mais fino, esta abordagem tem o benefício adicional de trabalhar independentemente do seu estado actual (ramo activo ou cabeça separada).OK, então estamos bem, sem compromisso externo., Seja como for, vamos definir explicitamente aquele em que estamos interessados (obviamente você tem um SHA1 diferente):

demo (master u-2) $ git checkout -q 0e90143

(o-q só está lá para nos poupar a tagarelar sobre como estamos a acabar numa cabeça separada. Normalmente isto seria um lembrete saudável, mas neste sabemos o que estamos a fazer.)

Agora que nosso submódulo é atualizado, podemos ver o resultado no status do container repo:

na parte “clássica” do status, vemos um novo tipo de commits mudança, o que significa que o commit referenciado foi alterado., Outra possibilidade (que poderia ser adicionada a esta) é o novo conteúdo, o que significaria que fizemos mudanças locais no diretório de trabalho do submódulo.

a parte inferior, habilitado pelo nosso estado.submoduleSummary = true configuração anterior, afirma explicitamente introduzida compromete-se (como eles usam um clique com o botão direito apontando parêntese em ângulo >) desde o nosso último recipiente confirmar que tinha tocado o submódulo.

na família” terrible default behaviors” , git diff deixa muito a desejar:

O que…?, Existe uma opção CLI que nos permite ver algo mais útil:

main (master * u=) $ git diff --submodule=log
Submodule vendor/plugins/demo fe64799..0e90143:
> Pseudo-commit #2
> Pseudo-commit #1

não existem outras alterações locais agora para além do commit referenciado no submódulo… repare que isto corresponde quase exactamente à parte inferior da nossa visualização de Estado git melhorada.

ter que digitar esse tipo de opção CLI cada vez (que, a propósito, não aparece nas ofertas de conclusão atuais do Git) é bastante difícil de usar. Felizmente, existe uma configuração correspondente:

agora só precisamos realizar o commit do container que finaliza a atualização do nosso submódulo., Se você teve que tocar no código do container para fazê-lo funcionar com esta atualização, enviá-lo, naturalmente. Por outro lado, evite a mistura de mudanças relacionadas com submódulos e outras coisas que apenas dizem respeito ao código do contêiner: separando bem as duas, as migrações posteriores para outras abordagens de reutilização de códigos são facilitadas (também, como de costume, atomic commits FTW).

à medida que estamos prestes a pegar esta atualização submódula no repo do nosso colega, vamos empurrar logo após cometer (o que não é uma boa prática geral).

main (master * u=) $ git commit -am "Setting submodule on PC2"
main (master u+1) $ git push

Puxando um submódulo-usando repo

Clique!, Capitão “colega”!

portanto, estamos a extrair as actualizações do comando do contentor …

(poderá não ter o ” reajustamento e actualização com sucesso… “e ver uma” junção feita pela estratégia ‘recursiva’ ” em vez disso. Se assim for,meu coração vai para você, e você deve aprender imediatamente por que puxa deve ajustar a base).

repare na segunda metade deste ecrã: trata-se do submódulo, a começar por “obter o submódulo…”.

Este comportamento tornou-se o padrão com o Git 1.7.5, com a configuração de obtenção.,recorre agora a modelos que não cumprem a pedido: se um projecto de contentor recebe actualizações aos commits de submódulos referenciados, estes submódulos são obtidos automaticamente. (Lembre-se de buscar é a primeira parte de puxar.)

ainda assim, e isso é crítico: git auto-fetches, mas não auto-update. A sua ‘cache’ local está actualizada com o remoto do submódulo, mas o directório de trabalho do submódulo ficou preso ao seu conteúdo anterior. Pelo menos, podes desligar o portátil, entrar num avião, e seguir em frente uma vez desligado., Embora esta obtenção automática esteja limitada a submódulos já conhecidos: quaisquer novos, ainda não copiados para a configuração local, não são obtidos automaticamente.

git auto-fetches, mas não auto-update.

a linha de comandos actual, com o seu asterisco (*), indica modificações locais, porque o nosso WD não está em sincronia com o índice, estando este último ciente dos novos commits submódulos referenciados. Confira o estado:

Notice how the angle brackets point left (<)?, Git vê que o WD atual não tem estes dois commits, ao contrário das expectativas do projeto container.

Este é o grande perigo: se não actualizar explicitamente a pasta de trabalho do submódulo, a sua próxima persistência do contentor irá regredir o submódulo. Isto é uma Armadilha de primeira ordem.

é, portanto, obrigatório que você finalize a atualização:

desde que estejamos tentando formar bons hábitos genéricos, o comando preferido aqui seria um submodule update — init — recursivo Git, a fim de auto-init qualquer novo submodule, e para atualizar recursivamente estes, se necessário.,

Existe outro caso de aresta: se o URL remoto do submódulo mudou desde a última vez que foi usado (talvez um dos colaboradores o tenha alterado no .gitmodules), terá de actualizar manualmente a sua configuração local para corresponder a esta opção. Em tal situação, antes da atualização do submódulo git, você precisa executar uma sincronização do submódulo git.

Devo mencionar, por uma questão de Completude, que mesmo se a atualização do submódulo git estiver por omissão a verificar o SHA1 referenciado, você pode alterar isso para, por exemplo, reiniciar qualquer trabalho submódulo local (falaremos sobre isso muito em breve) no topo dela., Você faria isso definindo a configuração de atualização para o seu submódulo para se ajustar dentro da configuração local do seu container.

E eu sinto muito, mas não, não há nenhuma configuração local, ou mesmo opção CLI, que pode atualizar automaticamente no pull. Para automatizar tais coisas, você precisa usar tanto os pseudônimos, scripts personalizados, ou ganchos locais cuidadosamente trabalhados., Aqui está um exemplo spull alias (linha única, dividida aqui para visualizar):

git config --global alias.spull '!git pull && git submodule sync --recursive && git submodule update --init --recursive'

Se você deseja manter a capacidade de passar argumentos personalizados para git pull, você pode definir uma função on-the-fly e chamá-lo, ou ir com um script personalizado. A primeira abordagem seria assim (novamente, linha única):

não muito legível, eh? Prefiro a abordagem de script personalizado., Vamos dizer que você colocaria um git-spull arquivo de script em algum lugar dentro do seu CAMINHO (eu tenho um ~/perso/bin no meu CAMINHO apenas para essas coisas):

#! /bin/bash
git pull "$@" &&
git submodule sync --recursive &&
git submodule update --init --recursive

Nós, em seguida, dar-lhe direitos de execução:

chmod +x git-spull

E agora podemos usá-lo como gostaríamos de ter usado o alias.

actualizar um submódulo no contentor

Este é o caso de utilização mais difícil, e deve manter-se afastado dele tanto quanto possível, preferindo a manutenção através do repo Central e dedicado.,

no entanto, pode acontecer que o código submódulo não possa ser testado, ou mesmo compilado, fora do Código do contentor. Muitos temas e plugins têm tais restrições.

A primeira coisa a entender é, porque você vai fazer commits, você deve começar a partir de uma base adequada, que será uma dica de branch. Portanto, você precisa verificar que os mais recentes commits do ramo não “quebrarão” o seu projeto de contêiner. Se o fizerem, bem, criar o seu próprio ramo específico do contentor no submódulo parece tentador, mas esse caminho leva a um forte acoplamento entre o submódulo e o recipiente, o que não é aconselhável., Você pode querer parar de “submodular” esse código neste projeto em particular, e apenas incorporá-lo como qualquer conteúdo regular em vez disso.vamos admitir que você pode, em boa consciência, adicionar ao atual ramo mestre do submódulo. Vamos começar por sincronizar o nosso estado local no remoto:

outra maneira de fazer isso seria, a partir do repo container, sincronizar explicitamente o ramo local do submódulo sobre o seu ramo remoto localizado (linha única no topo, último-seguido pelo espaço em branco):

Podemos agora editar o código, fazê-lo funcionar, testá-lo, etc., Uma vez que estamos prontos, podemos então executar os dois commits e os dois empurrões necessários (é super fácil, e na prática muito frequente, para esquecer um pouco disso).

vamos simplesmente adicionar trabalho falso e fazer os dois commits relacionados, nos níveis submódulo e container:

neste ponto, o maior perigo é esquecer de empurrar o submódulo. Você volta para o projeto container, commit it, e só empurra o container. É um erro fácil de cometer, especialmente dentro de uma IDE ou GUI. Quando os seus colegas tentam obter actualizações, o inferno solta-se., Veja o primeiro passo:

não há absolutamente nenhuma indicação de que o Git não conseguiu obter a persistência referenciada do remoto do submódulo. A primeira dica disto está no estado:

repare no aviso: aparentemente, a persistência referenciada para o submódulo não está em lado nenhum. De fato, se tentarmos atualizar o diretório de trabalho do submódulo, teremos:

main (master * u=) $ git submodule update
fatal: reference is not a tree: 12e3a529698c519b2fab790630f71bd531c45727
Unable to checkout '12e3a529698c519b2fab790630f71bd531c45727' in submodule path 'vendor/plugins/demo'

você pode ver claramente como é importante lembrar de empurrar o submódulo também, idealmente antes de empurrar o contêiner., Vamos fazer isso em colega e tentar a atualização novamente:

Devo notar que existe uma opção CLI que irá verificar se os commits de submódulo referenciados de momento precisam ser empurrados também, e se assim for irá empurrá — los: é git push-recurse-submódulos=on-demand (bastante mouthful, reconhecidamente). Ele precisa ter algo container-level para empurrar para trabalhar, no entanto: apenas submódulos não vai cortá-lo.

além disso, (não há configuração para isso, então você teria que padronizar procedimentos em torno de um pseudônimo, por exemplo spush:) — começando com o Git 2.7.0, agora há um push.,recorre a configurações de módulos que poderá definir (a pedido ou a pedido).

git config --global alias.spush 'push --recurse-submodules=on-demand'

a Remoção de um submódulo

Há duas situações em que você gostaria de “remover” um submódulo:

  • Você só quer limpar o diretório de trabalho (talvez antes do arquivamento do recipiente WD), mas queremos manter a possibilidade de restaurá-lo mais tarde (por isso tem que permanecer no .git modules and .git / modules);
  • deseja removê-lo definitivamente do ramo actual.

vamos ver cada caso por sua vez.,

remover temporariamente um submódulo

a primeira situação é facilmente manuseada pelo desinit do submódulo git. Veja por si mesmo:

isto não tem qualquer impacto no estado do recipiente. O submódulo já não é conhecido localmente (já não é conhecido .git / config), por isso a sua ausência no directório de trabalho passa despercebida. Ainda temos o directório de fornecedores/plugins / demo, mas está vazio; podemos removê-lo sem consequências.

o submódulo não deve ter quaisquer modificações locais quando você faz isso, caso contrário você precisa forçar a chamada.,

qualquer subcomandante posterior do submódulo git irá ignorar este submódulo alegremente até que você o inicie novamente, já que o submódulo nem sequer estará na configuração local. Tais comandos incluem atualização, foreach e sincronização.por outro lado, o submódulo permanece definido em .gitmodules: um inicial, seguido por uma atualização (ou uma única atualização — init) irá restaurar como novo:

remover Permanentemente um submódulo

Isto significa que você deseja livrar-se do submódulo de bom: regular git rm vai fazer, assim como para qualquer outra parte do diretório de trabalho., Isto só funcionará se o seu submódulo usar um gitfile (A.git que é um arquivo, não um diretório), que é o caso começando com Git 1.7.8. Caso contrário, você terá que lidar com isso à mão (eu vou dizer-lhe como no final).

para além de remover o submódulo da pasta de trabalho, o comando irá actualizar o .o ficheiro gitmodules não faz mais referência ao submódulo. Aqui você vai:

naturalmente, advanced status info trip sobre eles mesmos, porque o gitfile para o submódulo desapareceu (na verdade, toda a Diretoria de demonstração desapareceu).,

O que é estranho, porém, é que a configuração local retém informações submódulas, ao contrário do que acontece quando você desinita. Então, para uma remoção abrangente, eu recomendo que você faça ambos, em seqüência, de modo a acabar devidamente limpo (ele não funcionaria após o nosso comando anterior, porque ele limpou .gitmodules already):

git submodule deinit path/to/module # ensure local config cleanup
git rm path/to/module # clean WD and .gitmodules

independentemente da sua abordagem, o repo do submódulo permanece presente .git / modules/vendor/plugins / demo, mas você está livre para matar isso quando quiser.

Se alguma vez precisar de remover um submódulo que foi configurado antes do Git 1.7.,8, e, portanto, incorpora o seu .directório git diretamente no diretório de trabalho do container (em vez de depender de um gitfile), você terá que quebrar o bulldozer: os dois comandos anteriores precisam ser precedidos por uma remoção manual de pasta, por exemplo, RM-fr fornecedor/plugins/demo, porque esses comandos sempre se recusarão a excluir um repositório real.