Atualize o submódulo Git para o commit mais recente na origem

853

Eu tenho um projeto com um submódulo Git. É de uma URL ssh: // ... e está no commit A. O commit B foi enviado para esse URL, e eu quero que o submódulo recupere o commit e mude para ele.

Agora, meu entendimento é que isso git submodule updatedeve ser feito, mas isso não acontece. Não faz nada (sem saída, código de saída com êxito). Aqui está um exemplo:

$ mkdir foo
$ cd foo
$ git init .
Initialized empty Git repository in /.../foo/.git/
$ git submodule add ssh://user@host/git/mod mod
Cloning into mod...
user@host's password: hunter2
remote: Counting objects: 131, done.
remote: Compressing objects: 100% (115/115), done.
remote: Total 131 (delta 54), reused 0 (delta 0)
Receiving objects: 100% (131/131), 16.16 KiB, done.
Resolving deltas: 100% (54/54), done.
$ git commit -m "Hello world."
[master (root-commit) 565b235] Hello world.
 2 files changed, 4 insertions(+), 0 deletions(-)
 create mode 100644 .gitmodules
 create mode 160000 mod
# At this point, ssh://user@host/git/mod changes; submodule needs to change too.
$ git submodule init
Submodule 'mod' (ssh://user@host/git/mod) registered for path 'mod'
$ git submodule update
$ git submodule sync
Synchronizing submodule url for 'mod'
$ git submodule update
$ man git-submodule 
$ git submodule update --rebase
$ git submodule update
$ echo $?
0
$ git status
# On branch master
nothing to commit (working directory clean)
$ git submodule update mod
$ ...

Eu também tentei git fetch mod, que aparece para fazer uma busca (mas não pode, possivelmente, porque não é solicitar uma senha!), Mas git loge git shownegar a existência de novos commits. Até agora, acabei de rmusar o módulo e adicioná-lo novamente, mas isso é errado em princípio e tedioso na prática.

Thanatos
fonte
5
A resposta de David Z parece ser a melhor maneira de fazer isso - agora que o Git tem a funcionalidade que você precisa incorporada por meio da --remoteopção, talvez seja útil marcar isso como a resposta aceita e não a abordagem "manual" na resposta de Jason?
Mark Amery
1
Estou de acordo com @MarkAmery. Enquanto Jason deu uma solução funcional, não é a maneira pretendida de fazê-lo, pois deixa o ponteiro de confirmação do submódulo no identificador de confirmação errado. O novo --remoteé definitivamente uma solução melhor neste momento e, como essa questão foi vinculada a partir de um Github Gist sobre submódulos, acho que seria melhor para os novos leitores ver a nova resposta.
MutantOctopus
Toque agradável com a hunter2senha: o)
lfarroco

Respostas:

1458

Na git submodule updateverdade, o comando diz ao Git que você deseja que seus submódulos façam check-out do commit já especificado no índice do superprojeto. Se você deseja atualizar seus submódulos para a confirmação mais recente disponível a partir do controle remoto, será necessário fazer isso diretamente nos submódulos.

Então, em resumo:

# Get the submodule initially
git submodule add ssh://bla submodule_dir
git submodule init

# Time passes, submodule upstream is updated
# and you now want to update

# Change to the submodule directory
cd submodule_dir

# Checkout desired branch
git checkout master

# Update
git pull

# Get back to your project root
cd ..

# Now the submodules are in the state you want, so
git commit -am "Pulled down update to submodule_dir"

Ou, se você é uma pessoa ocupada:

git submodule foreach git pull origin master
Jason
fonte
335
git submodule foreach git pull
Mathias Bynens
87
@Nicklas Nesse caso, use git submodule foreach git pull origin master.
Mathias Bynens
54
Neste ponto, com todas essas correções, preciso que alguém escreva uma postagem explicativa no blog e me aponte para lá. Por favor.
Suz
25
Uma pequena melhoria na abordagem 'foreach' - você pode adicionar --recursive lá caso tenha submodules dentro dos submódulos. assim: git submodule foreach --recursive git pull origin master.
Orion elenzil
4
@Abdull A -aopção git commit"Diga ao comando para preparar automaticamente os arquivos que foram modificados e excluídos, mas os novos arquivos sobre os quais você não contou ao Git não são afetados".
godfrzero
473

