Les sous-modules Git II

J’ai déjà écrit un article de présentation des sous-modules Git:

https://amethyste16.wordpress.com/2021/01/03/les-sous-modules-git/

 

Je vais le prolonger en traitant un cas que je rencontre sur mon projet, mais pas abordé dans cet article:

Le projet conteneur référence plusieurs sous-modules. On le fait évoluer, mais cela nécessite de modifier également les sous-modules.

La solution proposée et apparemment celle recommandée par les tutos que j’ai pu lire peut devenir assez lourde dans ce cas et même sujette à des erreurs. Donc on va modifier projet et sous-modules et pousser les modifications dans le même push.

 

J’en profiterai pour analyser quelques pièges dans lesquels nous sommes tombés. C’est donc plutôt un article de retours d’expérience, l’essentiel de la partie technique a déjà été abordée dans l’article qui précède.

Environnement de démo

Pour changer on va partir d’un remote en ligne plutôt que local comme dans l’article précédent.

CONFIDENTIEL: bon je l’avoue, je n’ai jamais réussi à faire tourner la démo qui suit sur un remote local. Je ne comprends pas pourquoi!

 

J’ai choisi Azure Devops, mais Github ou autre fera aussi l’affaire. On crée deux projets:

  • Main
  • Plugin1

A ce stade les projets ressemblent à ceci:

 

On va lancer le script suivant pour alimenter un peu le contenu afin d’avoir un espace de travail:


cls

mkdir demo
cd demo

echo "Récupère plugin1 et le complète"
echo "-------------------------------"

git clone https://fmirouze.visualstudio.com/plugin1/_git/plugin1
cd plugin1

echo "plugin1 info" > lib1.txt
git add lib1.txt
git commit -m "first commit plugin1"
git push

cd ..

echo "Récupère main et le complète"
echo "-------------------------------"

git clone https://XXXXXX.visualstudio.com/main/_git/main
cd main

echo "main info" > code.txt
git add code.txt
git commit -m "first commit main"

git submodule add https://XXXXX.visualstudio.com/plugin1/_git/plugin1
git commit -m "Ajout du sous-module plugin1"
git push

cd ..

 

NOTE: vous remplacerez l’url de la commande clone par ce qui convient

 

Il fait deux choses:

  1. Alimente plugin1 avec un nouveau fichier et pousse le tout dans le remote
  2. Alimente main et lui associe le module plugin1, puis pousse le tout dans remote

NOTE: Dans la vraie vie on devrait tirer une branche, puis faire une pull request. On va laisser cela de côté dans l’immédiat, mais j’en reparle plus loin.

 

Justement à quoi ressemble maintenant le remote?

Plugin1 voit un nouveau fichier:

 

Devops appelle main la branche par défaut. Rien à voir avec le nom du repository conteneur. Ça doit se paramétrer quelque part…

 

Plus intéressante main:

Je vous laisse vérifier que code.txt a une ligne de plus. Remarquez la présence du fichier .gitmodules décrit en détail dans l’article précédent. Et bien entendu le module. Si on le sélectionne, Devops affiche:

 

C’est un numéro de commit, le dernier effectué:

 

C’est normal, on a déjà souligné que les sous-modules Git sont toujours un repository HEAD DETACHED.

Entrons maintenant dans le cœur du sujet.

 

Démo: récupérer un projet avec un sous-module

 

On l’a déjà fait dans le premier article, mais ce sera la première étape. Voici le script:


echo "Clone team1"
echo "-------------------------------"

git clone --recursive https://XXXXXX.visualstudio.com/main/_git/main team1
cd team1

cd plugin1

Un repository local team1 apparaît il contient le projet main complet avec le sous module:

/c/temp/test/demo/team1/plugin1 (main)
$ dir
README.md lib1.txt

 

Si on fait un git status dans plugin1 on constate que l’on est pas dans une branche:

/c/temp/test/demo/team1/plugin1 ((d1187ee…))
$ git status
HEAD detached at d1187ee
nothing to commit, working tree clean

 

A ce stade c’est normal. Nous allons modifier ce plugin, mais pour cela on doit se placer dans une branche:


git checkout main

 

Et on procède comme pour n’importe quel autre repository:


