<
Media
>
Article

Les sous-modules git

8
min
07
/
01
/
2025

Qu'est-ce que les sous-modules git ?

Un sous-module c'est un dépôt git… dans un autre dépôt git.

Cela permet d'avoir un lien, unilatéral, entre deux dépôts de code.

Nous pouvons imaginer un cas simple d'utilisation : un produit A utilise une librairie commune à plusieurs produits A, B et C.

Pour réaliser ceci nous pouvons utiliser des sous-modules. Les produits A, B et C vont tous avoir un sous-module contenant le code de la librairie commune.

Évidemment, une autre approche serait d'utiliser un gestionnaire de paquets, mais nous en reparlerons plus tard.

Structure d'un sous-module

Un sous-module c'est un dépôt git dans un autre dépôt git, mais il y a quand même certaines spécificités et malheureusement aussi certains pièges.

Déjà, il est important de toujours savoir dans quel dépôt nous nous trouvons au moment où nous exécutons une commande.

Dans le dépôt parent, git gère les sous-modules comme des liens symboliques vers un commit spécifique.

Par contre, une fois dans le sous-module (ou dépôt enfant) toutes les opérations git habituelles peuvent être exécutées.

Travailler avec des sous-modules git

Pour commencer, nous avons deux dépôts git : depo_1 et depo_2 qui contiennent chacun un fichier README.md.

~ $ git clone git@github.com:Younup/depo_1.git
~ $ git clone git@github.com:Younup/depo_2.git

~ $ tree
.
├── depo_1
│   └── README.md
└── depo_2
    └── README.md

Ajouter un sous-module git

Nous allons maintenant ajouter depo_2 comme sous-module de depo_1 :

~ $ cd depo_1

~/depo_1 --> depo_1.git@main $ git submodule add git@github.com:Younup/depo_2.git

~/depo_1 --> depo_1.git@main $ git commit -m "Add submodule"

~/depo_1 --> depo_1.git@main $ git push
~ $ tree
.
├── depo_1
│   ├── depo_2
│   │   └── README.md
│   └── README.md
└── depo_2
    └── README.md

Récupérer un dépôt qui contient des sous-modules

Pour simuler la récupération de depo_1 avec les sous-modules, nous allons le supprimer puis le re-cloner :

~ $ rm -fr depo_1
~ $ git clone git@github.com:Younup/depo_1.git
~ $ cd repo_1

Et là, nous constatons que le sous-module n'est pas présent…

~/depo_1 --> depo_1.git@main $ tree
.
├── depo_2
└── README.md

Par défaut, git ne clone pas les sous-modules. En effet, toutes les opérations sur les sous-modules se font via la commande git submodule.

Il faut donc dire à git de récupérer aussi les sous-modules de notre dépôt.

~/depo_1 --> depo_1.git@main $ git submodule init
~/depo_1 --> depo_1.git@main $ git submodule update

~/depo_1 --> depo_1.git@main $ tree
.
├── depo_2
│   └── README.md
└── README.md
NOTE : si les sous-modules ont aussi des sous-modules, il faut ajouter l'option --recursive.

Push

Nous allons maintenant effectuer une modification dans depo_2 puis faire un push dans le sous-module.

État initial du depo_2 :

Nous allons :

  • créer une nouvelle branche feature dans depo_2 et se placer sur celle-ci ;
  • ajouter les modifications avec git add ;
  • faire un commit ;
  • et enfin effectuer un push sur depo_2.
NOTE : pour cette dernière opération, il faut spécifier --set-upstream origin feature lors du premier push sur cette branche, afin de spécifier la branche remote qui sera rattachée à notre branche locale.
~/depo_1/depo_2 depo_2@(HEAD detached at 2e8bd03) $ git checkout -b feature
Switched to branch 'feature'

~/depo_1/depo_2 --> depo_2.git@feature $ git add *

~/depo_1/depo_2 --> depo_2.git@feature $ git commit -m "Modif on feature"
[feature 0b0b9f2] Modif on feature

~/depo_1/depo_2 --> depo_2.git@feature $ git push --set-upstream origin feature

Nous avons effectué les modifications dans depo_2 en local et remote.

Mais qu'en est-il pour depo_1 ?

~/depo_1 --> depo_1.git@main $ git status
On branch main
Your branch is up to date with 'origin/main'.

Changes not staged for commit:
        modified:   depo_2 (new commits)

Comme vous pouvez le voir dans le résultat de la commande git status ci-dessus, depo_1 contient des modifications relatives au sous-module depo_2.

Le graphique suivant représente l'historique de depo_2 avec des tags qui indiquent où depo_1 pointe en local et remote.

sous-module depo_2 et référence de depo_1 :

À ce stade, depo_1 ne dispose pas encore de nos modifications en remote. Si quelqu'un d'autre le récupère, le sous-module pointera sur le tag depo_1_remote.

Pour partager nos modifications dans depo_1, il faut simplement faire un push comme si nous avions modifié un fichier :

~/depo_1 --> depo_1.git@main $ git add depo_2/
~/depo_1 --> depo_1.git@main $ git commit -m "Update submodule"
~/depo_1 --> depo_1.git@main $ git push

sous-module depo_2 :

ATTENTION : si vous clonez le dépôt principal, le sous-module ne sera pas sur une branche, seulement sur une révision "sans tête" (headless).

Pull

C'est un peu pareil pour faire un pull.

Supposons que nous soyons sur le premier commit de depot_1 et que nous voulions récupérer les modifications faites dans le paragraphe précédent.

sous-module depo_2 :

Comme d'habitude, commençons par faire un fetch et un pull.

~/depo_1_2/depo_1 --> depo_1.git@main $ git fetch
From github.com:Younup/depo_1
   d698556..8076465  main       -> origin/main