O Git 1.8.2 apresenta uma nova opção,, --remoteque permitirá exatamente esse comportamento. Corrida

git submodule update --remote --merge

buscará as alterações mais recentes do upstream em cada submódulo, mesclará e verificará a revisão mais recente do submódulo. Como a documentação coloca:

--controlo remoto

Esta opção é válida apenas para o comando update. Em vez de usar o SHA-1 registrado do superprojeto para atualizar o submódulo, use o status da ramificação de rastreamento remoto do submódulo.

Isso é equivalente à execução git pullem cada submódulo, que geralmente é exatamente o que você deseja.

David Z
fonte
4
"equivalente à execução git pullem cada submódulo" Para esclarecer, não há diferença (da perspectiva do usuário) entre sua resposta e git submodule foreach git pull?
Dennis
3
@ Dennis faz essencialmente a mesma coisa, mas não tenho certeza se a funcionalidade é exatamente a mesma. Pode haver algumas pequenas diferenças que eu não conheça, por exemplo, na maneira como os dois comandos respondem a alguma configuração.
David Z
5
Eu gostaria de poder votar este 10.000X. Por que isso não aparece na documentação do git? Enorme supervisão.
serraosays 18/09/2015
4
Para mim, eles realmente diferiam bastante; foreach git pullapenas fez check-out deles, mas não atualizou o ponteiro do repositório principal para apontar para o commit mais recente do submódulo. Somente com --remoteele fez apontar para o último commit.
precisa saber é o seguinte
5
por que a opção --merge? Que diferença isso faz?
MFeinstein 17/03/19
127

No diretório pai do projeto, execute:

git submodule update --init

Ou se você tiver submódulos recursivos, execute:

git submodule update --init --recursive

Às vezes, isso ainda não funciona, porque, de alguma forma, você tem alterações locais no diretório do submódulo local enquanto o submódulo está sendo atualizado.

Na maioria das vezes, a alteração local pode não ser a que você deseja confirmar. Isso pode ocorrer devido a uma exclusão de arquivo no seu submódulo etc. Se sim, faça uma redefinição no diretório do submódulo local e no diretório pai do projeto, execute novamente:

git submodule update --init --recursive
pinux
fonte
5
Esta é a verdadeira resposta. posso empurrá-lo para o meu repositório remoto de alguma forma?
MonsterMMORPG
Isso funciona para novos submódulos! Eu poderia atualizar todos os outros, mas a pasta de novos submódulos permaneceria vazia até eu executar este comando.
Alexis Wilke #
1
Ele não faz alterações nos submódulos existentes
Sergey G.
73

Seu projeto principal aponta para um commit específico em que o submódulo deve estar. git submodule updatetenta verificar a confirmação em cada submódulo que foi inicializado. O submódulo é realmente um repositório independente - apenas criar um novo commit no submódulo e pressionar isso não é suficiente. Você também precisa adicionar explicitamente a nova versão do submódulo no projeto principal.

Portanto, no seu caso, você deve encontrar o commit correto no submódulo - vamos assumir que essa é a dica master:

cd mod
git checkout master
git pull origin master

Agora volte ao projeto principal, prepare o submódulo e confirme:

cd ..
git add mod
git commit -m "Updating the submodule 'mod' to the latest version"

Agora empurre sua nova versão do projeto principal:

git push origin master

A partir deste momento, se alguém atualizar seu projeto principal, git submodule updateele atualizará o submódulo, assumindo que ele foi inicializado.

Mark Longair
fonte
24

Parece que dois cenários diferentes estão sendo misturados nesta discussão:

Cenário 1

Usando os ponteiros do meu repositório pai para submódulos, quero verificar o commit em cada submódulo para o qual o repositório pai está apontando, possivelmente depois de primeiro percorrer todos os submódulos e atualizá-los / removê-los do controle remoto.

Isso é, como apontado, feito com

git submodule foreach git pull origin BRANCH
git submodule update

Cenário 2, que eu acho que é o objetivo do OP

Novas coisas aconteceram em um ou mais submódulos, e eu quero 1) extrair essas alterações e 2) atualizar o repositório pai para apontar para o commit HEAD (mais recente) deste / desses submódulos.

Isso seria feito por

git submodule foreach git pull origin BRANCH
git add module_1_name
git add module_2_name
......
git add module_n_name
git push origin BRANCH