echo "modify plugin1" > file1.txt
git add file1.txt
git commit -m "Commit test 1"
git push

 

Et on vérifie facilement que tout se passe bien:

Allons dans team1 voir ce qui se passe:


cd ..

git status

 

L’affichage:

/c/temp/test/demo/team1 (main)
$ git status
On branch main
Your branch is up to date with ‘origin/main’.

Changes not staged for commit:
(use « git add <file>… » to update what will be committed)
(use « git restore <file>… » to discard changes in working directory)
modified: plugin1 (new commits)

Submodules changed but not updated:

* plugin1 d1187ee…e7bfdbb (1):
> Commit test 1

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

 

Git détecte correctement que le projet principal n’est plus à jour. Avant de résoudre le problème on va le modifier lui-aussi:


echo "modif main 1" >> code.txt

 

Le statut évolue évidemment un peu:

/c/temp/test/demo/team1 (main)
$ git status
On branch main
Your branch is up to date with ‘origin/main’.

Changes not staged for commit:
(use « git add <file>… » to update what will be committed)
(use « git restore <file>… » to discard changes in working directory)
modified: code.txt
modified: plugin1 (new commits)

Submodules changed but not updated:

* plugin1 d1187ee…e7bfdbb (1):
> Commit test 1

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

 

On répond de façon classique:


git add *
git commit -m "Commit team1"
git push

 

 

Le commit n’est plus le même, une mise à jour a donc bien eu lieu. Vérifiez au passage que code.txt contient une ligne de plus.

On a donc bien démontré un scénario dans lequel on modifie simultanément un sous-module et le projet conteneur. Nous ferons plus tard d’autres expériences.

 

Démo: mise à jour

On a poussé des modifications dans remote, sont t’elle récupérable? On va tester deux scénarios. Tout d’abord:


cd ..

git clone --recursive https://XXXXX.visualstudio.com/main/_git/main team2

 

On crée un nouveau repository local appelé team2.

Vous constaterez qu’il est bien redescendu. Vérifiez qu’il manque rien tant dans le plugin, que dans le projet principal. Plugin1 est bien entendu HEAD DETACHED.

 

Nous avons encore le repository main qui nous a servit à créer le projet initial. Il n’est bien entendu plus à jour. Vérifions que ce soit possible, on commence par se déplacer dans /main, puis:


git pull

git status

 

En sortie:

/c/temp/test/demo/main (main)
$ git pull
remote: Azure Repos
remote: Found 3 objects to send. (24 ms)
Unpacking objects: 100% (3/3), 351 bytes | 17.00 KiB/s, done.
From https://XXXX.visualstudio.com/main/_git/main
33dd99f..bda881e main -> origin/main
Fetching submodule plugin1
From https://XXX.visualstudio.com/plugin1/_git/plugin1
d1187ee..e7bfdbb main -> origin/main
Updating 33dd99f..bda881e
Fast-forward
code.txt | 1 +
plugin1 | 2 +-
2 files changed, 2 insertions(+), 1 deletion(-)

/c/temp/test/demo/main (main)
$ git status
On branch main
Your branch is up to date with ‘origin/main’.

Changes not staged for commit:
(use « git add <file>… » to update what will be committed)
(use « git restore <file>… » to discard changes in working directory)
modified: plugin1 (new commits)

Submodules changed but not updated:

* plugin1 e7bfdbb…d1187ee (1):
< Commit test 1

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

 

On a donc mis à jour le projet conteneur, mais il reste à mettre à jour les sous-modules comme nous l’indique le statut. On connaît la commande:


git submodule update --recursive --init

En sortie:

/c/temp/test/demo/main (main)
$ git submodule update –recursive –init
Submodule path ‘plugin1’: checked out ‘e7bfdbbc176b514e09e385f1a3f1383b74740f17’

/c/temp/test/demo/main (main)
$ git status
On branch main
Your branch is up to date with ‘origin/main’.

nothing to commit, working tree clean

 

Et c’est bon. Vérifiez que le repository local main est bien à jour.

Ceci étant établi, je propose que l’on fasse quelques expériences pour être certain d’avoir bien en main le mécanisme de gestion des sous-modules.

 

Script

