Révisions GIT: épisode 6

Avec les cinq premiers épisodes, nous avons abordés les fonctionnalités les plus courantes de GIT: les dépôts locaux, les commandes de base, les branches, ainsi que les dépôts distants. Nous allons maintenant décrire des fonctionnalités un peu moins courantes. L’utiisation des sous-modules ne répond qu’à des besoins particuliers, lorsque la taille du projet devient importante, ou tout simplement lorsque l’application développée le nécessite.

Révisions GIT: épisode 6

Pourquoi les sous-modules

Il y a deux raisons principales à l’utilisation des sous-modules (submodules), ou des sous-arborescences (subtrees) avec GIT :

  • Dans le cadre d’un projet imposant, tentaculaire, avec des composants qui sont plus ou moins indépendants les uns des autres. Dans ce cas, les sous-modules évitent de devoir télécharger l’intégralité du code systématiquement. Vous ne clonez en local que les modules dont vous avez besoin,
  • Vous utilisez un outil qui ne fonctionne correctement, qu’avec les sous-modules.

Prenons un exemple avec Hugo, le générateur de site statique utilisé pour ce blog. Un site Hugo contient deux parties : le site lui-même (le contenu), et le thème. Le thème est un composant “indépendant” du site, mais doit être déployé dans une sous-arborescence de ce site. L’évolution du contenu (le site lui-même), et le développement du thème sont complètement décorrelés. Dans beaucoup de cas, les deux sont développés par deux personnes différentes.

.
├── config.toml
├── archetypes
├── content
├── data
├── layouts
├── static
└── themes
    └── <nom du thème>
        ├── assets
        ├── data
        ├── i18n
        ├── layouts
        └── theme.toml

Si nous souhaitons héberger notre site sur des plateformes comme Netlify, le site et le thème doit se trouver chacun dans un dépôt GIT. Si nous suivons les méthodes décrites dans l’épisode 2, nous ferions quelque chose du type

# en considérant que nous sommes à la racine du site
$ git clone https://github.com/egeorjon/blog site
$ cd themes
$ git clone https://github.com/egeorjon/minimal-blank minimal-blank

Dans ce cas, nous aurions une arborescence de travail qui ressemblerait à celle de la Figure 1

Figure 1: Utilisation de git clone pour installer un thème
Figure 1: Utilisation de git clone pour installer un thème
Nous avons l’équivalent de deux dépôts : le premier à la racine pour gérer le code du site, et le second dans themes/minimal-blank pour gérer le code du thème.

Cette méthode ne fonctionne pas avec Netlify, et elle ne sera pas acceptée par GIT dans certains cas. Prenons un exemple:

# Plaçons nous dans le répertoire du thème
$ cd themes/minimal-blank
# Modifions un fichier
$ code theme.toml
# la commande git status va nous donner le status du theme
$ git status
    On branch main
    Your branch is up to date with 'origin/main'.

    Untracked files:
    (use "git add <file>..." to include in what will be committed)
            theme.toml

    nothing added to commit but untracked files present (use "git add" to track)

# indexons, et validons ce changement
$ git add theme.toml
$ git commit -m "Update the description"
    [main 0a076bc] Initial release
    1 file changed, 1 insertion(+)
    create mode 100644 theme.toml

# Revenons au niveau du site
$ cd ../..
# Regardons le status de ce dépôt : git a reperé qu'un changement a eu lieu dans le répertoire
$ git status
    On branch main
    Your branch is up to date with 'origin/main'.

    Untracked files:
    (use "git add <file>..." to include in what will be committed)
            themes/minimal-blank

    nothing added to commit but untracked files present (use "git add" to track)

# Essayons d'indexer et de valider cette modification
$ git add themes/minimal-blank
    warning: adding embedded git repository: themes/minimal-blank
    hint: You've added another git repository inside your current repository.
    hint: Clones of the outer repository will not contain the contents of
    hint: the embedded repository and will not know how to obtain it.
    hint: If you meant to add a submodule, use:
    hint:
    hint:   git submodule add <url> themes/minimal-blank
    hint:
    hint: If you added this path by mistake, you can remove it from the
    hint: index with:
    hint:
    hint:   git rm --cached themes/minimal-blank
    hint:
    hint: See "git help submodule" for more information.