Não é muito prático, pois você teria que codificar n caminhos para todos os n submódulos em um script para atualizar os ponteiros de confirmação do repositório pai.

Seria legal ter uma iteração automatizada em cada submódulo, atualizando o ponteiro do repositório pai (usando git add) para apontar para a cabeça do submódulo (s).

Para isso, criei este pequeno script Bash:

git-update-submodules.sh

#!/bin/bash

APP_PATH=$1
shift

if [ -z $APP_PATH ]; then
  echo "Missing 1st argument: should be path to folder of a git repo";
  exit 1;
fi

BRANCH=$1
shift

if [ -z $BRANCH ]; then
  echo "Missing 2nd argument (branch name)";
  exit 1;
fi

echo "Working in: $APP_PATH"
cd $APP_PATH

git checkout $BRANCH && git pull --ff origin $BRANCH

git submodule sync
git submodule init
git submodule update
git submodule foreach "(git checkout $BRANCH && git pull --ff origin $BRANCH && git push origin $BRANCH) || true"

for i in $(git submodule foreach --quiet 'echo $path')
do
  echo "Adding $i to root repo"
  git add "$i"
done

git commit -m "Updated $BRANCH branch of deployment repo to point to latest head of submodules"
git push origin $BRANCH

Para executá-lo, execute

git-update-submodules.sh /path/to/base/repo BRANCH_NAME

Elaboração

Primeiro, suponho que o ramo com o nome $ BRANCH (segundo argumento) exista em todos os repositórios. Sinta-se livre para tornar isso ainda mais complexo.

O primeiro par de seções é uma verificação de que os argumentos estão lá. Então eu puxo as coisas mais recentes do repositório pai (eu prefiro usar --ff (avanço rápido) sempre que estou fazendo pulls. Eu me refiz, BTW).

git checkout $BRANCH && git pull --ff origin $BRANCH

Em seguida, pode ser necessário inicializar algum sub-módulo, se novos submódulos foram adicionados ou ainda não foram inicializados:

git submodule sync
git submodule init
git submodule update

Então eu atualizo / puxo todos os submódulos:

git submodule foreach "(git checkout $BRANCH && git pull --ff origin $BRANCH && git push origin $BRANCH) || true"

Observe algumas coisas: Primeiro, estou encadeando alguns comandos Git usando && - o que significa que o comando anterior deve ser executado sem erros.

Após uma possível solicitação bem-sucedida (se novas coisas foram encontradas no controle remoto), faço um esforço para garantir que uma possível confirmação de mesclagem não seja deixada para trás no cliente. Novamente, isso só acontece se uma atração realmente trouxer coisas novas.

Finalmente, a final || trueé garantir que o script continue com erros. Para fazer isso funcionar, tudo na iteração deve estar entre aspas duplas e os comandos Git entre parênteses (precedência do operador).

A minha parte favorita:

for i in $(git submodule foreach --quiet 'echo $path')
do
  echo "Adding $i to root repo"
  git add "$i"
done

Itere todos os submódulos - com --quiet, o que remove a saída 'Entering MODULE_PATH'. Usando 'echo $path'(deve estar entre aspas simples), o caminho para o submódulo é gravado na saída.

Essa lista de caminhos relativos ao submódulo é capturada em uma matriz ( $(...)) - finalmente itere e faça git add $ipara atualizar o repositório pai.

Finalmente, uma confirmação com alguma mensagem explicando que o repositório pai foi atualizado. Esse commit será ignorado por padrão, se nada for feito. Empurre isso para a origem e pronto.

Eu tenho um script executando isso em um trabalho de Jenkins que se liga a uma implantação automatizada agendada posteriormente e funciona como um encanto.

Espero que isso ajude alguém.