Voici le script qui rejoue le scénario précédent. Commencez par préparer les deux remotes dans Devops, puis lancez:


cls

mkdir demo
cd demo

echo "Récupère plugin1 et le complète"
echo "-------------------------------"

git clone https://XXX.visualstudio.com/plugin1/_git/plugin1
cd plugin1

echo "plugin1 info" &gt; lib1.txt
git add lib1.txt
git commit -m "first commit plugin1"
git push

cd ..

echo "Récupère main et ajoute un sous module"
echo "-------------------------------"

git clone https://XXXX.visualstudio.com/main/_git/main
cd main

echo "main info" &gt; code.txt
git add code.txt
git commit -m "first commit main"

git submodule add https://XXX.visualstudio.com/plugin1/_git/plugin1
git commit -m "Ajout du sous-module plugin1"
git push

cd ..

echo "Clone team1"
echo "-------------------------------"

git clone --recursive https://XXX.visualstudio.com/main/_git/main team1
cd team1

cd plugin1

@echo modify plugin1

git checkout main
echo "modify plugin1" &gt; file1.txt
git add file1.txt
git commit -m "Commit test 1"
git push

cd ..

@echo modify main
echo "modif main 1" &gt;&gt; code.txt
git add *
git commit -m "Commit team1"
git push

cd ..

@echo Team2 get new project
@echo --------------------------
git clone --recursive https://XXX.visualstudio.com/main/_git/main team2

@echo go back to main
@echo --------------------------

cd main

git pull
git submodule update --recursive --init

cd ../..

 

À la fin on obtient cette structure de répertoires:

 

Quelques pièges à connaître

Il est important de signaler que j’ai rédigé cet article avec la version Git 2.30.0.windows.1

C’est important, car je n’obtiens pas forcément les mêmes résultats que ce que je peux lire dans certains tutos. Les dernières versions ont apparemment renforcées les protections pour limiter le nombre d’erreurs et c’est tant mieux!

 

Attention au push partiel

Revenons dans team1 et modifions plugin1:


cd plugin1
echo "Modif 2 sur plugin1" >> file1.txt
git add file1.txt
git commit -m "Commit test 1"
git push

Et on s’arrête là. Pour rappel, le projet conteneur affiche ceci:

/c/temp/test/demo/team1 (main)
$ git status
On branch main
Your branch is up to date with ‘origin/main’.

Changes not staged for commit:
(use « git add <file>… » to update what will be committed)
(use « git restore <file>… » to discard changes in working directory)
modified: plugin1 (new commits)

Submodules changed but not updated:

* plugin1 e7bfdbb…aa3a4d1 (1):
> Commit test 1

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

Mais nous n’avons poussé que le nouveau module dans remote. Le repository pointe encore vers la version précédente.

Si je me rends par exemple dans team2:

/c/temp/test/demo/team2 (main)
$ git fetch

/c/temp/test/demo/team2 (main)
$ git status
On branch main
Your branch is up to date with ‘origin/main’.

nothing to commit, working tree clean

Et oui, fetch ne remarque rien.

La règle est donc simple:

  1. Add/Commit/Push pour tous les modules modifiés
  2. Add/Commit/Push pour le projet conteneur

Si vous faites des PR ce sera pareil, on traitera un exemple tout à l’heure.

 

Dans team1 faites maintenant la séquence Add/Commit/Push pour mettre d’aplomb le projet. Le fetch précédent donne ceci par contraste:

/c/temp/test/demo/team2 (main)
$ git fetch
remote: Azure Repos
remote: Found 2 objects to send. (12 ms)
Unpacking objects: 100% (2/2), 230 bytes | 17.00 KiB/s, done.
From https://XXXXX.visualstudio.com/main/_git/main
1a3c6e7..ccd00d6 main -> origin/main
Fetching submodule plugin1
From https://XXXXX.visualstudio.com/plugin1/_git/plugin1
aa3a4d1..79bd36f main -> origin/main

 

Pull ne suffit pas

Nous sommes dans team2 et faisons juste un pull:

/c/temp/test/demo/team2 (main)
$ git pull
Updating bda881e..1a3c6e7
Fast-forward
plugin1 | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)

