Git 🌙
Chapters â–Ÿ 2nd Edition

7.11 Utilitaires Git - Sous-modules

Sous-modules

Il arrive souvent lorsque vous travaillez sur un projet que vous deviez utiliser un autre projet comme dĂ©pendance. Cela peut ĂȘtre une bibliothĂšque qui est dĂ©veloppĂ©e par une autre Ă©quipe ou que vous dĂ©veloppez sĂ©parĂ©ment pour l’utiliser dans plusieurs projets parents. Ce scĂ©nario provoque un problĂšme habituel : vous voulez ĂȘtre capable de gĂ©rer deux projets sĂ©parĂ©s tout en utilisant l’un dans l’autre.

Voici un exemple. Supposons que vous dĂ©veloppez un site web et que vous crĂ©ez des flux Atom. PlutĂŽt que d’écrire votre propre code de gĂ©nĂ©ration Atom, vous dĂ©cidez d’utiliser une bibliothĂšque. Vous allez vraisemblablement devoir soit inclure ce code depuis un gestionnaire partagĂ© comme CPAN ou Ruby gem, soit copier le code source dans votre propre arborescence de projet. Le problĂšme d’inclure la bibliothĂšque en tant que bibliothĂšque externe est qu’il est difficile de la personnaliser de quelque maniĂšre que ce soit et encore plus de la dĂ©ployer, car vous devez vous assurer de la disponibilitĂ© de la bibliothĂšque chez chaque client. Mais le problĂšme d’inclure le code dans votre propre projet est que n’importe quelle personnalisation que vous faites est difficile Ă  fusionner lorsque les modifications du dĂ©veloppement principal arrivent.

Git gĂšre ce problĂšme avec les sous-modules. Les sous-modules vous permettent de gĂ©rer un dĂ©pĂŽt Git comme un sous-rĂ©pertoire d’un autre dĂ©pĂŽt Git. Cela vous laisse la possibilitĂ© de cloner un dĂ©pĂŽt dans votre projet et de garder isolĂ©s les commits de ce dĂ©pĂŽt.

DĂ©marrer un sous-module

DĂ©taillons le dĂ©veloppement d’un projet simple qui a Ă©tĂ© divisĂ© en un projet principal et quelques sous-projets.

Commençons par ajouter le dĂ©pĂŽt d’un projet Git existant comme sous-module d’un dĂ©pĂŽt sur lequel nous travaillons. Pour ajouter un nouveau sous-module, nous utilisons la commande git submodule add avec l’URL du projet que nous souhaitons suivre. Dans cette exemple, nous ajoutons une bibliothĂšque nommĂ©e « DbConnector ».

$ git submodule add https://github.com/chaconinc/DbConnector
Clonage dans 'DbConnector'...
remote: Counting objects: 11, done.
remote: Compressing objects: 100% (10/10), done.
remote: Total 11 (delta 0), reused 11 (delta 0)
DĂ©paquetage des objets: 100% (11/11), fait.
Vérification de la connectivité... fait.

Par dĂ©faut, les sous-modules ajoutent le sous-projet dans un rĂ©pertoire portant le mĂȘme nom que le dĂ©pĂŽt, dans notre cas « DbConnector ». Vous pouvez ajouter un chemin diffĂ©rent Ă  la fin de la commande si vous souhaitez le placer ailleurs.

Si vous lancez git status à ce moment, vous noterez quelques différences.

$ git status
Sur la branche master
Votre branche est Ă  jour avec 'origin/master'.

Modifications qui seront validées :
  (utilisez "git reset <fichier>..." pour désindexer)

	nouveau fichier :   .gitmodules
	nouveau fichier :   DbConnector

PremiĂšrement, un fichier .gitmodules vient d’apparaĂźtre. C’est le fichier de configuration qui stocke la liaison entre l’URL du projet et le sous-rĂ©pertoire local dans lequel vous l’avez tirĂ©.

$ cat .gitmodules
[submodule "DbConnector"]
	path = DbConnector
	url = https://github.com/chaconinc/DbConnector

Si vous avez plusieurs sous-modules, vous aurez plusieurs entrĂ©es dans ce fichier. Il est important de noter que ce fichier est en gestion de version comme vos autres fichiers, Ă  l’instar de votre fichier .gitignore. Il est poussĂ© et tirĂ© comme le reste de votre projet. C’est Ă©galement le moyen que les autres personnes qui clonent votre projet ont de savoir oĂč rĂ©cupĂ©rer le projet du sous-module.

Note

Comme l’URL dans le fichier .gitmodules est ce que les autres personnes essaieront en premier de cloner et de tirer, assurez-vous que cette URL est effectivement accessible par les personnes concernĂ©es. Par exemple, si vous utilisez une URL diffĂ©rente pour pousser que celle que les autres utiliseront pour tirer, utilisez l’URL Ă  laquelle les autres ont accĂšs. Vous pouvez surcharger cette URL localement pour votre usage propre avec la commande git config submodule.DbConnector.url PRIVATE_URL.

L’autre information dans la sortie de git status est l’entrĂ©e du rĂ©pertoire du projet. Si vous exĂ©cutez git diff, vous verrez quelque chose d’intĂ©ressant :

$ git diff --cached DbConnector
diff --git a/DbConnector b/DbConnector
new file mode 160000
index 0000000..c3f01dc
--- /dev/null
+++ b/DbConnector
@@ -0,0 +1 @@
+Subproject commit c3f01dc8862123d317dd46284b05b6892c7b29bc

MĂȘme si DbConnector est un sous-rĂ©pertoire de votre rĂ©pertoire de travail, Git le voit comme un sous-module et ne suit pas son contenu (si vous n’ĂȘtes pas dans ce rĂ©pertoire). En Ă©change, Git l’enregistre comme un commit particulier de ce dĂ©pĂŽt.

Si vous souhaitez une sortie diff plus agrĂ©able, vous pouvez passer l’option --submodule Ă  git diff.

