Git submodules

La notion de submodule permet d'avoir des dépôts git à l'intérieur d'un dépôt, tout en gardant la gestion des différents dépôts séparée.
Permet par exemple d'intégrer une lib à l'intérieur d'un projet, ou de séparer la gestion d'un projet en plusieurs sous-projets tout en pouvant les utiliser ensemble.

Référence : https://git-scm.com/book/en/v2/Git-Tools-Submodules

Premiers pas

On se place dans le répertoire git-tests, qui contient déjà les dépôts repo1, clone-repo1, et on initialise un nouveau dépôt de test :
git init submodule-test
On crée un fichier et fait un premier commit :
cd submodule-test

echo "première ligne" > fichier1.txt
git add fichier1.txt
git ci -m "Première ligne dans fichier1.txt"
On ajoute maintenant le dépôt repo1 comme submodule du dépôt submodule-test :
git submodule add ../repo1/
Clonage dans '/path/to/git-test/submodule-test/repo1'...
fait.
On constate l'effet de la commande avec git status :
git st
Sur la branche main
Modifications qui seront validées :
  (utilisez "git reset HEAD ..." pour désindexer)

	nouveau fichier : .gitmodules
	nouveau fichier : repo1
Note La commande
git submodule add ../repo1/
met une copie de repo1 à la racine. Pour que repo1 soit recopié dans un sous-répertoire, on peut faire par exemple :
git submodule add ../repo1/ path/to/subdirectory

Fichier .gitmodules

Le fichier .gitmodules a été créé par git :
cat .gitmodules
[submodule "repo1"]
	path = repo1
	url = ../repo1/

Fichier .git/config

On peut voir aussi que .git/config a été modifié :
[submodule "repo1"]
	url = /path/to/git-test/repo1
	active = true
Ce qu'on a fait est correct dans le cadre d'un exercice, mais pas adapté à la vie réelle, car si quelqu'un clone le dépôt submodule-test, git va aller voir le fichier .submodule et essayer de le rappatrier depuis le path ../repo1. A moins que l'utilisateur ait déjà le dépôt repo1 sur sa machine, il n'arrivera pas à récupérer le submodule.
Donc il aurait mieux valu créer le submodule avec un path que tous les utilisateurs peuvent utiliser, par exemple
git submodule add https://github.com/.../repo1.git
Ça ne nous empêche pas d'utiliser la version locale de repo1, en faisant :
git config submodule.repo1.url ../repo1
Cela modifie .git/config, donc affecte uniquement la version locale du dépôt submodule-test.

Récapitulation

project
    ├── .gitmodules
    ├── MyModule
    │   └── .git
    └── .git
        └── config

Dans le submodule

Si on va dedans, on peut voir que ce dépôt a été cloné avec tout son historique :
cd repo1
git slog
git remote -v
Pourtant, on voit que .git n'est plus un répertoire mais un fichier qui contient une seule ligne :
gitdir: ../.git/modules/repo1
Le répertoire .git/modules/repo1 est un répertoire (bare) qui contient le dépôt correspondant au submodule.

Cloner un dépôt avec submodules

git clone https://github.com/some-user/Project
git submodule init
git submodule update
ou, directement :
git clone --recurse-submodules https://github.com/some-user/Project
(fait init et update du submodule et de tous ses sous-modules).
Si on a oublié --recurse-submodules dans le clone, on peut faire :
git submodule update --init --recursive

Travailler avec les submodules

cd path/to/MyNewModule
# vérifier si des modifications ont été apportées
git fetch
# récupérer ces modifications en local
git merge origin/main
git diff --cached path/to/MyNewModule
git diff --cached --submodule
Pour éviter de faire --submodule à chaque fois :
git config --global diff.submodule log
# ou alors
git config --local diff.submodule log
Pour éviter de faire cd path/to/MyNewModule lors du fetch :
git submodule update --remote path/to/MyNewModule
Utilise la branche main par défaut.

Pour suivre une autre branche :
git config -f .gitmodules submodule.MyNewModule.branch stable
Sans l'option -f .gitmodules, seul .git/config est modifié.
Avec -f .gitmodules, la modification ira aussi dans .gitmodules et sera donc versionnée. Tous ceux qui cloneront votre dépôt suivront cette branche. Maintenant, quand on fait :
git pull
git nous indique que des changements ont eu lieu dans les submodules. Pour avoir aussi un résumé des changements :
git config status.submodulesummary 1
Une fois les modifications enregistrées dans un commit, on peut les voir avec :
git log -p --submodule

Récupérer les changements dans un submodule

Un simple git pull fait un git fetch dans nos submodules, mais pas un update.
Il faut faire :
git submodule update --init --recursive
--recursive est nécessaire si certains submodules ont eux-même des submodules - à toujours faire par sécurité au cas où un de nos submodule soit transformé pour avoir lui-même un submodule.

Si un submodule change d'url, le git pull ne va pas marcher.
Dans ce cas, faire :
git submodule sync --recursive

Travailler sur un submodule

Cas où on travaille à la fois sur le projet principal et dans différents submodules.
Jusqu'à présent, git submodule update sert à rappatrier les changements dans les submodules, mais les sous-répertoires se trouvent dans un état de “detached HEAD”.
Pour remédier à cela, il faut aller dans chaque sous-module et indiquer à git quelle branche suivre :
cd path/to/submodule
git checkout stable
Pour récupérer les changements dans les submodules, il faut dorénavant faire :
git submodule update --remote --merge
Si on fait des changements dans le submodule, faire par exemple :
git submodule update --remote --rebase
Attention, si on oublie --merge ou --rebase, git va récupérer les changements et mettre le dédôt des sous-modules dans un état "detached HEAD".

Si on a travaillé en local sur un submodule et qu'un git submodule update génère des conflits, on doit les gérer (dans le répertoire de chaque submodule) comme on gère un conflit normal.

Attention, lorsqu'on fait un commit sur le dépôt principal, il faut aussi faire un push des commits effectués dans les submodules.

A DETAILLER
git push --recurse-submodules=on-demand
git push --recurse-submodules=check
git config push.recurseSubmodules check

Supprimer un submodule

Voir stackoverflow :
# Remove the submodule entry from .git/config
git submodule deinit -f path/to/submodule

# Remove the submodule directory from the superproject's .git/modules directory
rm -rf .git/modules/path/to/submodule

# Remove the entry in .gitmodules and remove the submodule directory located at path/to/submodule
git rm -f path/to/submodule