Frederik Struck-Schøning
fonte
2
! @ # $% SO Estamos usando scripts semelhantes aos seus; Uma nota: Em vez de `` git submodule foreach --quiet 'echo $ path' '`` usamos `` git submodule foreach --recursive --quiet pwd `` `dentro dos loops for. O pwdcomando imprime o 'caminho absoluto' adequado para cada submódulo presente; --recursivegarante a visita a todos os submódulos, incluindo os submódulos dentro dos submódulos -... que podem estar presentes em um grande projeto. Ambos os métodos causam problemas com diretórios que incluem espaços, por exemplo /c/Users/Ger/Project\ Files/..., a política é nunca usar espaços em branco em nenhum lugar de nossos projetos.
Ger Hobbelt 12/12
2
Isso é legal, e você está certo de que há um mal-entendido em algumas respostas sobre qual é a pergunta, mas, como apontado pela excelente resposta de David Z, seu script é desnecessário, pois a funcionalidade foi incorporada ao Git desde meados de 2013, quando eles adicionaram a --remoteopção. git submodule update --remotese comporta aproximadamente da maneira que seu script.
Mark Amery
@GerHobbelt Thanks. Você está certo, temos apenas um nível de submódulos, então nunca pensei em torná-lo recursivo. Não vou atualizar o script, antes que eu tenha a chance de verificar se ele funciona como esperado, mas definitivamente meu script incluiria sub-submódulos. Quanto aos espaços nas pastas, isso definitivamente parece algo a ser evitado! : S
Frederik Struck-Schøning 06/02
@MarkAmery Obrigado pelo seu feedback. Eu vejo um problema, no entanto: não pelo argumento poder especificar ramificação para sub-módulos. No manual git: The remote branch used defaults to master, but the branch name may be overridden by setting the submodule.<name>.branch option in either .gitmodules or .git/config (with .git/config taking precedence).não quero editar .gitmodules nem .git / config toda vez que quiser fazer isso em outro ramo que não seja o master. Mas talvez eu tenha perdido alguma coisa? Além disso, o método parece impor mesclagens recursivas (perdendo a possibilidade de avançar rapidamente).
Frederik Struck-Schøning
Última coisa: tentei o método do @ DavidZ, e ele parece não fazer exatamente o que pretendi (e qual operação estava perguntando): Adicionando o commit HEAD dos submódulos ao pai (ou seja, "atualizando o ponteiro" ) Parece, no entanto, fazer o único trabalho muito bem (e mais rápido) ao buscar e mesclar as alterações mais recentes em todos os submódulos. Infelizmente, por padrão, apenas a partir da ramificação principal (a menos que você edite o arquivo .gitmodules (veja acima)).
Frederik Struck-Schøning
19

Puro e simples, para buscar os submódulos:

git submodule update --init --recursive

E agora continue atualizando-os para a ramificação principal mais recente (por exemplo):

git submodule foreach git pull origin master
Daniel Andrei Mincă
fonte
12

Observe que, embora a forma moderna de atualização do submódulo seja:

git submodule update --recursive --remote --merge --force

A forma mais antiga era:

git submodule foreach --quiet git pull --quiet origin

Exceto ... essa segunda forma não é realmente "silenciosa".

Veja commit a282f5a (12 Abr 2019) por Nguyễn Thái Ngọc Duy ( pclouds) .
(Incorporado por Junio ​​C Hamano - gitster- in commit f1c9f6c , 25 de abr de 2019)

submodule foreach: correção " <command> --quiet" não sendo respeitada

Robin relatou que

git submodule foreach --quiet git pull --quiet origin

não está mais quieto.
Deve ficar quieto antes do fc1b924 ( submodule: submodulesubcomando port ' foreach' do shell para o C, 10/05/2018, Git v2.19.0-rc0) porqueparseopt não pode comer opções acidentalmente.

" git pull" se comporta como se --quietnão fosse dado.

Isso acontece porque o parseoptin submodule--helpertentará analisar as duas --quietopções como se fossem opções de foreach, não git-pull's.
As opções analisadas são removidas da linha de comando. Então, quando puxamos mais tarde, executamos exatamente isso

git pull origin

Ao chamar o auxiliar do submódulo, adicionar " --" na frente de "git pull " será interrompido parseoptpara analisar as opções que realmente não pertencem submodule--helper foreach.

PARSE_OPT_KEEP_UNKNOWN é removido como medida de segurança. parseoptnunca deve ver opções desconhecidas ou algo deu errado. Há também algumas atualizações de string de uso enquanto eu estou olhando para elas.

Enquanto isso, eu também adiciono " --" a outros subcomandos que passam " $@" para submodule--helper. " $@" nesses casos, há caminhos e menos probabilidade de existir --something-like-this.
Mas o argumento ainda permanece, git-submoduleanalisou e classificou o que são opções, o que são caminhos.
submodule--helpernunca deve considerar caminhos passados git-submodulecomo opções, mesmo que pareçam um.