# La commande a échoué.

Pour résoudre ce point de blocage, comme nous l’indique GIT, nous allons devoir employer les sous-modules (submodules). Il existe une autre méthode, appelée sous-arborescence que je n’aborderai pas dans cette épisode (ni dans la série d’ailleurs).

Les sous-modules nous donnent la possibilité de gérer un dépôt se trouvant dans une sous-arborecence d’un dépôt existant. Le sous-module doit être considéré comme une référence vers un dépôt dont nous souhaitons utiliser le code. Cette fonction permet également de disposer, dans notre dépôt principal, de l’historique des modifications des “sous-dépôts”.

Initialisation de dépôts avec les sous-modules

Nous allons reprendre notre exemple précédent. Nous avons un dépôt principal (conteneur) pour le site, et un dépôt « secondaire » pour le thème. Nous souhaitons utiliser ce thème. Dans le chapitre précédent, nous avons utiliser git clone, ce qui a conduit à un échec. Nous allons cette fois, utiliser la commande git submodule :

$ git submodule add <url du dépôt distant> <répertoire>

Exemple :

# Status de notre dépôt principal: nous n'avons aucune modification en cours.
$ git status
    On branch main
    Your branch is up to date with 'origin/main'.

    nothing to commit, working tree clean

# Nous nous plaçons dans le répertoire du thème
$ cd themes

# Téléchargeons le thème avec la commande submodule
$ git submodule add https://github.com/egeorjon/minimal-blank minimal-blank
    Cloning into '.../themes/minimal-blank'...
    remote: Enumerating objects: 1506, done.
    remote: Counting objects: 100% (1506/1506), done.
    remote: Compressing objects: 100% (1115/1115), done.
    remote: Total 1506 (delta 596), reused 1264 (delta 358), pack-reused 0
    Receiving objects: 100% (1506/1506), 5.93 MiB | 1.45 MiB/s, done.
    Resolving deltas: 100% (596/596), done.

# Refaisons un point sur notre dépôt principal
$ git status
    On branch main
    Your branch is up to date with 'origin/main'.

    Changes to be committed:
    (use "git restore --staged <file>..." to unstage)
            new file:   ../.gitmodules
            new file:   minimal-blank    

La commande git submodule add a

  • Crée le répertoire,
  • Téléchargé le thème,
  • Crée un fichier .gitmodules à la racine de notre dépôt.

Nous avons maintenant une arborecence qui ressemble à celle de la figure 2

Figure 2: Utilisation de git submodule pour installer un thème
Figure 2: Utilisation de git submodule pour installer un thème

Il y a plusieurs remarques à faire à ce stade

  • Le .git qui se trouve dans le thème, n’est plus un répertoire, mais un simple fichier,
  • Nous avons un fichier GIT supplémentaire : .gitmodules,
  • Le status nous indique, que du point de vue du dépôt principal, le thème est considéré comme un répertoire comme un autre.

Jetons un oeil dans le fichier .gitmodules, situé à la racine du dépôt principal

$ cat .gitmodules
    [submodule "themes/minimal-blank"]
        path = themes/minimal-blank
        url = https://github.com/egeorjon/minimal-blank

Le fichier contient la liste des sous-modules attachés au dépôt principal, avec leur lien vers leur dépôt distant.

Utilisation des sous-modules

A partir de là, il faut considérer que nous avons deux dépôts distincts : le dépôt du site, et le dépôt du thème. Nous pouvons manipuler ces deux dépôts de façon quasiment indépendante.

# Nous sommes à la racine de notre dépôt principal
$ git status
    On branch main
    Your branch is ahead of 'origin/main' by 2 commits.
    (use "git push" to publish your local commits)

    Changes to be committed:
    (use "git restore --staged <file>..." to unstage)
            new file:   ../.gitmodules
            new file:   minimal-blank

$ git remote -v
    origin  https://github.com/egeorjon/blog (fetch)
    origin  https://github.com/egeorjon/blog (push)