$ git diff --cached --submodule
diff --git a/.gitmodules b/.gitmodules
new file mode 100644
index 0000000..71fc376
--- /dev/null
+++ b/.gitmodules
@@ -0,0 +1,3 @@
+[submodule "DbConnector"]
+       path = DbConnector
+       url = https://github.com/chaconinc/DbConnector
Submodule DbConnector 0000000...c3f01dc (new submodule)

Au moment de valider, vous voyez quelque chose comme :

$ git commit -am 'added DbConnector module'
[master fb9093c] added DbConnector module
 2 files changed, 4 insertions(+)
 create mode 100644 .gitmodules
 create mode 160000 DbConnector

Remarquez le mode 160000 pour l’entrĂ©e DbConnector. C’est un mode spĂ©cial de Git qui signifie globalement que vous ĂȘtes en train d’enregistrer un commit comme un rĂ©pertoire plutĂŽt qu’un sous-rĂ©pertoire ou un fichier.

Enfin, poussez ces modifications :

$ git push origin master

Cloner un projet avec des sous-modules

Maintenant, vous allez apprendre à cloner un projet contenant des sous-modules. Quand vous récupérez un tel projet, vous obtenez les différents répertoires qui contiennent les sous-modules, mais encore aucun des fichiers :

$ git clone https://github.com/chaconinc/MainProject
Clonage dans 'MainProject'...
remote: Counting objects: 14, done.
remote: Compressing objects: 100% (13/13), done.
remote: Total 14 (delta 1), reused 13 (delta 0)
DĂ©paquetage des objets: 100% (14/14), fait.
Vérification de la connectivité... fait.
$ cd MainProject
$ ls -la
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 DbConnector
-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
$ cd DbConnector/
$ ls
$

Le répertoire DbConnector est présent mais vide. Vous devez exécuter deux commandes : git submodule init pour initialiser votre fichier local de configuration, et git submodule update pour tirer toutes les données de ce projet et récupérer le commit approprié tel que listé dans votre super-projet :

