J’ai récemment assisté à l’atelier Git avancé de Christophe Porteneuve. Au programme, de nombreuses choses intéressantes, dont les submodules Git, La partie de la formation les concernant m’intéressait particulièrement pour une problématique que je rencontre très fréquemment : travailler avec un projet Maven multi-modules.

Il y a bien sûr la solution qui consiste à utiliser un unique dépôt Git à la racine du projet. Ca fonctionne bien, mais pour des gros projets,  je préfère avoir un dépôt dédié à chacun des modules Maven. Cela ne poserait aucun problème, s’il n’y avait une arborescence de répertoires entre le projet principal, et ses modules.
Voyons donc avec un exemple comment mettre en place une gestion de configuration séparée pour chacun des modules d’un projet Maven.

Créer le projet de démonstration

Commençons par créer le projet Maven multi-module qui va nous servir d’exemple. Pour faciliter cette étape, j’ai choisi d’utiliser l’archetype appfuse-modular-jsf (qui me fournit directement un projet de deux sous-modules).

1
mvn archetype:generate -B -DarchetypeGroupId=org.appfuse.archetypes -DarchetypeArtifactId=appfuse-modular-jsf -DarchetypeVersion=2.0.2 -DgroupId=org.courtine -DartifactId=demo

Si la création s’est bien passée, le projet dont nous allons nous servir doit ressembler à ça :

Arborescence du projet appfuse-modular-jsf

Arborescence du projet appfuse-modular-jsf

Nous avons donc pour la suite des évènements :

  • le projet « demo »
  • le module « demo-core »
  • le module « demo-webapp »

Mise en place des dépôts distants

  • création des dépôts distants (paradoxalement en local, pour simplifier le tutoriel, mais cela fonctionne évidemment avec des dépôts placés sur un serveur central) :
1
2
3
4
cd $BARE_REPOS
git init --bare demo-core.git
git init --bare demo-webapp.git
git init --bare demo.git
  • chargement de la première révision des modules dans le dépôt distant :
1
2
3
4
5
6
cd $PROJECT_HOME/demo/core
git init
git add .
git commit -m 'Initial revision'
git remote add origin $BARE_REPOS/demo-core.git
git push -u origin master

On fait la même chose pour le module « demo-webapp ».

Le dépôt du projet Démo

Jusqu’ici, rien d’extraordinaire : nous avons deux dépôts distincts dans notre projet, chacun possédant un dépôt distant.

Passons maintenant au dépôt qui nous intéresse : celui du projet « Démo », qui va contenir les deux submodules.

1
2
3
4
cd $PROJECT_HOME/demo/
git init
git submodule add $BARE_REPOS/demo-core.git core
git submodule add $BARE_REPOS/demo-webapp.git web

Arrêtons-nous sur la commande git submodule add :

  • le premier argument est l’adresse du dépôt distant du module
  • le deuxième argument est le répertoire local dans lequel va se trouver ce module.

Après ces commandes, regardons le statut de notre projet :

Statut des submodules Git

Statut des submodules Git

Nous remarquons que la création des sous-modules a ajouté un fichier .gitmodules, listant les sous-modules (ainsi que les adresses de leurs dépôts distants respectifs). Nous remarquons aussi que la commande s’est bien comportée comme prévu : l’ensemble des fichiers des deux sous-modules n’est pas dupliqué dans le dépôt principal. Les core et web affichés dans le statut sont des références à la révision respective des sous-modules utilisée.

Si un sous-module est un projet partagé (librairie, etc.), il est ainsi possible dans le projet principal de se caler sur une révision précise de celle-ci (qui n’est pas forcément la dernière).

Finissons maintenant la mise en place du projet Démo :

1
2
3
4
git add .
git commit -m "Commit initial du projet Demo"
git remote add origin $BARE_REPOS/demo.git
git push -u origin master

Utilisation

Cloner le dépôt

Pour un nouveau développeur qui arrive sur le projet, il faut commencer par cloner ce projet contenant des submodules. La marche à suivre est la suivante :

1
2
3
4
git clone $BARE_REPOS/demo.git
cd demo
git submodule init
git submodule update

Pour aller plus vite (surtout utile dans le cas où les submodules ont eux-mêmes des submodules), on peut utiliser le raccourci suivant :

1
git clone --recursive $BARE_REPOS/demo.git

Travailler sur un submodule

Après avoir cloné un projet contenant un submodule, si on va dans le répertoire de celui-ci, on s’aperçoit que l’on est sur un « detached HEAD ». En effet, le git submodule update du projet principal récupère une référence précise du submodule, en se moquant de savoir si celle-ci correspond à une branche, un tag, etc.

Pour travailler sur le submodule, il faut donc se replacer sur une branche connue. Ca ne pose pas de problème particulier, mais il faut juste le savoir :

1
2
cd $PROJECT_HOME/demo/core
git checkout master

Mettre à jour un submodule

La mise à jour d’un submodule se fait en plusieurs phases. Tout d’abord, la mise à jour du submodule lui-même :

1
2
3
4
cd $PROJECT_HOME/demo/core
# Modif...
git commit -am "Modification du module"
git push

Dans le projet principal, un nouveau git status permet de voir qu’une modification du submodule a bien été détectée. Pour partager cette mise à jour, il ne faut donc pas oublier de la publier également dans le projet principal :

1
2
3
cd $PROJECT_HOME/demo
git commit -am "Nouvelle version du submodule"
git push

Ici, deux erreurs à ne pas faire :

  • publier le submodule en oubliant de publier le projet principal
  • pire : publier le projet principal en ayant oublié de publier le submodule. En local, on ne s’aperçoit de rien mais si un autre développeur fait une mise à jour, la référence du submodule dans le projet principal lui sera inconnue (car non publiée)…

Récupérer une mise à jour de submodule

Dernier piège : récupérer un submodule mis à jour (par un autre développeur) :

1
2
3
cd $PROJECT_HOME/demo
git pull
git submodule update

Le simple pull permet de récupérer la nouvelle révision de référence du submodule, mais pas le module lui-même. Pour cela, il faut le compléter d’un git submodule update.

Conclusion

Les submodules permettent donc de répondre convenablement à la problématique initiale. Cependant, avant de les mettre véritablement en place, il faut s’entraîner afin de bien comprendre leur mode de fonctionnement (on a très vite fait d’oublier une étape…).