Mettre à jour le sous-module Git vers le dernier commit sur l'origine


853

J'ai un projet avec un sous-module Git. Il provient d'une URL ssh: // ... et se trouve sur la validation A. La validation B a été poussée vers cette URL, et je veux que le sous-module récupère la validation et la modifie.

Maintenant, je crois que cela git submodule updatedevrait le faire, mais ce n'est pas le cas. Il ne fait rien (pas de sortie, code de sortie de réussite). Voici un exemple:

$ 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
$ ...

J'ai également essayé git fetch mod, qui semble faire une extraction (mais ne peut pas, car il ne demande pas de mot de passe!), Mais git loget git shownie l'existence de nouveaux commits. Jusqu'à présent, je viens de rmmodifier le module et de le rajouter, mais c'est à la fois faux en principe et fastidieux dans la pratique.


5
La réponse de David Z semble être la meilleure façon de le faire - maintenant que Git a la fonctionnalité dont vous avez besoin intégrée via l' --remoteoption, il serait peut-être utile de marquer cela comme la réponse acceptée plutôt que l'approche "à la main" dans la réponse de Jason?
Mark Amery

1
Je suis très d'accord avec @MarkAmery. Bien que Jason ait donné une solution de travail, ce n'est pas la manière prévue de le faire, car elle laisse le pointeur de validation du sous-module au mauvais identifiant de validation. Le nouveau --remoteest définitivement une meilleure solution à ce stade, et puisque cette question a été liée à un Github Gist sur les sous-modules, je pense qu'il serait préférable pour les lecteurs entrants de voir la nouvelle réponse.
MutantOctopus

Belle touche avec le hunter2mot de passe: o)
lfarroco

Réponses:


1458

La git submodule updatecommande indique en fait à Git que vous voulez que vos sous-modules vérifient chacun le commit déjà spécifié dans l'index du superprojet. Si vous souhaitez mettre à jour vos sous-modules vers la dernière validation disponible à partir de leur télécommande, vous devrez le faire directement dans les sous-modules.

Donc en résumé:

# 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, si vous êtes une personne occupée:

git submodule foreach git pull origin master

335
git submodule foreach git pull
Mathias Bynens

87
@Nicklas Dans ce cas, utilisez git submodule foreach git pull origin master.
Mathias Bynens

54
À ce stade, avec toutes ces corrections apportées aux corrections, j'ai besoin de quelqu'un pour écrire un article de blog explicatif et me diriger là-bas. S'il vous plaît.
Suz

25
amélioration mineure de l'approche «foreach» - vous voudrez peut-être ajouter - récursive là-dedans au cas où vous auriez des sous-modules dans les sous-modules. donc: git submodule foreach --recursive git pull origin master.
orion elenzil

4
@Abdull Le -acommutateur git commit"Tell [s] the command to stage automatiquement les fichiers qui ont été modifiés et supprimés, mais les nouveaux fichiers dont vous n'avez pas parlé à Git ne sont pas affectés."
godfrzero

473

Git 1.8.2 propose une nouvelle option --remote, qui activera exactement ce comportement. Fonctionnement

git submodule update --remote --merge

récupérera les dernières modifications en amont dans chaque sous-module, les fusionnera et vérifiera la dernière révision du sous-module. Comme le dit la documentation :

--éloigné

Cette option n'est valable que pour la commande update. Au lieu d'utiliser le SHA-1 enregistré du superprojet pour mettre à jour le sous-module, utilisez l'état de la branche de suivi à distance du sous-module.

Cela revient à exécuter git pulldans chaque sous-module, ce qui est généralement exactement ce que vous voulez.


4
"équivaut à courir git pulldans chaque sous-module" Pour clarifier, il n'y a pas de différence (du point de vue de l'utilisateur) entre votre réponse et git submodule foreach git pull?
Dennis

3
@Dennis, il fait essentiellement la même chose, mais je ne suis pas sûr que la fonctionnalité soit exactement la même. Il peut y avoir quelques différences mineures que je ne connais pas, par exemple dans la façon dont les deux commandes répondent à certains paramètres de configuration.
David Z

5
J'aimerais pouvoir voter pour ce 10 000X. Pourquoi cela n'apparaît-il nulle part dans la documentation de git? Énorme surveillance.
serraosays

4
Pour moi, ils différaient de façon assez significative; foreach git pullles a uniquement extraits, mais n'ont pas mis à jour le pointeur du référentiel principal pour pointer vers la nouvelle validation du sous-module. Ce n'est qu'avec --remotecela qu'il a fait pointer le dernier commit.
Ela782

5
pourquoi l'option --merge? Quelle différence cela fait-il?
mFeinstein

127

