Aller au contenu
Développement medium

Git submodules : intégrer des dépôts externes

12 min de lecture

Un submodule Git est un dépôt Git imbriqué dans un autre. Le projet parent référence un commit précis du sous-dépôt, ce qui permet d'intégrer des bibliothèques, des outils ou des configurations partagées tout en gardant les historiques séparés. Ce guide couvre le workflow complet, de l'ajout à la mise à jour.

Prérequis : Remotes fondamentaux et Branches distantes.

  • Ajouter un submodule dans un dépôt existant et cloner avec ses submodules
  • Mettre à jour un submodule vers une nouvelle version du dépôt distant
  • Identifier les pièges classiques : HEAD détaché, oubli de commit, double push
  • Comparer submodules et git subtree pour choisir la bonne approche

Les submodules sont utiles quand :

  • Vous intégrez une bibliothèque partagée entre plusieurs projets
  • Vous voulez une version figée d'une dépendance (pas toujours la dernière)
  • Les dépôts doivent garder des historiques indépendants
  • Chaque équipe gère son propre dépôt

Exemples concrets : un framework interne, des fichiers de configuration Terraform partagés, un thème de documentation.

Une seule commande suffit pour rattacher un dépôt externe. Elle prend deux arguments : l'URL du dépôt à intégrer, puis le chemin où le déposer dans votre projet. Ce chemin devient le nom du submodule, c'est lui que vous retrouverez dans toutes les commandes ultérieures.

Fenêtre de terminal
git submodule add https://github.com/org/lib-utils.git libs/utils

Cette commande :

  1. Clone lib-utils dans libs/utils/

  2. Crée (ou met à jour) le fichier .gitmodules :

    [submodule "libs/utils"]
    path = libs/utils
    url = https://github.com/org/lib-utils.git
  3. Enregistre le commit exact du submodule dans l'index du projet parent

  4. Écrit l'URL dans .git/config du projet parent, sous la clé submodule.libs/utils.url : c'est cette copie locale, et non .gitmodules, que Git utilise réellement pour cloner

Commitez les changements :

Fenêtre de terminal
git add .gitmodules libs/utils
git commit -m "chore: ajouter lib-utils comme submodule"

Un git clone ordinaire crée bien le dossier libs/utils/ mais le laisse vide : Git connaît la référence, il ne l'a simplement pas suivie. L'option --recurse-submodules enchaîne le clone du projet parent et celui de chaque submodule dans la foulée. C'est la forme à retenir quand vous récupérez un projet pour la première fois.

Fenêtre de terminal
git clone --recurse-submodules https://github.com/org/mon-projet.git

Le rattrapage se fait en deux temps, et cette séparation explique la plupart des messages d'erreur. init recopie les URLs de .gitmodules vers votre .git/config local, update va chercher le contenu au commit exact enregistré par le projet parent. Oublier init donne un fatal: No url found for submodule path.

Fenêtre de terminal
git submodule init # Enregistre les URLs depuis .gitmodules
git submodule update # Clone les submodules au commit référencé

Ou en une seule commande :

Fenêtre de terminal
git submodule update --init --recursive

--recursive gère aussi les submodules imbriqués (submodules de submodules).

Mettre à jour un submodule, c'est deux opérations dans deux dépôts différents. Vous faites d'abord avancer le sous-dépôt, puis vous enregistrez cette nouvelle position dans le projet parent avec un commit. Sauter la seconde étape est l'oubli le plus courant : votre poste fonctionne, mais vos collègues récupèrent toujours l'ancienne version.

  1. Allez dans le dossier du submodule :

    Fenêtre de terminal
    cd libs/utils
  2. Mettez à jour depuis le remote :

    Fenêtre de terminal
    git fetch
    git checkout main
    git pull
  3. Revenez au projet parent et commitez la nouvelle référence :

    Fenêtre de terminal
    cd ../..
    git add libs/utils
    git commit -m "chore: mettre à jour lib-utils vers v2.1"

Sans --remote, git submodule update ramène le submodule au commit enregistré par le parent. Avec --remote, il fait l'inverse : il va chercher la pointe de la branche distante et fait donc avancer le submodule. Une seule lettre d'option sépare deux comportements opposés, c'est la confusion la plus fréquente sur cette commande.

Fenêtre de terminal
git submodule update --remote

En l'absence de configuration, la branche suivie est celle vers laquelle pointe le HEAD du dépôt distant. Vous pouvez en imposer une autre, par exemple develop :

Fenêtre de terminal
git submodule set-branch --branch develop -- libs/utils

Cette commande écrit branch = develop dans .gitmodules, un fichier versionné : pensez à le commiter pour que le choix s'applique à toute l'équipe.

Vous pouvez modifier le code du submodule directement :

  1. Entrez dans le submodule et créez une branche :

    Fenêtre de terminal
    cd libs/utils
    git switch -c fix/typo
  2. Modifiez, commitez :

    Fenêtre de terminal
    git add .
    git commit -m "fix: corriger typo dans README"
  3. Poussez vers le remote du submodule :

    Fenêtre de terminal
    git push origin fix/typo
  4. Revenez au projet parent et enregistrez le nouveau commit :

    Fenêtre de terminal
    cd ../..
    git add libs/utils
    git commit -m "chore: avancer lib-utils (fix typo)"

Ajoutez cette option aux commandes courantes pour ne pas oublier les submodules :