Fetching submodule sub/depo_2
From github.com:Younup/depo_2
   0b0b9f2..81a3d90  feature    -> origin/feature

~/depo_1_2/depo_1 --> depo_1.git@main $ git pull
Updating d698556..8076465
Fast-forward
 sub/depo_2 | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

Seulement, nous constatons, une fois de plus, que les fichiers du sous-module n'ont pas été mis à jour.

Et, comme dans les cas précédents, cela sera résolu en appelant git submodule.

~/depo_1_2/depo_1 --> depo_1.git@main $ git submodule update
Submodule path 'sub/depo_2': checked out '81a3d90051dfe142f29a746730be82403b304d53'

sous-module depo_2 :

Workflow avec les sous-modules

Voyons maintenant comment gérer les conflits avec les sous-modules.

Imaginons que deux développeurs aient chacun modifié le sous-module depo_2 depuis depo_1, chacun dans une branche différente :

Sur le graphique précédent, le depo_1 du premier développeur pointe sur le commit main_1 du depo_2. De même le depo_1 du deuxième développeur pointe sur le commit main_2 du depo_2.

Dans cette situation, git ne vous laissera pas faire de merge ou de rebase directement depuis depo_1.

C'est là une différence par rapport au merge depuis le même dépôt. Puisque le sous-module pointe uniquement vers une révision et pas vers une branche, il n'est pas possible de résoudre les conflits depuis depo_1.

Mais il y a, bien sûr, une solution assez simple : il suffit de faire le merge ou rebase depuis depo_2 vers sa branche main, puis de faire pointer depo_1 vers le commit ainsi créé.

Sous-modules ou gestionnaires de paquets

REMARQUE Ce paragraphe est teinté de mon expérience en tant que développeur C/C++.

Tout cela peut paraître un peu complexe si vous avez l'habitude de travailler avec un langage qui incorpore un gestionnaire de paquets :

  • Java utilise Maven ou Gradle,
  • Python utilise Pip et
  • Rust utilise Cargo.

Cependant il y a une différence entre les deux, c'est qu'un manageur de paquet gère des binaires alors qu'un sous module git gère du code.

Choisir l'un ou l'autre va dépendre de vos préférences et des contraintes spécifiques au projet. Il n'y a donc pas de règles strictes.

Dépendances internes ou externes

Si vous n'êtes pas le propriétaire de la librairie et que vous ne prévoyez pas d'y ajouter des fonctionnalités, préférez un gestionnaire de paquet.

Hébergement des paquets

Un gestionnaire de paquet a besoin d'un service d'hébergement de paquet pour pouvoir fonctionner.

Il en existe des publiques mais ce n'est peut-être pas souhaitable pour votre projet (confidentialité, propriété, licence, …).

On n'y pense pas forcément mais configurer et maintenir un service d'hébergement de paquets privé cela représente du travail (infrastructure, maintenance, intégration continue, …). Pour une petite équipe, ce n'est pas toujours pertinent et se satisfaire uniquement de git peut être préférable.

Besoin du code ou cycles de release liés

Si vous avez besoin du code d'une dépendance (consultation, test, debug, instrumentation, apporter des modifications, …) alors préférez les sous-modules.

De même si vous sortez une nouvelle version de votre librairie pour chaque release de votre projet principal, cela signifie que les deux sont liés. Dans ce cas les sous-modules sont probablement plus adaptés. Ils vous permettront, par exemple, de modifier les deux simultanément.

Aide à la prise de décision

Si vous hésitez toujours, voici un résumé pour vous aider :

Conclusion

En conclusion, les sous-modules git offrent une solution efficace pour gérer des dépendances de code.

Bien que cette approche puisse paraître moins flexible qu'un gestionnaire de paquets, elle présente des avantages dans certains contextes, notamment pour gérer du code source plutôt que des binaires et lorsque les cycles de développement entre le projet principal et ses dépendances sont étroitement liés.

Toutefois, leur utilisation nécessite une bonne maîtrise des commandes git.

Choisir entre sous-modules et gestionnaires de paquets dépend donc des besoins spécifiques du projet : confidentialité, infrastructure, nécessité d'accéder au code, ou simple besoin de gestion de binaires. Il n'y a pas de réponse unique, et chaque équipe devra évaluer les avantages et inconvénients en fonction de ses contraintes.

Rien ne vous empêche, aussi, d'utiliser les deux simultanément, pour pouvoir profiter des avantages de chacun.

Sous-modules pour les librairies internes et gestionnaires de paquet pour les libraires externes ?

Annexes

Liens

Documentation de git sur les sous-modules

Afficher le nom de votre dépôt git et sa branche dans votre prompt

Dans votre .bashrc (ou équivalent), remplacez la définition de la variable PS1 par :

parse_git_branch() {
   git branch 2> /dev/null | sed -e '/^[^*]/d' -e 's/* \(.*\)/\1/'
}
get_git_repo_name() {
   basename $(git remote get-url origin 2>/dev/null) 2>/dev/null
}

PS1='${debian_chroot:+($debian_chroot)}\u@\h:\w --> $(get_git_repo_name)@$(parse_git_branch) \$ '

Ici nous affichons : dossier --> depo@branche $

Mermaid

Les diagrammes ont été faits avec Mermaid.

No items found.
ça t’a plu ?
Partage ce contenu
Hugo

Hugo est un éternel idéaliste.

Il rêve du jour où les humains travailleront en harmonie avec les machines.

En attendant, il programme en C/C++ sur système embarqué et quand il lui reste du temps, il dessine ou joue aux jeux vidéo.

Et au cas où les machines se rebelleraient, il apprend le MMA, on ne sait jamais…