Dans le répertoire parent de votre projet, exécutez:

git submodule update --init

Ou si vous avez des sous-modules récursifs exécutés:

git submodule update --init --recursive

Parfois, cela ne fonctionne toujours pas, car vous avez en quelque sorte des modifications locales dans le répertoire du sous-module local pendant la mise à jour du sous-module.

La plupart du temps, le changement local n'est peut-être pas celui que vous souhaitez valider. Cela peut se produire en raison d'une suppression de fichier dans votre sous-module, etc. Si c'est le cas, effectuez une réinitialisation dans votre répertoire de sous-module local et dans le répertoire parent de votre projet, exécutez à nouveau:

git submodule update --init --recursive

5
c'est la vraie réponse. puis-je le pousser dans mon référentiel distant d'une manière ou d'une autre?
MonsterMMORPG

Cela fonctionne pour les nouveaux sous-modules! Je pouvais mettre à jour tous les autres mais le dossier des nouveaux sous-modules resterait vide jusqu'à ce que j'exécute cette commande.
Alexis Wilke

1
Il ne tire pas les modifications des sous
Sergey G.

73

Votre projet principal pointe vers un commit particulier auquel le sous-module doit se trouver. git submodule updateessaie de vérifier ce commit dans chaque sous-module qui a été initialisé. Le sous-module est vraiment un référentiel indépendant - il suffit de créer un nouveau commit dans le sous-module et de pousser cela ne suffit pas. Vous devez également ajouter explicitement la nouvelle version du sous-module dans le projet principal.

Donc, dans votre cas, vous devriez trouver le bon commit dans le sous-module - supposons que c'est la pointe de master:

cd mod
git checkout master
git pull origin master

Revenez maintenant au projet principal, mettez en scène le sous-module et validez que:

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

Poussez maintenant votre nouvelle version du projet principal:

git push origin master

À partir de ce moment, si quelqu'un met à jour son projet principal, alors git submodule updateil mettra à jour le sous-module, en supposant qu'il a été initialisé.


24

Il semble que deux scénarios différents soient mélangés dans cette discussion:

Scénario 1

En utilisant les pointeurs de mon référentiel parent vers les sous-modules, je veux vérifier la validation dans chaque sous-module vers lequel pointe le référentiel parent, peut-être après avoir d'abord itéré tous les sous-modules et les avoir mis à jour / extraits à distance.

Ceci est, comme souligné, fait avec

git submodule foreach git pull origin BRANCH
git submodule update

Scénario 2, qui je pense est ce que vise OP

De nouvelles choses se sont produites dans un ou plusieurs sous-modules, et je veux 1) tirer ces modifications et 2) mettre à jour le référentiel parent pour pointer vers la validation HEAD (la plus récente) de ce / ces sous-modules.

Cela serait fait par

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

Pas très pratique, car il faudrait coder en dur n chemins vers tous les n sous-modules, par exemple dans un script pour mettre à jour les pointeurs de validation du référentiel parent.

Il serait intéressant d'avoir une itération automatisée à travers chaque sous-module, mettant à jour le pointeur du référentiel parent (à l'aide git add) pour pointer vers la tête du ou des sous-modules.

Pour cela, j'ai réalisé ce petit 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

Pour l'exécuter, exécutez

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

Élaboration

Tout d'abord, je suppose que la branche avec le nom $ BRANCH (deuxième argument) existe dans tous les référentiels. N'hésitez pas à rendre cela encore plus complexe.