La première fois que j’ai vu ça, j’ai cru que les modifications de modules étaient également descendues. ben non comme le démontrera aisément la lecture de /plugin1/file1.txt

 

Il est indispensable de lancer également:


git submodule update --init --recursive

 

Un lecteur m’a signalé un paramétrage qui permet de combiner pull et submodule update:

https://stackoverflow.com/questions/4611512/is-there-a-way-to-make-git-pull-automatically-update-submodules

C’est une très bonne idée, je pense.

 

Pour des raisons pédagogiques, je vais toutefois laisser ce point de côté. Je préfère décomposer dans cet article.

 

Pull + update ou update + pull?

Dans team1 faites une modification dans plugin1 et pull le tout de la façon correctement vue tout à l’heure.

On revient à team2 que l’on souhaite rafraîchir. Supposons que l’on procède ainsi:


git submodule update --init --recursive
git pull

 

On met d’abord les sous-modules à jour, puis le conteneur. Si vous avez compris ce qu’est un sous-module Git vous vous doutez de ce qui se passe:

seules les modifications du projet conteneur seront descendues.

 

Donc l’ordre correct est:


git pull
git submodule update --init --recursive

 

Modifier un module encore en HEAD DETACHED

Une erreur possible est d’oublier de rattacher à une branche le sous-module modifié. Que se passerait t’il?

Le plus simple pour la suite est de refaire un git clone, puis de se rendre dans le plugin1 et jouer la séquence bien connue:

 


echo "modif 1" > file2.txt

git add *

git commit -m "Modif 1"

git push

 

En sortie:

/c/temp/test/demo/team2/plugin1 ((4efdedd…))
$ git commit -m « Modif 1 »
[detached HEAD 03bdccf] Modif 1
1 file changed, 1 insertion(+)
create mode 100644 file2.txt

/c/temp/test/demo/team2/plugin1 ((03bdccf…))
$ git push
fatal: You are not currently on a branch.
To push the history leading to the current (detached HEAD)
state now, use

git push origin HEAD:<name-of-remote-branch>

 

Et vous vérifierez facilement que rien ne bouge côté remote comme le suggère le message d’erreur. Le risque est d’aller un peu vite et ne pas lire le message d’erreur. Si ensuite on fait un checkout:

 

/c/temp/test/demo/team2/plugin1 ((03bdccf…))
$ git checkout main
Warning: you are leaving 1 commit behind, not connected to
any of your branches:

03bdccf Modif 1

If you want to keep it by creating a new branch, this may be a good time
to do so with:

git branch <new-branch-name> 03bdccf

Switched to branch ‘main’
Your branch is up to date with ‘origin/main’.

Pas très bon, les modifications sont perdues.

Donc faites attention.

Ce statut HEAD detached n’est pas pour autant sans intérêt. On peut faire toutes les modifications que l’on veut et même des commits sans altérer vos branches. C’est un bon moyen de tester quelques trucs.

 

Conclusions

J’espère que cet article vous a intéressé, mais surtout qu’il vous a fait comprendre un problème important avec les sous-modules: il y a pas mal de pièges et de difficultés.

J’en ai démontré quelques-uns, mais il en reste encore pas mal. Que se passe-t-il si on supprime ou déplace un sous-module par exemple.

Les sous-modules obligent à pas mal de discipline et de rigueur. Si votre équipe décide d’aller dans cette voie, prenez vraiment le temps de vous entraîner sur un projet sans enjeu du genre ceux créés dans cet article pour être certains de se sentir à l’aise.

 

Si vous travaillez avec VSCode, une extension peut aider:

Git (Submodule) Assistant

Elle surveille un certain nombre de pratiques dangereuses ou peu recommandées. J’ai mis en bibliographie des liens vers des tutos VSCode et sous-modules.

 

Bibliographie

Votre commentaire

Entrez vos coordonnées ci-dessous ou cliquez sur une icône pour vous connecter:

Logo WordPress.com

Vous commentez à l’aide de votre compte WordPress.com. Déconnexion /  Changer )

Image Twitter

Vous commentez à l’aide de votre compte Twitter. Déconnexion /  Changer )

Photo Facebook

Vous commentez à l’aide de votre compte Facebook. Déconnexion /  Changer )

Connexion à %s