Fenêtre de terminal
git pull --recurse-submodules
git switch --recurse-submodules feature/ajout-login
git clone --recurse-submodules https://github.com/org/mon-projet.git

Pour l'activer par défaut :

Fenêtre de terminal
git config --global submodule.recurse true

Il n'y a pas de commande unique, et c'est ce qui rend l'opération piégeuse : un submodule laisse des traces à trois endroits. Le .gitmodules versionné, le .git/config local, et le dépôt réel stocké dans .git/modules/. Sauter la première étape ci-dessous laisse une entrée submodule.libs/utils.url orpheline dans votre configuration locale, qui ressortira lors d'un futur git submodule update.

Fenêtre de terminal
# 1. Désenregistrer le submodule (vide le dossier, nettoie .git/config)
git submodule deinit -f libs/utils
# 2. Supprimer l'entrée de .gitmodules et le dossier de l'index
git rm libs/utils
# 3. Supprimer le dépôt interne conservé par Git
rm -rf .git/modules/libs/utils
# 4. Commiter
git commit -m "chore: supprimer le submodule lib-utils"

Vérifiez le résultat avant de commiter : git config --get-regexp '^submodule\.' ne doit plus rien renvoyer pour ce chemin.

git submodule foreach exécute une commande shell dans chaque submodule, l'un après l'autre. C'est utile pour une inspection groupée sur un projet qui en compte plusieurs. Deux précautions : la commande s'arrête au premier submodule en échec, et elle ne connaît rien du projet parent, donc elle ne commite jamais la nouvelle référence pour vous.

Fenêtre de terminal
# Afficher le statut de tous les submodules
git submodule foreach 'git status'
# Mettre à jour tous les submodules vers leur branche suivie
git submodule update --remote
# Ou, si vous voulez passer par git pull dans chaque submodule :
# git submodule foreach 'git pull'
# Stasher dans tous les submodules
git submodule foreach 'git stash'

Après git submodule update, le submodule est toujours en detached HEAD (il pointe vers un commit, pas une branche). Pour travailler dedans, créez ou checkoutez une branche :

Fenêtre de terminal
cd libs/utils
git switch main

Le projet parent voit que le submodule pointe vers un commit différent de celui référencé. C'est normal si vous avez fait un git pull dans le submodule. Commitez la mise à jour dans le parent :

Fenêtre de terminal
git add libs/utils
git commit -m "chore: avancer lib-utils"

Par défaut, un git diff sur un submodule n'affiche que deux empreintes de commit, ce qui ne dit rien de ce qui a réellement changé. Les options ci-dessous demandent à Git de résumer les commits traversés plutôt que les SHA bruts. C'est la seule façon de relire une mise à jour de dépendance avant de la valider.

Fenêtre de terminal
# Voir les détails
git diff --submodule
# Résumé
git diff --submodule=short

Pour avoir le résumé automatiquement :

Fenêtre de terminal
git config --global diff.submodule log

La ligne qui décide dans neuf cas sur dix est Contribution au sous-projet. Si votre équipe modifie régulièrement le code intégré, les submodules sont le seul choix qui garde un vrai dépôt Git en local, avec son historique et sa possibilité de pousser. Si vous consommez du code sans jamais le modifier, un gestionnaire de paquets vous épargnera toute la complexité décrite dans cette page. Le subtree est le compromis pour ceux qui veulent un clone simple sans dépendance externe.

CritèreSubmodulesSubtreeGestionnaire de paquets
HistoriquesSéparésFusionnéNon versionné
Mise à jourManuelle (commit ref)git subtree pullnpm update, etc.
ComplexitéMoyenne-élevéeMoyenneFaible
CloneNécessite --recurseAutomatiqueNécessite install
Contribution au sous-projetFacile (push dans le submodule)Possible (subtree push)Via le repo original

Quatre de ces cinq symptômes ont la même origine : le projet parent référence un commit que Git ne trouve pas, ou pas encore. Avant d'appliquer une solution, lancez git submodule status à la racine du projet : un préfixe - signale un submodule non initialisé, un + que le commit sorti diffère de celui enregistré. Ce seul caractère oriente le diagnostic plus vite que n'importe quel message d'erreur.

SymptômeCause probableSolution
Dossier submodule vide après clone--recurse-submodules oubliégit submodule update --init --recursive
HEAD détaché dans le submoduleComportement normalgit switch branche pour travailler
reference is not a tree au cloneCommit du submodule non pousséPousser le submodule d'abord
Conflit sur la ref du submoduleDeux branches avancent le submoduleRésolvez en choisissant le bon commit
fatal: No url found for submodule.gitmodules absent ou corrompuVérifiez/recréez .gitmodules
  • Un submodule référence un commit précis d'un dépôt externe
  • git submodule update --init --recursive est la commande à retenir pour initialiser
  • Après update, le submodule est en detached HEAD, normal
  • Poussez toujours le submodule avant le parent
  • submodule.recurse true simplifie la vie au quotidien
  • Pour des besoins plus simples, considérez subtree

Ce site vous est utile ?

Sachez que moins de 1% des lecteurs soutiennent ce site.

Je maintiens +700 guides gratuits, sans pub ni tracking. Un soutien, même symbolique, m'aide à couvrir l'hébergement et à garder ces ressources gratuites. Merci pour votre appui.

Le formulaire ne s'affiche pas ? Ouvrir Ko-fi dans un onglet.

Abonnez-vous et suivez mon actualité DevSecOps sur LinkedIn