# Plaçons nous dans le répertoire du thème
$ cd themes/minimal-blank

# La commande status nous donne le status du dépôt du thème.
$ git status
    On branch main
    Your branch is up to date with 'origin/main'.

    nothing to commit, working tree clean

$ git remote -v
    origin  https://github.com/egeorjon/minimal-blank (fetch)
    origin  https://github.com/egeorjon/minimal-blank (push)

Mises à jour depuis un dépôt central

Supposons que nous gérions le site, et que le thème utilisé soit « public ». Il faut penser à mettre régulièrement à jour le thème, pour bénéficier des dernières fonctionnalités. Plusieurs méthodes sont possibles :

  • Méthode 1: Utiliser les commandes habituelles,
  • Méthode 2: Utiliser les commandes submodule.

Méthode 1:

# On se place au niveau du dépôt du thème
$ cd themes/minimal-Blank

# On récupère les modifications du dépôt distant
$ git fetch
    POST git-upload-pack (165 bytes)
    POST git-upload-pack (968 bytes)
    remote: Enumerating objects: 1, done.
    remote: Counting objects: 100% (1/1), done.
    remote: Total 1 (delta 0), reused 0 (delta 0), pack-reused 0
    Unpacking objects: 100% (1/1), 624 bytes | 104.00 KiB/s, done.
    From https://github.com/egeorjon/minimal-Blank
    1658871..7ef9f1a  main                  -> origin/main

# On incorpore les modifcations à la branche principale
$ git merge origin/main

Nous aurions pu effectuer ces opérations avec git pull.

Méthode 2 :

$ git submodule update --init
$ 

Dans les deux cas, il faut « intégrer » les changements du thème au niveau du site (dépôt principal)

$ 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)
    (commit or discard the untracked or modified content in submodules)
            modified:   themes/minimal-blank (modified content)

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

$ git add minimal-blank
$ git commit -m "Update the theme"

Commandes particulières pour les dépôts principaux

Un autre point d’attention pour les projets utilisant les sous-modules, porte sur la façon de les cloner. Habituellement, lorsque nous voulons récupérer un projet sur notre machine, nous utilisons la commande git clone : sans option la commande va télécharger le projet, mais sans les sous-modules. Les répertoires seront présents mais vides. Pour télécharger le projet, ainsi que l’ensemble des sous-modules (des dépendances), il faut utiliser le paramètre --recursive.

$ git clone --recursive <url du dépôt distant> <répertoire cible>

En cas d’oubli de ce paramètre, la commande clone va juste créer les répertoires des sous-modules, mais ces répertoires seront vides. Nous pouvons récupérer ces sous-modules avec les commandes git submodule init et git submodule update.

$ git clone https://github.com/egeorjon/EG-Blog EG-Blog
    Cloning into 'EG-Blog'...
    remote: Enumerating objects: 4008, done.
    remote: Counting objects: 100% (505/505), done.
    remote: Compressing objects: 100% (209/209), done.
    remote: Total 4008 (delta 322), reused 427 (delta 282), pack-reused 3503
    Receiving objects: 100% (4008/4008), 53.62 MiB | 1.01 MiB/s, done.
    Resolving deltas: 100% (1647/1647), done.

$ cd EG-Blog/themes/minimal-Blank
$ ls
.
..

Démarrons avec la commande submodule init

