Définition
Un sous-module (submodule) dans Git est un dépôt Git imbriqué à l'intérieur d'un autre dépôt Git. Les sous-modules permettent d'intégrer et de gérer un dépôt Git distinct comme une partie d'un autre dépôt. Cela est particulièrement utile lorsque vous souhaitez inclure un projet externe dans un autre projet, tout en conservant les deux projets séparés.
Principe de fonctionnement
- Ajout d'un sous-module : Vous pouvez ajouter un sous-module à un dépôt Git en utilisant la commande git submodule add URL_du_dépôt. Cela crée un répertoire dans votre dépôt principal qui pointe vers un autre dépôt Git.
- Synchronisation des sous-modules : Lorsque vous clonez un dépôt contenant des sous-modules, les sous-modules ne sont pas automatiquement clonés. Vous devez utiliser la commande git submodule init pour initialiser les sous-modules, puis git submodule update pour les mettre à jour ou les cloner.
- Gestion des versions : Les sous-modules sont liés à une révision spécifique du dépôt imbriqué. Vous pouvez donc contrôler quelle version du sous-module est utilisée dans votre projet.
Utilisation des sous-modules
Les sous-modules sont particulièrement utiles dans les cas où vous travaillez sur des projets complexes dépendant d'autres projets, ou lorsque vous voulez partager un ensemble de bibliothèques ou de composantes entre différents projets sans les dupliquer.
Mise en situation
Il arrive souvent que lorsque vous travaillez sur un projet, vous ayez besoin d'utiliser un autre projet à partir de celui-ci. Il peut s'agir d'une bibliothèque développée par un tiers ou que vous développez séparément et que vous utilisez dans plusieurs projets parents. Un problème courant se pose dans ces scénarios : vous souhaitez pouvoir traiter les deux projets comme des projets distincts tout en étant en mesure d'utiliser l'un à partir de l'autre.
Voici un exemple. Supposons que vous développiez un site Web et que vous créiez des flux Atom. Au lieu d'écrire votre propre code générateur d'Atom, vous décidez d'utiliser une bibliothèque. Vous devrez probablement inclure ce code à partir d'une bibliothèque partagée comme une installation CPAN ou un gem de Ruby, ou copier le code source dans votre propre arborescence de projet. Le problème avec l'inclusion de la bibliothèque est qu'il est difficile de personnaliser la bibliothèque de quelque manière que ce soit et souvent plus difficile de la déployer, car vous devez vous assurer que chaque client dispose de cette bibliothèque. Le problème avec la copie du code dans votre propre projet est que toutes les modifications personnalisées que vous apportez sont difficiles à fusionner lorsque les modifications en amont deviennent disponibles.
Git résout ce problème à l'aide de sous-modules (submodule). Les sous-modules vous permettent de conserver un dépôt Git en tant que sous-répertoire d'un autre dépôt Git. Cela vous permet de cloner un autre dépôt dans votre projet et de conserver vos commits séparés.
Commencer par les sous-modules
Nous allons voir comment développer un projet simple ayant été divisé en un projet principal et quelques sous-projets.
Commençons par ajouter un dépôt Git existant en tant que sous-module du dépôt sur lequel nous travaillons. Pour ajouter un nouveau sous-module, utilisez la commande git submodule add avec l'URL absolue ou relative du projet que vous souhaitez commencer à suivre. Dans cet exemple, nous allons ajouter une bibliothèque appelée «MonSousModule» :
git submodule add https://github.com/gladir/MonSousModule |
on obtiendra un résultat ressemblant à ceci :
Cloning into 'MonSousModule'... remote: Counting objects: 11, done. remote: Compressing objects: 100% (10/10), done. remote: Total 11 (delta 0), reused 11 (delta 0) Unpacking objects: 100% (11/11), done. Checking connectivity... done. |
Par défaut, les sous-modules ajouteront le sous-projet dans un répertoire portant le même nom que le dépôt, dans ce cas «MonSousModule». Vous pouvez ajouter un chemin différent à la fin de la commande si vous souhaitez qu'il aille ailleurs.
Si vous exécutez git status à ce stade, vous remarquerez quelques éléments :
git status |
on obtiendra un résultat ressemblant à ceci :
On branch master Your branch is up-to-date with 'origin/master'. Changes to be committed: (use "git reset HEAD <file>..." to unstage) new file: .gitmodules new file: MonSousModule |
Vous devriez d'abord remarquer le nouveau fichier .gitmodules. Il s'agit d'un fichier de configuration entreposant la cartographie entre l'URL du projet et le sous-répertoire local dans lequel vous l'avez extrait :
[submodule "MonSousModule"] path = MonSousModule url = https://github.com/gladir/MonSousModule |
Remarque : Étant donné que l'URL du fichier .gitmodules est celle que les autres personnes essaieront d'abord de cloner/récupérer, assurez-vous d'utiliser une URL à laquelle elles peuvent accéder si possible. Par exemple, si vous utilisez une URL différente de celle à laquelle les autres personnes auraient accès pour effectuer le push, utilisez celle à laquelle les autres ont accès. Vous pouvez remplacer cette valeur localement avec git config submodule.MonSousModule.url PRIVATE_URL pour votre propre usage. Le cas échéant, une URL relative peut être utile.
L'autre liste dans la sortie de git status est l'entrée du dossier du projet. Si vous exécutez git diff dessus, vous voyez quelque chose d'intéressant :
git diff --cached MonSousModule |
on obtiendra un résultat ressemblant à ceci :
diff --git a/MonSousModule b/MonSousModule new file mode 160000 index 0000000..c3f01dc --- /dev/null +++ b/MonSousModule @@ -0,0 +1 @@ +Subproject commit cbf01dc8862123d317dd47284b05b6892c7b29bc |
Bien que MonSousModule soit un sous-répertoire de votre répertoire de travail, Git le considère comme un sous-module et ne suit pas son contenu lorsque vous n'êtes pas dans ce répertoire. Au lieu de cela, Git le considère comme un commit particulier de ce dépôt.
Si vous souhaitez une sortie de diff un peu plus agréable, vous pouvez passer l'option --submodule à git diff :
git diff --cached --submodule |
on obtiendra un résultat ressemblant à ceci :
diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..71fc376 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "MonSousModule"] + path = MonSousModule + url = https://github.com/gladir/MonSousModule Submodule MonSousModule 0000000...c3f01dc (new submodule) |
Lorsque vous vous engagez, vous voyez quelque chose comme ceci :
git commit -am 'Add MonSousModule module' |
on obtiendra un résultat ressemblant à ceci :
[master fb9093c] Add MonSousModule module 2 files changed, 4 insertions(+) create mode 100644 .gitmodules create mode 160000 MonSousModule |
Notez le mode 160000 pour l'entrée MonSousModule. Il s'agit d'un mode spécial dans Git signifiant essentiellement que vous enregistrez un commit en tant qu'entrée de répertoire plutôt qu'en tant que sous-répertoire ou fichier.
Enfin, appliquez ces modifications :
git push origin master |
Cloner un projet avec des sous-modules
Nous allons ici cloner un projet contenant un sous-module. Lorsque vous clonez un tel projet, vous obtenez par défaut les répertoires contenant les sous-modules, mais aucun des fichiers qu'ils contiennent encore :
git clone https://github.com/gladir/MainProject |
on obtiendra un résultat ressemblant à ceci :
Cloning into 'MainProject'... remote: Counting objects: 14, done. remote: Compressing objects: 100% (13/13), done. remote: Total 14 (delta 1), reused 13 (delta 0) Unpacking objects: 100% (14/14), done. Checking connectivity... done. |
Ensuite, vous allez voir dans le projet principal :
cd MainProject ls -la |
on obtiendra un résultat ressemblant à ceci :
total 16 drwxr-xr-x 9 schacon staff 306 Sep 17 15:21 . drwxr-xr-x 7 schacon staff 238 Sep 17 15:21 .. drwxr-xr-x 13 schacon staff 442 Sep 17 15:21 .git -rw-r--r-- 1 schacon staff 92 Sep 17 15:21 .gitmodules drwxr-xr-x 2 schacon staff 68 Sep 17 15:21 MonSousModule -rw-r--r-- 1 schacon staff 756 Sep 17 15:21 Makefile drwxr-xr-x 3 schacon staff 102 Sep 17 15:21 includes drwxr-xr-x 4 schacon staff 136 Sep 17 15:21 scripts drwxr-xr-x 4 schacon staff 136 Sep 17 15:21 src |
Ensuite, vous allez voir dans le projet MonSousModule :
cd MonSousModule/ ls |
Le répertoire MonSousModule est présent, mais vide. Vous devez exécuter deux commandes : git submodule init pour initialiser votre fichier de configuration local et git submodule update pour récupérer toutes les données de ce projet et extraire le commit approprié répertorié dans votre super projet :
git submodule init |
on obtiendra un résultat ressemblant à ceci :
Submodule 'MonSousModule' (https://github.com/gladir/MonSousModule) registered for path 'MonSousModule' |
Ensuite, vous le mettez à jour :
git submodule update |
on obtiendra un résultat ressemblant à ceci :
Cloning into 'MonSousModule'... remote: Counting objects: 11, done. remote: Compressing objects: 100% (10/10), done. remote: Total 11 (delta 0), reused 11 (delta 0) Unpacking objects: 100% (11/11), done. Checking connectivity... done. Submodule path 'MonSousModule': checked out 'c3f01dc8872123d317dd46284b05b7892c7b29bc' |
Votre sous-répertoire MonSousModule est désormais dans l'état exact où il se trouvait lorsque vous avez effectué votre commit plus tôt.
Il existe cependant une autre façon de procéder, un peu plus simple. Si vous passez --recurse-submodules à la commande git clone, elle initialisera et mettra automatiquement à jour chaque sous-module du dépôt, y compris les sous-modules imbriqués si l'un des sous-modules du dépôt en possède lui-même :
git clone --recurse-submodules https://github.com/gladir/MainProject |
on obtiendra un résultat ressemblant à ceci :
Cloning into 'MainProject'... remote: Counting objects: 14, done. remote: Compressing objects: 100% (13/13), done. remote: Total 14 (delta 1), reused 13 (delta 0) Unpacking objects: 100% (14/14), done. Checking connectivity... done. Submodule 'MonSousModule' (https://github.com/gladir/MonSousModule) registered for path 'MonSousModule' Cloning into 'MonSousModule'... remote: Counting objects: 11, done. remote: Compressing objects: 100% (10/10), done. remote: Total 11 (delta 0), reused 11 (delta 0) Unpacking objects: 100% (11/11), done. Checking connectivity... done. Submodule path 'MonSousModule': checked out 'c3f01dc8862123d317dd47284b05b6892c7b29bc' |
Si vous avez déjà cloné le projet et oublié --recurse-submodules, vous pouvez combiner les étapes git submodule init et git submodule update en exécutant git submodule update --init. Pour initialiser, récupérer et extraire également tous les sous-modules imbriqués, vous pouvez utiliser la méthode infaillible git submodule update --init --recursive.