Les deux premières sections consistent à vérifier que les arguments sont là. Ensuite, je récupère les derniers éléments du référentiel parent (je préfère utiliser --ff (avance rapide) chaque fois que je fais des pulls. J'ai rebase off, BTW).

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

Ensuite, une initialisation de sous-module peut être nécessaire si de nouveaux sous-modules ont été ajoutés ou ne sont pas encore initialisés:

git submodule sync
git submodule init
git submodule update

Ensuite, je mets à jour / tire tous les sous-modules:

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

Remarquez quelques choses: Tout d'abord, je chaîne certaines commandes Git en utilisant &&- ce qui signifie que la commande précédente doit s'exécuter sans erreur.

Après un éventuel pull réussi (si de nouveaux éléments ont été trouvés sur la télécommande), je fais un push pour m'assurer qu'un éventuel merge-commit n'est pas laissé sur le client. Encore une fois, cela ne se produit que si un pull a réellement apporté de nouvelles choses.

Enfin, la finale || trues'assure que le script continue sur les erreurs. Pour que cela fonctionne, tout dans l'itération doit être encapsulé entre guillemets et les commandes Git sont encapsulées entre parenthèses (priorité de l'opérateur).

Ma partie préférée:

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

Itérer tous les sous-modules - avec --quiet, ce qui supprime la sortie «Entering MODULE_PATH». À l'aide de 'echo $path'(doit être entre guillemets simples), le chemin d'accès au sous-module est écrit en sortie.

Cette liste de chemins de sous-module relatifs est capturée dans un tableau ( $(...)) - enfin, répétez cela et faites git add $ipour mettre à jour le référentiel parent.

Enfin, un commit avec un message expliquant que le référentiel parent a été mis à jour. Ce commit sera ignoré par défaut, si rien n'a été fait. Poussez ceci à l'origine, et vous avez terminé.

J'ai un script qui exécute cela dans un travail Jenkins qui s'enchaîne ensuite à un déploiement automatisé planifié, et cela fonctionne comme un charme.

J'espère que cela sera utile à quelqu'un.


2
! @ # $% SO Nous utilisons des scripts similaires aux vôtres; une note: Au lieu de `` git submodule foreach --quiet 'echo $ path' '`` nous utilisons `` git submodule foreach --recursive --quiet pwd' '`` à l'intérieur des boucles for. La pwdcommande imprime le «chemin absolu» approprié pour chaque sous-module présent; --recursives'assure que nous visitons tous les sous-modules, y compris les sous-modules dans les sous-modules -... qui peuvent être présents dans un grand projet. Les deux méthodes causer des problèmes avec les répertoires qui comprennent des espaces, par exemple /c/Users/Ger/Project\ Files/...où la politique est de ne jamais utiliser des espaces partout dans nos projets.
Ger Hobbelt

2
C'est bien, et vous avez raison, il y a un malentendu dans certaines réponses sur ce qu'est la question même, mais comme l'a souligné l'excellente réponse de David Z, votre script n'est pas nécessaire car la fonctionnalité a été intégrée à Git depuis la mi-2013 lorsque ils ont ajouté l' --remoteoption. git submodule update --remotese comporte approximativement comme le fait votre script.
Mark Amery

@GerHobbelt Merci. Vous avez raison, nous n'avons qu'un seul niveau de sous-modules, donc je n'ai jamais pensé à le rendre récursif. Je ne mettrai pas à jour le script, avant d'avoir eu la chance de vérifier qu'il fonctionne comme prévu, mais mon script contiendrait certainement des sous-sous-modules. Quant aux espaces dans les dossiers, cela ressemble définitivement à quelque chose à éviter! : S
Frederik Struck-Schøning

@MarkAmery Merci pour vos commentaires. Je vois cependant un problème: pas d'argumentation secondaire pouvant spécifier la branche pour les sous-modules. Du manuel de 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).je ne veux pas éditer .gitmodules ni .git / config chaque fois que je veux le faire dans une autre branche que master. Mais peut-être que j'ai raté quelque chose? De plus, la méthode semble imposer des fusions récursives (manquant ainsi la possibilité d'une avance rapide).
Frederik Struck-Schøning

Dernière chose: j'ai essayé la méthode de @ DavidZ, et cela ne semble pas faire exactement la chose, j'ai décidé de faire (et à propos de laquelle op demandait): Ajouter la validation HEAD des sous-modules au parent (c'est-à-dire "mettre à jour le pointeur" ). Cependant, il semble faire le seul travail très bien (et plus rapidement) de récupération et de fusion des dernières modifications dans tous les sous-modules. Hélas, par défaut uniquement à partir de la branche principale (sauf si vous modifiez le fichier .gitmodules (voir ci-dessus)).
Frederik Struck-Schøning

19

Clair et simple, pour aller chercher les sous-modules:

git submodule update --init --recursive

Et maintenant, continuez à les mettre à jour vers la dernière branche principale (par exemple):

git submodule foreach git pull origin master

13

Remarque, alors que la forme moderne de mise à jour des commits de sous-modules serait:

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

L'ancienne forme était:

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

Sauf que cette deuxième forme n'est pas vraiment "silencieuse".

Voir commit a282f5a (12 avril 2019) par Nguyễn Thái Ngọc Duy ( pclouds) .
(Fusionné par Junio ​​C Hamano - gitster- dans commit f1c9f6c , 25 avril 2019)

submodule foreach: correction du " <command> --quiet" non respecté

Robin a rapporté que

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

n'est plus vraiment calme.
Il devrait être silencieux avant fc1b924 ( submodule: submodulesous-commande port ' foreach' du shell au C, 2018-05-10, Git v2.19.0-rc0) car parseoptil ne peut pas alors manger accidentellement des options.

" git pull" se comporte comme s'il --quietn'était pas donné.

Cela se produit car parseoptin submodule--helperessaiera d'analyser les deux --quietoptions comme si elles étaient des options foreach, pas git-pulldes options .
Les options analysées sont supprimées de la ligne de commande. Donc quand on tire plus tard, on exécute juste ceci

git pull origin

Lors de l'appel de l'assistant de sous-module, l'ajout de " --" devant " git pull" s'arrêtera parseoptpour les options d'analyse qui n'appartiennent pas vraiment submodule--helper foreach.

PARSE_OPT_KEEP_UNKNOWNest supprimé par mesure de sécurité. parseoptne devrait jamais voir d'options inconnues ou quelque chose a mal tourné. Il y a aussi quelques mises à jour de chaîne d'utilisation pendant que je les regarde.

Pendant que j'y suis, j'ajoute également " --" à d'autres sous-commandes qui passent " $@" à submodule--helper. " $@" dans ces cas sont des chemins et moins susceptibles de l'être --something-like-this.
Mais le point est toujours là, git-submodulea analysé et classé quelles sont les options, quels sont les chemins.
submodule--helperne devrait jamais considérer les chemins passés git-submodulecomme des options même s'ils en ressemblent un.


Et Git 2.23 (Q3 2019) corrige un autre problème: " git submodule foreach" ne protégeait pas les options de ligne de commande passées à la commande à exécuter correctement dans chaque sous-module, lorsque l' --recursiveoption " " était en cours d'utilisation.

Voir commit 30db18b (24 juin 2019) par Morian Sonnet ( momoson) .
(Fusionné par Junio ​​C Hamano - gitster- en commit 968eecb , 09 juil.2019 )

submodule foreach: correction de la récursivité des options

Appel:

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

conduit à une erreur indiquant que l'option --<option>est inconnue de submodule--helper.
C'est bien sûr seulement, quand <option>n'est pas une option valide pour git submodule foreach.

La raison en est que l'appel ci-dessus est traduit en interne en un appel au sous-module - helper:

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

Cet appel commence par exécuter la sous-commande avec son option à l'intérieur du sous-module de premier niveau et se poursuit en appelant la prochaine itération de l' submodule foreachappel

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

à l'intérieur du sous-module de premier niveau. Notez que le double tiret devant la sous-commande est manquant.

Ce problème ne commence à se poser que récemment, car l' PARSE_OPT_KEEP_UNKNOWNindicateur pour l'analyse des arguments de a git submodule foreachété supprimé dans la validation a282f5a .
Par conséquent, l'option inconnue se plaint maintenant, car l'analyse des arguments n'est pas correctement terminée par le double tiret.

Ce commit corrige le problème en ajoutant le double tiret devant la sous-commande pendant la récursivité.


7
git pull --recurse-submodules

Cela tirera tous les derniers commits.


4

Dans mon cas, je voulais gitmettre à jour au plus tard et en même temps re-remplir tous les fichiers manquants.

Ce qui suit a restauré les fichiers manquants ( --forcece qui ne semble pas avoir été mentionné ici), mais il n'a pas généré de nouveaux commits:

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

Cela a:

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


3

@Jason est correct dans un sens mais pas entièrement.

mise à jour

Mettez à jour les sous-modules enregistrés, c'est-à-dire clonez les sous-modules manquants et extrayez le commit spécifié dans l'index du référentiel contenant. Cela fera que les sous-modules HEAD seront détachés à moins que --rebase ou --merge ne soit spécifié ou que le sous-module clé. $ Name.update soit défini pour rebaser ou fusionner.

Il en git submodule updateva de même pour la vérification, mais c'est pour la validation dans l'index du référentiel contenant. Il n'a pas encore connaissance du nouveau commit en amont. Allez donc dans votre sous-module, obtenez le commit que vous voulez et validez l'état du sous-module mis à jour dans le référentiel principal, puis faites le git submodule update.


1
Il semble que si je déplace le sous-module vers un autre commit, puis l'exécute git submodule update, update déplacera le sous-module vers le commit spécifié dans le HEAD actuel du superprojet. (quel que soit le commit le plus récent du superprojet, le sous-projet devrait être - ce comportement, après l'explication dans le post de Jason, me semble logique) Il semble également aller chercher, mais uniquement dans le cas où le sous-projet est sur le mauvais commit , ce qui ajoutait à ma confusion.
Thanatos

2

Voici un impressionnant one-liner pour tout mettre à jour au plus tard sur master:

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

Merci à Mark Jaquith


2

Si vous ne connaissez pas la branche hôte, procédez comme suit:

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

Il obtiendra une branche du référentiel Git principal et puis pour chaque sous-module fera un pull de la même branche.


0

Si vous cherchez à commander une masterbranche pour chaque sous-module, vous pouvez utiliser la commande suivante à cette fin:

git submodule foreach git checkout master
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.