# Initialisation du sous-module
$ git submodule init
    Submodule 'themes/minimal-Blank' (https://github.com/egeorjon/minimal-blank) registered for path './'

# Téléchargment du sous-module
$ git submodule update
    Cloning into '.../themes/Minimal-Blank'...
    Submodule path './': checked out 'bf106c7f99b11717587ec545c2fc1a84555080aa'

$ ls
    archetypes
    assets
    data
    i18n
    layouts
    LICENSE
    README.md
    static
    theme.toml

Cycle d’utilisation des sous-modules

Comme je l’ai précisé plus haut, une fois dans le sous-module, nous pouvons utiliser les commandes standards (add / commit / push), créer des branches, gérer l’historique, exactement comme un dépôt standard. Il faut cependant veiller à valider les changements des sous-modules au niveau du dépôt principal.

Reprenons l’exemple précédent: nous avons un site dans un dépôt principal, et le thème de ce site, dans le dépôt secondaire. Mais contrairement au cas précédent, nous effectuons le développement du site, ET du thème. Nous allons effectuer une modification au niveau du thème :

# Plaçons-nous au niveau du thème
$ cd themes/minimal-blank

# Modifions un fichier
$ code theme.toml

# la commande git status va nous donner le status du theme
$ git status
    On branch main
    Your branch is up to date with 'origin/main'.

    Untracked files:
    (use "git add <file>..." to include in what will be committed)
            theme.toml

    nothing added to commit but untracked files present (use "git add" to track)

# indexons, et validons ce changement
$ git add theme.toml
$ git commit -m "Update the description"
    [main 0a076bc] Initial release
    1 file changed, 1 insertion(+)
    create mode 100644 theme.toml

# Poussons les modifications vers notre dépôt central
$ git push 

A ce stade, nous avons fait une modification au niveau du thème, la modification a été enregistré sur le dépôt local, et le dépôt central. Au niveau du dépôt principal, il nous reste à « relier » les modifications effectuées au dépôt principal :

# Revenons au niveau du site
$ cd ../..
# La modification du thème apparaît dans le dépôt principal
$ git status
    On branch main
    Your branch is ahead of 'origin/main' by 2 commits.
    (use "git push" to publish your local commits)

    Changes to be committed:
    (use "git restore --staged <file>..." to unstage)
               new file:   minimal-blank

# Nous validons le changement effectué à l'intérieur du thème
$ git commit -a -m "Update the theme's description"
    [main 244a2b9] Theme update to add lastmod date
    1 file changed, 4 insertions(+)
    create mode 100644 .gitmodules
    create mode 160000 themes/minimal-blank

$ git status
On branch main
Your branch is ahead of 'origin/main' by 3 commits.
  (use "git push" to publish your local commits)

nothing to commit, working tree clean

Le cycle de développement doit donc être

  1. Modifications du/des sous-modules,
  2. Validation des modifications (git add / git commit),
  3. Pousser les modifications locales du/des sous-modules, vers le dépôt distant (git push),
  4. Au niveau du dépôt principal, validation des modifications du/des sous-modules (git add / git commit),
  5. Pousser les modificatons locales du dépôt principal local vers le dépôt distant (git push).

Une erreur fréquente est d’oublier d’effectuer le push des modifications du/des sous-modules (point 3). Il y a alors désalignement entre

  • d’une part, le dépôt local du site, dont les derniers commit pointent sur le dernières versions des sous-modules,
  • d’autre part, le dépôt distant, dont les derniers commit pointent une version du sous-module qui n’existe pas encore dans le dépôt distant,

comme l’illustre la figure 3.

Figure 3: Désalignement entre le dépôt principal et le sous-module
Figure 3: Désalignement entre le dépôt principal et le sous-module
.

Dans le cas d’un site généré avec Hugo, l’exécution conduira à un échec.

9:00:40 AM: Build ready to start
9:00:47 AM: build-image version: ac716c5be7f79fe384a0f3759e8ef612cb821a37 (xenial)
9:00:47 AM: build-image tag: v3.13.0
9:00:47 AM: buildbot version: f89dd42aa70d76d1da992dc3fa004a4c745208bc
9:00:47 AM: Fetching cached dependencies
9:00:52 AM: Finished downloading cache in 4.884436042s
9:00:52 AM: Starting to extract cache
9:00:57 AM: Finished extracting cache in 5.238375072s
9:00:57 AM: Finished fetching cache in 10.16855893s
9:00:57 AM: Starting to prepare the repo for build
9:00:58 AM: Preparing Git Reference refs/heads/master
9:00:59 AM: Error fetching branch: https://github.com/egeorjon/EG-Blog refs/heads/master
9:00:59 AM: Creating deploy upload records
9:00:59 AM: Failing build: Failed to prepare repo
9:00:59 AM: Failed during stage 'preparing repo': exit status 1
9:01:00 AM: Finished processing build request in 12.549951201s

Le message d’erreur est loin d’être explicite.

Les dangers des sous-modules

Comme nous venons de le voir, les sous-modules sont faciles à utiliser, mais ils demandent une certaine vigilance. Plusieurs cas peuvent conduire à des pertes de code.

  • Un git fetch, au niveau du dépôt principal, ne suffira pas à récupérer les modifications effectuées par les collègues dans les sous-modules,
  • Inversement, si nous effectuons des changements dans les sous-modules, ils ne seront pas poussés sur les dépôts distants, même si nous exécutons un git push au niveau du dépôt principal. Il faut donc penser à faire le push pour chacun des sous-modules modifiés.

Configuration spécifiques et paramètres

Au delà des risques mentionnés, l’utilisation des sous-modules peut être assez fastidieuse si nous avons plusieurs dépôts secondaires. GIT fournit certaines commandes et paramètres pour nous simplifier la vie. Par défaut, toutes les commandes GIT ne s’appliquent qu’au dépôt courant. En ajoutant l’option --recurse-submodules les commandes s’appliqueront à tous les modules (en supposant que nous les exécutions depuis le dépôt distant).

L’option s’applique à la plupart des commandes de GIT : checkout, fetch, grep, pull, push, read-tree, reset, restore et switch. Si nous souhaitons, par exemple, généraliser la commande fetch à tous les sous-modules :

$ git fetch --recurse-submodules -v
    POST git-upload-pack (154 bytes)
    From https://github.com/egeorjon/site
    = [up to date]      master     -> origin/master
    = [up to date]      github     -> origin/github
    = [up to date]      github_2   -> origin/github_2
    = [up to date]      github_3   -> origin/github_3
    = [up to date]      github_4   -> origin/github_4
    = [up to date]      moscow     -> origin/moscow
    Fetching submodule themes/Minimal-Blank
    POST git-upload-pack (154 bytes)
    From https://github.com/egeorjon/minimal-blank
    = [up to date]      hugo_83_i18n -> origin/hugo_83_i18n
    = [up to date]      main         -> origin/main

Il n’est pas possible de forcer cette option via la commande git config. Nous avons cependant deux méthodes pour ne pas avoir à taper la commande systématiquement :

Méthode 1 : Indiquer cette option lors de la création du dépôt principal

$ git clone --recurse-submodules <url du dépôt distant> <répertoire>

Méthode 2 : Créer d’un alias.

# Création de l'alias
$ git config --global alias.sfetch "fetch --recurse-submodules -v"

#Utilisation de l'alias plûtot que de la commande
$ git sfetch
    POST git-upload-pack (154 bytes)
    From https://github.com/egeorjon/EG-Blog
    = [up to date]      master     -> origin/master
    = [up to date]      github     -> origin/github
    = [up to date]      github_2   -> origin/github_2
    = [up to date]      github_3   -> origin/github_3
    = [up to date]      github_4   -> origin/github_4
    = [up to date]      moscow     -> origin/moscow
    Fetching submodule themes/Minimal-Blank
    POST git-upload-pack (154 bytes)
    From https://github.com/egeorjon/minimal-blank
    = [up to date]      hugo_83_i18n -> origin/hugo_83_i18n
    = [up to date]      main         -> origin/main

Pour la commande git status, l’option existe :

$ git config --global status.submoduleSummary true
$ git status
    HEAD detached at bf106c7
    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:   theme.toml

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

$ git commit -a -m "Update description"
    [detached HEAD 5ed0521] Update description
    1 file changed, 1 insertion(+), 1 deletion(-)

$ cd ../..

$ 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 restore <file>..." to discard changes in working directory)
            modified:   themes/Minimal-Blank (new commits)

    Submodules changed but not updated:

    * themes/Minimal-Blank bf106c7...5ed0521 (1):
    > Update description

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

Conclusion

Nous venons de faire un parcours rapide des sous-modules dans GIT. A priori aisée, l’utilisation des sous-modules comporte quelques pièges. Il faut donc être vigilant, et mettre en place l’outillage, comme les alias, pour limiter les risques et rendre la gestion des différents dépôts moins fastidieuse.

Références

Commentaires