E o Git 2.23 (terceiro trimestre de 2019) corrige outro problema: "git submodule foreach " não protegeu as opções da linha de comando passadas para o comando para serem executadas em cada submódulo corretamente, quando o "--recursive opção " estava em uso.

Veja commit 30db18b (24 Jun 2019) por Morian Sonnet ( momoson) .
(Incorporado por Junio ​​C Hamano - gitster- in commit 968eecb , 09 jul 2019)

submodule foreach: corrigir recursão de opções

A ligar:

git submodule foreach --recursive <subcommand> --<option>

leva a um erro informando que a opção --<option>é desconhecida submodule--helper.
Claro que isso é apenas quando <option>não é uma opção válida paragit submodule foreach .

A razão para isso é que a chamada acima é traduzida internamente em uma chamada para o submódulo - helper:

git submodule--helper foreach --recursive \
    -- <subcommand> --<option>

Essa chamada começa executando o subcomando com sua opção dentro do submódulo de primeiro nível e continua chamando a próxima iteração da submodule foreachchamada

git --super-prefix <submodulepath> submodule--helper \
   foreach --recursive <subcommand> --<option>

dentro do submódulo de primeiro nível. Observe que o traço duplo na frente do subcomando está ausente.

Esse problema começa a surgir apenas recentemente, pois o PARSE_OPT_KEEP_UNKNOWNsinalizador para a análise do argumento git submodule foreachfoi removido na confirmação a282f5a .
Portanto, a opção desconhecida é reclamada agora, pois a análise do argumento não é finalizada corretamente pelo traço duplo.

Essa confirmação corrige o problema adicionando o traço duplo na frente do subcomando durante a recursão.

VonC
fonte
7
git pull --recurse-submodules

Isso puxará todas as confirmações mais recentes.


fonte
4

No meu caso, eu queria git atualizar para o mais recente e, ao mesmo tempo, preencher novamente os arquivos ausentes.

O seguinte restaurou os arquivos ausentes (graças aos --forcequais não parece ter sido mencionado aqui), mas não gerou nenhum novo commit:

git submodule update --init --recursive --force

Isso fez:

git submodule update --recursive --remote --merge --force

noseratio
fonte
3

@ Jason está correto de uma maneira, mas não totalmente.

atualizar

Atualize os submódulos registrados, ou seja, clone os submódulos ausentes e faça o checkout da confirmação especificada no índice do repositório que o contém. Isso fará com que os sub-módulos HEAD sejam desanexados, a menos que --rebase ou --merge seja especificado ou o sub-módulo-chave. $ Name.update esteja configurado para rebase ou mesclagem.

O mesmo git submodule updateocorre com o checkout, mas é para o commit no índice do repositório que o contém. Ainda não conhece o novo commit upstream. Portanto, vá para o seu submódulo, obtenha a confirmação que você deseja e confirme o estado do submódulo atualizado no repositório principal e faça o git submodule update.

manojlds
fonte
1
Parece que se eu mover o submódulo para uma confirmação diferente e executar git submodule update, a atualização moverá o submódulo para a confirmação especificada no HEAD atual do superprojeto. (qualquer que seja o commit mais recente no superprojeto que o subprojeto deva estar - esse comportamento, após a explicação na postagem de Jason, me parece lógico) Ele também parece ser buscado, mas apenas no caso em que o subprojeto está no commit incorreto , o que estava aumentando a minha confusão.
Thanatos
2

Aqui está uma linha única impressionante para atualizar tudo para o mais recente no master:

git submodule foreach 'git fetch origin --tags; git checkout master; git pull' && git pull && git submodule update --init --recursive

Obrigado a Mark Jaquith

dustinrwh
fonte
2

Se você não conhece a ramificação do host, faça o seguinte:

git submodule foreach git pull origin $(git rev-parse --abbrev-ref HEAD)

Ele obterá uma ramificação do repositório principal do Git e, em seguida, para cada submódulo fará um puxão da mesma ramificação.

NickUnuchek
fonte
0

Se você deseja fazer o checkout de uma masterramificação para cada submódulo - você pode usar o seguinte comando para esse fim:

git submodule foreach git checkout master
Mohsin Mahmood
fonte