$ git submodule init
Sous-module 'DbConnector' (https://github.com/chaconinc/DbConnector) enregistré pour le chemin 'DbConnector'
$ git submodule update
Clonage dans 'DbConnector'...
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 'DbConnector': checked out 'c3f01dc8862123d317dd46284b05b6892c7b29bc'

Votre rĂ©pertoire DbConnector est maintenant dans l’état exact dans lequel il Ă©tait la derniĂšre fois que vous avez validĂ©.

Il existe une autre maniĂšre plus simple d’arriver au mĂȘme rĂ©sultat. Si vous passez l’option --recurse-submodules Ă  la commande git clone, celle-ci initialisera et mettra Ă  jour automatiquement chaque sous-module du dĂ©pĂŽt.

$ git clone --recurse-submodules https://github.com/chaconinc/MainProject
Clonage dans 'MainProject'...
remote: Counting objects: 14, done.
remote: Compressing objects: 100% (13/13), done.
remote: Total 14 (delta 1), reused 13 (delta 0)
DĂ©paquetage des objets: 100% (14/14), fait.
Vérification de la connectivité... fait.
Submodule 'DbConnector' (https://github.com/chaconinc/DbConnector) registered for path 'DbConnector'
Clonage dans 'DbConnector'...
remote: Counting objects: 11, done.
remote: Compressing objects: 100% (10/10), done.
remote: Total 11 (delta 0), reused 11 (delta 0)
DĂ©paquetage des objets: 100% (11/11), fait.
Vérification de la connectivité... fait.
chemin du sous-module 'DbConnector' : 'c3f01dc8862123d317dd46284b05b6892c7b29bc' extrait

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 lançant git submodule update --init. Pour initialiser, récupérer et extraire aussi tous les sous-modules récursivement, vous pouvez utiliser la commande complÚte git submodule update --init --recursive.

Travailler sur un projet comprenant des sous-modules

Nous avons Ă  prĂ©sent une copie d’un projet comprenant des sous-modules, et nous allons collaborer Ă  la fois sur le projet principal et sur le projet du sous-module.

Tirer des modifications amont

Le modùle le plus simple d’utilisation des sous-modules est le cas de la simple consommation d’un sous-projet duquel on souhaite obtenir les mises à jour de temps en temps mais auquel on n’apporte pas de modification dans la copie de travail. Examinons un exemple simple.

Quand vous souhaitez vérifier si le sous-module a évolué, vous pouvez vous rendre dans le répertoire correspondant et lancer git fetch puis git merge de la branche amont pour mettre à jour votre code local.

$ git fetch
From https://github.com/chaconinc/DbConnector
   c3f01dc..d0354fc  master     -> origin/master
$ git merge origin/master
Mise Ă  jour c3f01dc..d0354fc
Avance rapide
 scripts/connect.sh | 1 +
 src/db.c           | 1 +
 2 files changed, 2 insertions(+)

Si vous revenez maintenant dans le projet principal et lancez git diff --submodule, vous pouvez remarquer que le sous-module a été mis à jour et vous pouvez obtenir une liste des commits qui y ont été ajoutés. Si vous ne voulez pas taper --submodule à chaque fois que vous lancez git diff, vous pouvez le régler comme format par défaut en positionnant le paramÚtre de configuration diff.submodule à la valeur « log ».

$ git config --global diff.submodule log
$ git diff
Submodule DbConnector c3f01dc..d0354fc:
  > more efficient db routine
  > better connection routine

Si vous validez à ce moment, vous fixez la version du sous-module à la version actuelle quand d’autres personnes mettront à jour votre projet.

Il existe aussi un moyen plus facile, si vous préférez ne pas avoir à récupérer et fusionner manuellement les modifications dans le sous-répertoire. Si vous lancez la commande git submodule update --remote, Git se rendra dans vos sous-modules et réalisera automatiquement le fetch et le merge.

$ git submodule update --remote DbConnector
remote: Counting objects: 4, done.
remote: Compressing objects: 100% (2/2), done.
remote: Total 4 (delta 2), reused 4 (delta 2)
DĂ©paquetage des objets: 100% (4/4), fait.
Depuis https://github.com/chaconinc/DbConnector
   3f19983..d0354fc  master     -> origin/master
chemin du sous-module 'DbConnector': checked out 'd0354fc054692d3906c85c3af05ddce39a1c0644' extrait

Cette commande considĂšre par dĂ©faut que vous souhaitez mettre Ă  jour la copie locale vers la branche master du dĂ©pĂŽt du sous-module. Vous pouvez, cependant, indiquer une autre branche. Par exemple, si le sous-module DbConnector suit la branche stable du dĂ©pĂŽt amont, vous pouvez l’indiquer soit dans votre fichier .gitmodules (pour que tout le monde le suive de mĂȘme) ou juste dans votre fichier local .git/config. Voyons ceci dans le cas du fichier .gitmodules :

$ git config -f .gitmodules submodule.DbConnector.branch stable

$ git submodule update --remote
remote: Counting objects: 4, done.
remote: Compressing objects: 100% (2/2), done.
remote: Total 4 (delta 2), reused 4 (delta 2)
DĂ©paquetage des objets: 100% (4/4), fait.
Depuis https://github.com/chaconinc/DbConnector
   27cf5d3..c87d55d  stable -> origin/stable
chemin du sous-module 'DbConnector' : 'c87d55d4c6d4b05ee34fbc8cb6f7bf4585ae6687' extrait

Si vous ne spĂ©cifiez pas la partie -f .gitmodules, la commande ne fera qu’une modification locale, mais il semble plus logique d’inclure cette information dans l’historique du projet pour que tout le monde soit au diapason.

Quand vous lancez git status, Git vous montrera que nous avons de nouveaux commits (« new commits ») pour le sous-module.

$ git status
Sur la branche master
Votre branche est Ă  jour avec 'origin/master'.

Modifications qui ne seront pas validées :
  (utilisez "git add <fichier>..." pour mettre à jour ce qui sera validé)
  (utilisez "git checkout -- <fichier>..." pour annuler les modifications dans la copie de travail)

  modifié :   .gitmodules
  modifié :   DbConnector (new commits)

aucune modification n'a été ajoutée à la validation (utilisez "git add" ou "git commit -a")

Si vous activez le paramÚtre de configuration status.submodulesummary, Git vous montrera aussi un résumé des modifications dans vos sous-modules :

$ git config status.submodulesummary 1

$ git status
Sur la branche master
Votre branche est Ă  jour avec 'origin/master'.

Modifications qui ne seront pas validées :
  (utilisez "git add <fichier>..." pour mettre à jour ce qui sera validé)
  (utilisez "git checkout -- <fichier>..." pour annuler les modifications dans la copie de travail)

	modifié :   .gitmodules
	modifié :   DbConnector (new commits)

Sous-modules modifiés mais non mis à jour :

* DbConnector c3f01dc...c87d55d (4):
  > catch non-null terminated lines

Ici, si vous lancez git diff, vous pouvez voir que le fichier .gitmodules a Ă©tĂ© modifiĂ© mais aussi qu’il y a un certain nombre de commits qui ont Ă©tĂ© tirĂ©s et sont prĂȘts Ă  ĂȘtre validĂ©s dans le projet du sous-module.

$ git diff
diff --git a/.gitmodules b/.gitmodules
index 6fc0b3d..fd1cc29 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -1,3 +1,4 @@
 [submodule "DbConnector"]
        path = DbConnector
        url = https://github.com/chaconinc/DbConnector
+       branch = stable
 Submodule DbConnector c3f01dc..c87d55d:
  > catch non-null terminated lines
  > more robust error handling
  > more efficient db routine
  > better connection routine

C’est une information intĂ©ressante car vous pouvez voir le journal des modifications que vous vous apprĂȘtez Ă  valider dans votre sous-module. Une fois validĂ©es, vous pouvez encore visualiser cette information en lançant git log -p.

$ git log -p --submodule
commit 0a24cfc121a8a3c118e0105ae4ae4c00281cf7ae
Author: Scott Chacon <schacon@gmail.com>
Date:   Wed Sep 17 16:37:02 2014 +0200

    updating DbConnector for bug fixes

diff --git a/.gitmodules b/.gitmodules
index 6fc0b3d..fd1cc29 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -1,3 +1,4 @@
 [submodule "DbConnector"]
        path = DbConnector
        url = https://github.com/chaconinc/DbConnector
+       branch = stable
Submodule DbConnector c3f01dc..c87d55d:
  > catch non-null terminated lines
  > more robust error handling
  > more efficient db routine
  > better connection routine

Par dĂ©faut, Git essaiera de mettre Ă  jour tous les sous-modules lors d’une commande git submodule update --remote, donc si vous avez de nombreux sous-modules, il est prĂ©fĂ©rable de spĂ©cifier le sous-module que vous souhaitez mettre Ă  jour.

Tirer des modifications amont depuis le serveur distant

Glissons-nous maintenant dans les habits d’un collaborateur, qui a son propre clone local du dĂ©pĂŽt ProjetPrincipal. Lancer simplement git pull pour obtenir les nouvelles modifications validĂ©es ne suffit plus :

$ git pull
From https://github.com/chaconinc/ProjetPrincipal
   fb9093c..0a24cfc  master     -> origin/master
Fetching submodule DbConnector
From https://github.com/chaconinc/DbConnector
   c3f01dc..c87d55d  stable     -> origin/stable
Updating fb9093c..0a24cfc
Fast-forward
 .gitmodules         | 2 +-
 DbConnector         | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

$ git status
 On branch master
Your branch is up-to-date with 'origin/master'.
Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

	modified:   DbConnector (new commits)

Submodules changed but not updated:

* DbConnector c87d55d...c3f01dc (4):
  < catch non-null terminated lines
  < more robust error handling
  < more efficient db routine
  < better connection routine

no changes added to commit (use "git add" and/or "git commit -a")

Par dĂ©faut, la commande git pull rĂ©cupĂšre les modifications des sous-modules rĂ©cursivement, comme nous pouvons le voir sur la premiĂšre ligne ci-dessus. Cependant, elle ne met pas les sous-modules Ă  jour. C’est affichĂ© Ă  la sortie de la commande git status, qui indique que le sous-module est « modified », et a des nouveaux commit (new commits). De plus, les chevrons indiquant les nouveaux commits pointent Ă  gauche (<), ce qui signifie que ces commits sont enregistrĂ©s dans ProjetPrincipal, mais ne sont pas prĂ©sents dans l’extraction locale de DbConnector. Pour finaliser la mise Ă  jour, vous devez lancer git submodule update :

$ git submodule update --init --recursive
Submodule path 'vendor/plugins/demo': checked out '48679c6302815f6c76f1fe30625d795d9e55fc56'

$ git status
 On branch master
Your branch is up-to-date with 'origin/master'.
nothing to commit, working tree clean

Notez que pour rester en sĂ©curitĂ©, vous devriez lancer git submodule update avec le drapeau --init au cas oĂč les commits de ProjetPrincipal que vous venez de tirer ont ajoutĂ© des nouveaux sous-modules, et le drapeau --recursive si certains sous-module ont des sous-modules imbriquĂ©s.

Si vous souhaitez automatiser ce processsus, vous pouvez ajouter le drapeau --recurse-submodules Ă  la commande git pull (depuis Git 2.14). Cela forcera Git Ă  lancer git submodule update juste aprĂšs le tirage, de maniĂšre Ă  mettre Ă  jour les sous-modules dans l’état correct. De plus, si vous voulez que Git tire toujours avec --recurse-submodules, vous pouvez rĂ©gler l’option de configuration submodule.recurse Ă  true (cela marche pour git pull depuis Git 2.15). Cette option forcera Git Ă  utiliser le drapeau --recurse-submodules pour toutes les commandes qui le supportent (Ă  part clone).

Il y a une situation spĂ©ciale qui peut arriver lors du tirage depuis le super-projet ; le dĂ©pĂŽt amont peut avoir modifiĂ© l’URL du sous-module dans le fichier .gitmodules dans un des commits que vous tirez. Cela peut arriver si le projet du sous-module change de plate-forme d’hĂ©bergement. Dans ce dernier cas, il est possible pour git pull --recurse-submodules, or git submodule update, d’échouer si le super-projet fait rĂ©fĂ©rence Ă  un commit d’un sous-module qui n’est pas trouvĂ© dans le serveur distant du sous-module configurĂ© localement dans votre dĂ©pĂŽt. Pour corriger cette situation, la commande git submodule sync est nĂ©cessaire :

# copier la nouvelle URL dans votre config locale
$ git submodule sync --recursive
# mettre Ă  jour le sous-module depuis la nouvelle URL
$ git submodule update --init --recursive

Travailler sur un sous-module

Il y a fort Ă  parier que si vous utilisez des sous-modules, vous le faites parce que vous souhaitez en rĂ©alitĂ© travailler sur le code du sous-module en mĂȘme temps que sur celui du projet principal (ou Ă  travers plusieurs sous-modules). Sinon, vous utiliseriez plutĂŽt un outil de gestion de dĂ©pendances plus simple (tel que Maven ou Rubygems).

De ce fait, dĂ©taillons un exemple de modifications rĂ©alisĂ©es dans le sous-module en mĂȘme temps que dans le projet principal et de validation et de publication des modifications dans le mĂȘme temps.

Jusqu’à maintenant, quand nous avons lancĂ© la commande git submodule update pour rĂ©cupĂ©rer les modifications depuis les dĂ©pĂŽts des sous-modules, Git rĂ©cupĂ©rait les modifications et mettait les fichiers locaux Ă  jour mais en laissant le sous-rĂ©pertoire dans un Ă©tat appelĂ© « HEAD dĂ©tachĂ©e ». Cela signifie qu’il n’y pas de branche locale de travail (comme master, par exemple) pour y valider les modifications. Donc, toutes les modifications que vous y faites ne sont pas suivies non plus.

Pour rendre votre sous-module plus adaptĂ© Ă  la modification, vous avez besoin de deux choses. Vous devez vous rendre dans chaque sous-module et extraire une branche de travail. Ensuite vous devez dire Ă  Git ce qu’il doit faire si vous avez rĂ©alisĂ© des modifications et que vous lancez git submodule update --remote pour tirer les modifications amont. Les options disponibles sont soit de les fusionner dans votre travail local, soit de tenter de rebaser le travail local par dessus les modifications distantes.

En premier, rendons-nous dans le répertoire de notre sous-module et extrayons une branche.

$ git checkout stable
Basculement sur la branche 'stable'

Attaquons-nous au choix de politique de gestion. Pour le spĂ©cifier manuellement, nous pouvons simplement ajouter l’option --merge Ă  l’appel de update. Nous voyons ici qu’une modification Ă©tait disponible sur le serveur pour ce sous-module et qu’elle a Ă©tĂ© fusionnĂ©e.

$ git submodule update --remote --merge
remote: Counting objects: 4, done.
remote: Compressing objects: 100% (2/2), done.
remote: Total 4 (delta 2), reused 4 (delta 2)
DĂ©paquetage des objets: 100% (4/4), fait.
Depuis https://github.com/chaconinc/DbConnector
   c87d55d..92c7337  stable     -> origin/stable
Mise Ă  jour de c87d55d..92c7337
Avance rapide
 src/main.c | 1 +
 1 file changed, 1 insertion(+)
chemin du sous-module 'DbConnector': fusionné dans '92c7337b30ef9e0893e758dac2459d07362ab5ea'

Si nous nous rendons dans le rĂ©pertoire DbConnector, les nouvelles modifications sont dĂ©jĂ  fusionnĂ©es dans notre branche locale stable. Voyons maintenant ce qui arrive si nous modifions localement la bibliothĂšque et que quelqu’un pousse une autre modification en amont dans le mĂȘme temps.

$ cd DbConnector/
$ vim src/db.c
$ git commit -am 'unicode support'
[stable f906e16] unicode support
 1 file changed, 1 insertion(+)

Maintenant, si nous mettons à jour notre sous-module, nous pouvons voir ce qui arrive lors d’un rebasage de deux modifications concurrentes.

$ git submodule update --remote --rebase
PremiĂšrement, rembobinons head pour rejouer votre travail par-dessus...
Application : unicode support
chemin du sous-module 'DbConnector': rebasé dans '5d60ef9bbebf5a0c1c1050f242ceeb54ad58da94'

Si vous oubliez de spĂ©cifier --rebase ou --merge, Git mettra juste Ă  jour le sous-module vers ce qui est sur le serveur et rĂ©initialisera votre projet Ă  l’état « HEAD dĂ©tachĂ©e ».

$ git submodule update --remote
chemin du sous-module 'DbConnector' : '5d60ef9bbebf5a0c1c1050f242ceeb54ad58da94' extrait

Si cela arrive, ne vous inquiétez pas, vous pouvez simplement revenir dans le répertoire et extraire votre branche (qui contiendra encore votre travail) et fusionner ou rebaser origin/stable (ou la branche distante que vous souhaitez) à la main.

Si vous n’avez pas validĂ© vos modifications dans votre sous-module, et que vous lancez une mise Ă  jour de sous-module qui causerait des erreurs, Git rĂ©cupĂ©rera les modifications mais n’écrasera pas le travail non validĂ© dans votre rĂ©pertoire de sous-module.

$ git submodule update --remote
remote: Counting objects: 4, done.
remote: Compressing objects: 100% (3/3), done.
remote: Total 4 (delta 0), reused 4 (delta 0)
DĂ©paquetage des objets: 100% (4/4), fait.
Depuis https://github.com/chaconinc/DbConnector
   5d60ef9..c75e92a  stable     -> origin/stable
error: Vos modifications locales seraient écrasées par checkout:
	scripts/setup.sh
Please, commit your changes or stash them before you can switch branches.
Aborting
Impossible d'extraire 'c75e92a2b3855c9e5b66f915308390d9db204aca' dans le chemin du sous-module 'DbConnector'

Si vous avez réalisé des modifications qui entrent en conflit avec des modifications amont, Git vous en informera quand vous mettrez à jour.

$ git submodule update --remote --merge
Auto-merging scripts/setup.sh
CONFLIT (contenu): Conflit de fusion dans scripts/setup.sh
La fusion automatique a échoué ; réglez les conflits et validez le résultat
Impossible de fusionner 'c75e92a2b3855c9e5b66f915308390d9db204aca' dans le chemin du sous-module 'DbConnector'

Vous pouvez vous rendre dans le répertoire du sous-module et résoudre le conflit normalement.

Publier les modifications dans un sous-module

Nous avons donc des modifications dans notre répertoire de sous-module, venant à la fois du dépÎt amont et de modifications locales non publiées.

$ git diff
Submodule DbConnector c87d55d..82d2ad3:
  > Merge from origin/stable
  > updated setup script
  > unicode support
  > remove unnessesary method
  > add new option for conn pooling

Si nous validons dans le projet principal et que nous le poussons en amont sans pousser les modifications des sous-modules, les autres personnes qui voudront essayer notre travail vont avoir de gros problĂšmes vu qu’elles n’auront aucun moyen de rĂ©cupĂ©rer les modifications des sous-modules qui en font partie. Ces modifications n’existent que dans notre copie locale.

Pour ĂȘtre sur que cela n’arrive pas, vous pouvez demander Ă  Git de vĂ©rifier que tous vos sous-modules ont Ă©tĂ© correctement poussĂ©s avant de pouvoir pousser le projet principal. La commande git push accepte un argument --recurse-submodules qui peut avoir pour valeur « check » ou « on-demand ». L’option « check » fera Ă©chouer push si au moins une des modifications des sous-modules n’a pas Ă©tĂ© poussĂ©e.

$ git push --recurse-submodules=check
The following submodule paths contain changes that can
not be found on any remote:
  DbConnector

Please try

	git push --recurse-submodules=on-demand

or cd to the path and use

	git push

to push them to a remote.

Comme vous pouvez le voir, il donne aussi quelques conseils utiles sur ce que nous pourrions vouloir faire ensuite. L’option simple consiste Ă  se rendre dans chaque sous-module et Ă  pousser manuellement sur les dĂ©pĂŽts distants pour s’assurer qu’ils sont disponibles publiquement, puis de rĂ©essayer de pousser le projet principal.

L’autre option consiste Ă  utiliser la valeur « on-demand » qui essaiera de faire tout ceci pour vous.

$ git push --recurse-submodules=on-demand
Pushing submodule 'DbConnector'
DĂ©compte des objets: 9, fait.
Delta compression using up to 8 threads.
Compression des objets: 100% (8/8), fait.
Écriture des objets: 100% (9/9), 917 bytes | 0 bytes/s, fait.
Total 9 (delta 3), reused 0 (delta 0)
To https://github.com/chaconinc/DbConnector
   c75e92a..82d2ad3  stable -> stable
DĂ©compte des objets: 2, fait.
Delta compression using up to 8 threads.
Compression des objets: 100% (2/2), fait.
Écriture des objets: 100% (2/2), 266 bytes | 0 bytes/s, fait.
Total 2 (delta 1), reused 0 (delta 0)
To https://github.com/chaconinc/ProjetPrincipal
   3d6d338..9a377d1  master -> master

Comme vous pouvez le voir, Git s’est rendu dans le module DbConnector et l’a poussĂ© avant de pousser le projet principal. Si la poussĂ©e du sous-module Ă©choue pour une raison quelconque, la poussĂ©e du projet principal sera annulĂ©e.

Fusion de modifications de sous-modules

Si vous changez la rĂ©fĂ©rence d’un sous-module en mĂȘme temps qu’une autre personne, il se peut que cela pose problĂšme. ParticuliĂšrement, si les historiques des sous-modules ont divergĂ© et sont appliquĂ©s Ă  des branches divergentes dans un super-projet, rapprocher toutes les modifications peut demander un peu de travail.

Si un des commits est un ancĂȘtre direct d’un autre (c’est-Ă -dire une fusion en avance rapide), alors Git choisira simplement ce dernier pour la fusion et cela se rĂ©soudra tout seul.

Cependant, Git ne tentera pas de fusion, mĂȘme trĂšs simple, pour vous. Si les commits d’un sous-module divergent et doivent ĂȘtre fusionnĂ©s, vous obtiendrez quelque chose qui ressemble Ă  ceci :

$ git pull
remote: Counting objects: 2, done.
remote: Compressing objects: 100% (1/1), done.
remote: Total 2 (delta 1), reused 2 (delta 1)
DĂ©paquetage des objets: 100% (2/2), fait.
From https://github.com/chaconinc/ProjetPrincipal
   9a377d1..eb974f8  master     -> origin/master
Fetching submodule DbConnector
warning: Failed to merge submodule DbConnector (merge following commits not found)
Fusion automatique de DbConnector
CONFLIT (sous-module): Conflit de fusion dans DbConnector
La fusion automatique a échoué ; réglez les conflits et validez le résultat.

Donc, ce qui s’est passĂ© en substance est que Git a dĂ©couvert que les deux points de fusion des branches Ă  fusionner dans l’historique du sous-module sont divergents et doivent ĂȘtre fusionnĂ©s. Il l’explique par « merge following commits not found » (fusion suivant les commits non trouvĂ©e), ce qui n’est pas clair mais que nous allons expliquer d’ici peu.

Pour rĂ©soudre le problĂšme, vous devez comprendre l’état dans lequel le sous-module devrait se trouver. Étrangement, Git ne vous donne pas d’information utile dans ce cas, pas mĂȘme les SHA-1 des commits des deux cĂŽtĂ©s de l’historique. Heureusement, c’est assez facile Ă  comprendre. Si vous lancez git diff, vous pouvez obtenir les SHA-1 des commits enregistrĂ©s dans chacune des branches que vous essayiez de fusionner.

$ git diff
diff --cc DbConnector
index eb41d76,c771610..0000000
--- a/DbConnector
+++ b/DbConnector

Donc, dans ce cas, eb41d76 est le commit dans notre sous-module que nous avions et c771610 est le commit amont. Si nous nous rendons dans le rĂ©pertoire du sous-module, il devrait dĂ©jĂ  ĂȘtre sur eb41d76 parce que la fusion ne l’a pas touchĂ©. S’il n’y est pas, vous pouvez simplement crĂ©er et extraire une branche qui pointe dessus.

Ce qui importe, c’est le SHA-1 du commit venant de l’autre branche. C’est ce que nous aurons Ă  fusionner. Vous pouvez soit essayer de fusionner avec le SHA-1 directement ou vous pouvez crĂ©er une branche Ă  partir du commit puis essayer de la fusionner. Nous suggĂ©rons d’utiliser cette derniĂšre mĂ©thode, ne serait-ce que pour obtenir un message de fusion plus parlant.

Donc, rendons-nous dans le répertoire du sous-module, créons une branche basée sur ce second SHA-1 obtenu avec git diff et fusionnons manuellement.

$ cd DbConnector

$ git rev-parse HEAD
eb41d764bccf88be77aced643c13a7fa86714135

$ git branch try-merge c771610

$ git merge try-merge
Auto-merging src/main.c
CONFLICT (content): Merge conflict in src/main.c
Recorded preimage for 'src/main.c'
Automatic merge failed; fix conflicts and then commit the result.

Nous avons eu un conflit de fusion ici, donc si nous le résolvons et validons, alors nous pouvons simplement mettre à jour le projet principal avec le résultat.

$ vim src/main.c (1)
$ git add src/main.c
$ git commit -am 'fusion de nos modifications'
[master 9fd905e] fusion de nos modifications

$ cd .. (2)
$ git diff (3)
diff --cc DbConnector
index eb41d76,c771610..0000000
--- a/DbConnector
+++ b/DbConnector
@@@ -1,1 -1,1 +1,1 @@@
- Subproject commit eb41d764bccf88be77aced643c13a7fa86714135
 -Subproject commit c77161012afbbe1f58b5053316ead08f4b7e6d1d
++Subproject commit 9fd905e5d7f45a0d4cbc43d1ee550f16a30e825a
$ git add DbConnector (4)

$ git commit -m "Fusion du travail de Tom" (5)
[master 10d2c60] Fusion du travail de Tom
  1. Nous résolvons le conflit

  2. Ensuite, nous retournons dans le projet principal

  3. Nous pouvons revérifier les SHA-1

  4. Nous rĂ©solvons l’entrĂ©e en conflit dans le sous-module

  5. Enfin, nous validons la résolution.

Cela peut paraĂźtre compliquĂ© mais ce n’est pas trĂšs difficile.

Curieusement, il existe un autre cas que Git gĂšre seul. Si un commit de fusion existe dans le rĂ©pertoire du sous-module qui contient les deux commits dans ses ancĂȘtres, Git va le suggĂ©rer comme solution possible. Il voit qu’à un certain point de l’historique du projet du sous-module, quelqu’un a fusionnĂ© les branches contenant ces deux commits, donc vous dĂ©sirerez peut-ĂȘtre utiliser celui-ci.

C’est pourquoi le message d’erreur prĂ©cĂ©dent s’intitulait « merge following commits not found », parce que justement, il ne pouvait pas trouver le commit de fusion. C’est dĂ©routant car qui s’attendrait Ă  ce qu’il essaie de le chercher ?

S’il trouve un seul commit de fusion acceptable, vous verrez ceci :

$ git merge origin/master
warning: Failed to merge submodule DbConnector (not fast-forward)
Found a possible merge resolution for the submodule:
 9fd905e5d7f45a0d4cbc43d1ee550f16a30e825a: > merged our changes
If this is correct simply add it to the index for example
by using:

  git update-index --cacheinfo 160000 9fd905e5d7f45a0d4cbc43d1ee550f16a30e825a "DbConnector"

which will accept this suggestion.
Fusion automatique de DbConnector
CONFLIT (submodule): Conflit de fusion dans DbConnector
La fusion automatique a échoué ; réglez les conflits et validez le résultat.

Ce qu’il suggĂšre de faire est de mettre Ă  jour l’index comme si vous aviez lancĂ© git add, ce qui Ă©limine le conflit, puis de valider. Vous ne devriez cependant pas le faire. Vous pouvez plus simplement vous rendre dans le rĂ©pertoire du sous-module, visualiser la diffĂ©rence, avancer en avance rapide sur le commit, le tester puis le valider.

$ cd DbConnector/
$ git merge 9fd905e
Mise Ă  jour eb41d76..9fd905e
Avance rapide

$ cd ..
$ git add DbConnector
$ git commit -am 'Avance rapide sur un fils commun dans le sous-module'

Cela revient au mĂȘme, mais de cette maniĂšre vous pouvez au moins vĂ©rifier que ça fonctionne et vous avez le code dans votre rĂ©pertoire de sous-module quand c’est terminĂ©.

Trucs et astuces pour les sous-modules

Il existe quelques commandes qui permettent de travailler plus facilement avec les sous-modules.

Submodule foreach

Il existe une commande submodule foreach qui permet de lancer une commande arbitraire dans chaque sous-module. C’est particuliĂšrement utile si vous avez plusieurs sous-modules dans le mĂȘme projet.

Par exemple, supposons que nous voulons développer une nouvelle fonctionnalité ou faire un correctif et que nous avons déjà du travail en cours dans plusieurs sous-modules. Nous pouvons facilement remiser tout le travail en cours dans tous les sous-modules.

$ git submodule foreach 'git stash'
Entering 'CryptoLibrary'
No local changes to save
Entering 'DbConnector'
Saved working directory and index state WIP on stable: 82d2ad3 Merge from origin/stable
HEAD is now at 82d2ad3 Merge from origin/stable

Ensuite, nous pouvons créer une nouvelle branche et y basculer dans tous nos sous-modules.

$ git submodule foreach 'git checkout -b featureA'
Entering 'CryptoLibrary'
Basculement sur la nouvelle branche 'featureA'
Entering 'DbConnector'
Basculement sur la nouvelle branche  'featureA'

Vous comprenez l’idĂ©e. Une commande vraiment utile permet de produire un joli diff unifiĂ© des modifications dans le projet principal ainsi que dans tous les sous-projets.

$ git diff; git submodule foreach 'git diff'
Submodule DbConnector contains modified content
diff --git a/src/main.c b/src/main.c
index 210f1ae..1f0acdc 100644
--- a/src/main.c
+++ b/src/main.c
@@ -245,6 +245,8 @@ static int handle_alias(int *argcp, const char ***argv)

      commit_pager_choice();

+     url = url_decode(url_orig);
+
      /* build alias_argv */
      alias_argv = xmalloc(sizeof(*alias_argv) * (argc + 1));
      alias_argv[0] = alias_string + 1;
Entering 'DbConnector'
diff --git a/src/db.c b/src/db.c
index 1aaefb6..5297645 100644
--- a/src/db.c
+++ b/src/db.c
@@ -93,6 +93,11 @@ char *url_decode_mem(const char *url, int len)
        return url_decode_internal(&url, len, NULL, &out, 0);
 }

+char *url_decode(const char *url)
+{
+       return url_decode_mem(url, strlen(url));
+}
+
 char *url_decode_parameter_name(const char **query)
 {
        struct strbuf out = STRBUF_INIT;

Ici, nous pouvons voir que nous dĂ©finissons une fonction dans un sous-module et que nous l’appelons dans le projet principal. C’est un exemple exagĂ©rĂ©ment simplifiĂ©, mais qui aide Ă  mieux comprendre l’utilitĂ© de cette commande.

Alias utiles

Vous pourriez ĂȘtre intĂ©ressĂ© de dĂ©finir quelques alias pour des commandes longues pour lesquelles vous ne pouvez pas rĂ©gler la configuration par dĂ©faut. Nous avons traitĂ© la dĂ©finition d’alias Git dans Les alias Git, mais voici un exemple d’alias que vous pourriez trouver utiles si vous voulez travailler sĂ©rieusement avec les sous-modules de Git.

$ git config alias.sdiff '!'"git diff && git submodule foreach 'git diff'"
$ git config alias.spush 'push --recurse-submodules=on-demand'
$ git config alias.supdate 'submodule update --remote --merge'

De cette maniÚre, vous pouvez simplement lancer git supdate lorsque vous souhaitez mettre à jour vos sous-module ou git spush pour pousser avec une gestion de dépendance de sous-modules.

Les problĂšmes avec les sous-modules

Cependant, utiliser des sous-modules ne se déroule pas sans accroc.

Commuter des branches

Commuter des branches qui contiennent des sous-modules peut Ă©galement s’avĂ©rer difficile. Si vous crĂ©ez une nouvelle branche, y ajoutez un sous-module, et revenez ensuite Ă  une branche dĂ©pourvue de ce sous-module, vous aurez toujours le rĂ©pertoire de ce sous-module comme un rĂ©pertoire non suivi :

$ git --version
git version 2.12.2

$ git checkout -b add-crypto
Basculement sur la nouvelle branche 'add-crypto'

$ git submodule add https://github.com/chaconinc/CryptoLibrary
Cloning into 'CryptoLibrary'...
...

$ git commit -am 'adding crypto library'
[add-crypto 4445836] adding crypto library
 2 files changed, 4 insertions(+)
 create mode 160000 CryptoLibrary

$ git checkout master
warning: unable to rmdir CryptoLibrary: Directory not empty
Basculement sur la branche 'master'
Votre branche est Ă  jour avec 'origin/master'.

$ git status
Sur la branche master
Votre branche est Ă  jour avec 'origin/master'.

Fichiers non suivis :
  (utilisez "git add <fichier>..." pour inclure dans ce qui sera validé)

	CryptoLibrary/

aucune modification ajoutée à la validation mais des fichiers non suivis sont présents (utilisez "git add" pour les suivre)

Supprimer le rĂ©pertoire n’est pas difficile, mais sa prĂ©sence est assez dĂ©routante. Si vous le supprimez puis que vous rebasculez sur la branche qui contient le sous-module, vous devrez lancer submodule update --init pour le rĂ©alimenter.

$ git clean -ffdx
Suppression de CryptoLibrary/

$ git checkout add-crypto
Basculement sur la branche 'add-crypto'

$ ls CryptoLibrary/

$ git submodule update --init
Submodule path 'CryptoLibrary': checked out 'b8dda6aa182ea4464f3f3264b11e0268545172af'

$ ls CryptoLibrary/
Makefile	includes	scripts		src

Une fois de plus, ce n’est pas rĂ©ellement difficile, mais cela peut ĂȘtre dĂ©routant.

Les nouvelles versions de Git (Git >= 2.13) simplifie tout ceci en ajoutant le drapeau --recurse-submodules Ă  la commande git checkout, qui s’occupe de placer les sous-modules dans le bon Ă©tat pour la branche sur laquelle nous commutons.

$ git --version
git version 2.13.3

$ git checkout -b add-crypto
Basculement sur la branche 'add-crypto'

$ git submodule add https://github.com/chaconinc/CryptoLibrary
Clonage dans 'CryptoLibrary'...
...

$ git commit -am 'Add crypto library'
[add-crypto 4445836] Add crypto library
 2 files changed, 4 insertions(+)
 create mode 160000 CryptoLibrary

$ git checkout --recurse-submodules master
Switched to branch 'master'
Your branch is up-to-date with 'origin/master'.

$ git status
On branch master
Your branch is up-to-date with 'origin/master'.

nothing to commit, working tree clean

Utiliser le drapeau --recurse-submodules de git checkout peut aussi ĂȘtre utile quand vous travaillez sur plusieurs branches dans les super-projet, chacune aillant votre sous-module pointant sur des commits diffĂ©rents. En fait, si vous commutez de branches qui enregistrent le sous-module Ă  diffĂ©rents commits, Ă  l’exĂ©cution de git status le sous-module apparaĂźtra comme « modified », et indique « new commits » (nouveaux commits). C’est parce que l’état du sous-module n’est pas gĂ©rĂ© par dĂ©faut lors du basculement de branches.

Cela peut ĂȘtre vraiment dĂ©routant, donc c’est toujours une bonne idĂ©e de toujours lancer git checkout --recurse-submodules quand votre projet a des sous-modules. Pour les versions anciennes de Git qui n’ont pas de drapeau --recurse-submodules, aprĂšs l’extraction, vous pouvez utiliser git submodule update --init --recursive pour placer les sous-modules dans le bon Ă©tat.

Par chance, vous pouvez indiquer Ă  Git (>=2.14) de toujours utiliser le drapeau --recurse-submodules en paramĂ©trant l’option de configuration submodule.recurse : git config submodule.recurse true. Comme notĂ© ci-dessus, cela forcera auss Git Ă  parcourir rĂ©cursivement les sous-modules pour toute commande qui accepte l’option --recurse-submodules (exceptĂ© git clone).

Basculer d’un sous-rĂ©pertoire Ă  un sous-module

Une autre difficultĂ© commune consiste Ă  basculer de sous-rĂ©pertoires en sous-modules. Si vous suiviez des fichiers dans votre projet et que vous voulez les dĂ©placer dans un sous-module, vous devez ĂȘtre trĂšs prudent ou Git sera inflexible. PrĂ©sumons que vous avez les fichiers dans un sous-rĂ©pertoire de votre projet, et que vous voulez les transformer en un sous-module. Si vous supprimez le sous-rĂ©pertoire et que vous exĂ©cutez submodule add, Git vous hurle dessus avec :

$ rm -Rf CryptoLibrary/
$ git submodule add https://github.com/chaconinc/CryptoLibrary
'CryptoLibrary' already exists in the index

Vous devez d’abord supprimer le rĂ©pertoire CryptoLibrary de l’index. Vous pourrez ensuite ajouter le sous-module :

$ git rm -r CryptoLibrary
$ git submodule add https://github.com/chaconinc/CryptoLibrary
Cloning into 'CryptoLibrary'...
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.

Maintenant, supposons que vous avez fait cela dans une branche. Si vous essayez de basculer dans une ancienne branche oĂč ces fichiers sont toujours dans l’arbre de projet plutĂŽt que comme sous-module, vous aurez cette erreur :

$ git checkout master
error: The following untracked working tree files would be overwritten by checkout:
  CryptoLibrary/Makefile
  CryptoLibrary/includes/crypto.h
  ...
Please move or remove them before you can switch branches.
Aborting

Vous pouvez le forcer Ă  basculer avec checkout -f, mais soyez attentif Ă  ce qu’il soit propre ou les modifications seraient Ă©crasĂ©es.

$ git checkout -f master
warning: unable to rmdir CryptoLibrary: Directory not empty
Basculement sur la branche 'master'

Ensuite, lorsque vous rebasculez, vous aurez un répertoire CryptoLibrary vide et git submodule update pourrait ne pas le remettre en état. Vous allez devoir vous rendre dans le répertoire de votre sous-module et lancer git checkout . pour retrouver tous vos fichiers. Vous pouvez lancer ceci dans un script submodule foreach dans le cas de multiples sous-modules.

Il est important de noter que depuis les versions de Git rĂ©centes, les sous-modules conservent leurs donnĂ©es Git dans le rĂ©pertoire .git du projet principal, ce qui Ă  la diffĂ©rence des versions antĂ©rieures, permet de supprimer le dossier du sous-module sans perdre les commits et les branches qu’il contenait.

Avec ces outils, les sous-modules peuvent ĂȘtre une mĂ©thode assez simple et efficace pour dĂ©velopper simultanĂ©ment sur des projets connexes mais sĂ©parĂ©s.

scroll-to-top