Travailler avec git, c’est bien. Travailler avec des sous-modules, c’est bien aussi, mais ça peut devenir compliqué. Travailler avec des cascades de sous-modules, ça peut vite devenir l’enfer.

Lorsque l’on change de branche, que l’on ajoute ou supprime des sous-modules avant de revenir à une autre branche, git ne sait pas toujours quels fichiers conserver ou supprimer. Une solution est d’utiliser les git worktrees afin de séparer clairement les branches et les dossiers qui les contiennent.

Cet article présente la marche à suivre pour ne plus mélanger les branches et leurs sous-modules ainsi que l’intégration de cette fonctionnalité dans neovim.

L’usage des worktrees prend tout son sens lorsque des sous-modules sont en jeu. Dans le cas d’un dépôt simple, je ne suis pas certain qu’il y ait un intérêt quelconque à les utiliser.

Git

Clone

La première étape pour commencer à travailler sur un projet est bien sûr de récupérer le code source. Et c’est là qu’apparaît la première modification du workflow habituel: il faut utiliser l’option --bare lors du clone.

Avant

1 git clone git@gitlab.com:pcoves/pcoves.gitlab.io.git
2 ls -lh pcoves.gitlab.io/
3# Permissions Size User Date Modified Name
4# .rw-r--r-- 3.0k pcoves 29 Sep 14:23 config.toml
5# drwxr-xr-x - pcoves 29 Sep 14:22 content
6# drwxr-xr-x - pcoves 29 Sep 14:23 static
7# drwxr-xr-x - pcoves 29 Sep 14:23 themes
8 ls -lh pcoves.gitlab.io/.git
9# Permissions Size User Date Modified Name
10# drwxr-xr-x - pcoves 29 Sep 14:26 branches
11# .rw-r--r-- 337 pcoves 29 Sep 14:26 config
12# .rw-r--r-- 73 pcoves 29 Sep 14:26 description
13# .rw-r--r-- 29 pcoves 29 Sep 14:26 HEAD
14# drwxr-xr-x - pcoves 29 Sep 14:26 hooks
15# .rw-r--r-- 5.3k pcoves 29 Sep 14:26 index
16# drwxr-xr-x - pcoves 29 Sep 14:26 info
17# drwxr-xr-x - pcoves 29 Sep 14:26 logs
18# drwxr-xr-x - pcoves 29 Sep 14:26 objects
19# .rw-r--r-- 410 pcoves 29 Sep 14:26 packed-refs
20# drwxr-xr-x - pcoves 29 Sep 14:26 refs

Après

1 git clone --bare git@gitlab.com:pcoves/pcoves.gitlab.io.git
2 ls -lh pcoves.gitlab.io.git/
3# Permissions Size User Date Modified Name
4# drwxr-xr-x - pcoves 29 Sep 14:25 branches
5# .rw-r--r-- 134 pcoves 29 Sep 14:25 config
6# .rw-r--r-- 73 pcoves 29 Sep 14:25 description
7# .rw-r--r-- 21 pcoves 29 Sep 14:25 HEAD
8# drwxr-xr-x - pcoves 29 Sep 14:25 hooks
9# drwxr-xr-x - pcoves 29 Sep 14:25 info
10# drwxr-xr-x - pcoves 29 Sep 14:25 objects
11# .rw-r--r-- 365 pcoves 29 Sep 14:25 packed-refs
12# drwxr-xr-x - pcoves 29 Sep 14:25 refs

L’option --bare permet de ne clone que les fichiers nécessaires au bon fonctionnement du dépôt git mais pas le code qu’il manipule.

Worktree add

Il est désormais nécessaire d’ajouter un worktree, c’est-à-dire de récupérer le code d’une des branches comme on aurait pu l’avoir “avant”, sans l’option --bare.

1 cd pcoves.gitlab.io.git/
2 git worktree add main main
3# Preparing worktree (checking out 'main')
4# HEAD is now at 5e246a1 Merge branch 'main' of gitlab.com:pcoves/pcoves.gitlab.io
5 ls -lh
6# Permissions Size User Date Modified Name
7# drwxr-xr-x - pcoves 29 Sep 14:33 branches
8# .rw-r--r-- 134 pcoves 29 Sep 14:33 config
9# .rw-r--r-- 73 pcoves 29 Sep 14:33 description
10# .rw-r--r-- 21 pcoves 29 Sep 14:33 HEAD
11# drwxr-xr-x - pcoves 29 Sep 14:33 hooks
12# drwxr-xr-x - pcoves 29 Sep 14:33 info
13# drwxr-xr-x - pcoves 29 Sep 14:33 main
14# drwxr-xr-x - pcoves 29 Sep 14:33 objects
15# .rw-r--r-- 365 pcoves 29 Sep 14:33 packed-refs
16# drwxr-xr-x - pcoves 29 Sep 14:33 refs
17# drwxr-xr-x - pcoves 29 Sep 14:33 worktrees

L’ajout d’un woorktree, ici suivant la branche main fait apparaître un répertoire éponyme contenant le code du dépôt.

1 ls -lh main/
2# Permissions Size User Date Modified Name
3# .rw-r--r-- 3.4k pcoves 29 Sep 14:33 config.toml
4# drwxr-xr-x - pcoves 29 Sep 14:33 content
5# drwxr-xr-x - pcoves 29 Sep 14:33 sass
6# drwxr-xr-x - pcoves 29 Sep 14:33 static
7# drwxr-xr-x - pcoves 29 Sep 14:33 templates

Add some more

Il est possible d’ajouter autant de worktrees que désiré, en parallèle les uns des autres.

1 git worktree add feat/phoenyx/ feat/phoenyx
2# Preparing worktree (checking out 'feat/phoenyx')
3# HEAD is now at 5688986 Add another pass post
4 l feat/phoenyx/
5# Permissions Size User Date Modified Name
6# .rw-r--r-- 3.0k pcoves 29 Sep 14:41 config.toml
7# drwxr-xr-x - pcoves 29 Sep 14:41 content
8# drwxr-xr-x - pcoves 29 Sep 14:41 static
9# drwxr-xr-x - pcoves 29 Sep 14:41 themes

Lors du premier git worktree add main, git a récupéré la branche main (branche par défaut) dans le dossier main (argument de la commande) git worktree add. Dans le cas présent, j’ai mis deux fois feat/phoenyx: le premier est le path, le second est la branche que je veux récupérer dans mon worktree.

1 ls -lh
2# Permissions Size User Date Modified Name
3# drwxr-xr-x - pcoves 29 Sep 14:33 branches
4# .rw-r--r-- 134 pcoves 29 Sep 14:33 config
5# .rw-r--r-- 73 pcoves 29 Sep 14:33 description
6# drwxr-xr-x - pcoves 29 Sep 14:41 feat
7# .rw-r--r-- 21 pcoves 29 Sep 14:33 HEAD
8# drwxr-xr-x - pcoves 29 Sep 14:33 hooks
9# drwxr-xr-x - pcoves 29 Sep 14:33 info
10# drwxr-xr-x - pcoves 29 Sep 14:33 main
11# drwxr-xr-x - pcoves 29 Sep 14:33 objects
12# .rw-r--r-- 365 pcoves 29 Sep 14:33 packed-refs
13# drwxr-xr-x - pcoves 29 Sep 14:33 refs
14# drwxr-xr-x - pcoves 29 Sep 14:41 worktrees

Au final, je me retrouve avec un seul dépôt git mais bien plusieurs branches en parallèle. Il est désormais possible de développer dans l’une ou l’autre et les commits qui y seront effectués n’affecteront pas les autres worktrees.

Worktree remove

Une fois le travail effectué dans un worktree, il n’est pas nécessaire de le conserver.

1 git worktree remove feat/phoenyx
2 ls -lh
3# Permissions Size User Date Modified Name
4# drwxr-xr-x - pcoves 29 Sep 14:33 branches
5# .rw-r--r-- 134 pcoves 29 Sep 14:33 config
6# .rw-r--r-- 73 pcoves 29 Sep 14:33 description
7# drwxr-xr-x - pcoves 29 Sep 14:49 feat
8# .rw-r--r-- 21 pcoves 29 Sep 14:33 HEAD
9# drwxr-xr-x - pcoves 29 Sep 14:33 hooks
10# drwxr-xr-x - pcoves 29 Sep 14:33 info
11# drwxr-xr-x - pcoves 29 Sep 14:33 main
12# drwxr-xr-x - pcoves 29 Sep 14:33 objects
13# .rw-r--r-- 365 pcoves 29 Sep 14:33 packed-refs
14# drwxr-xr-x - pcoves 29 Sep 14:33 refs
15# drwxr-xr-x - pcoves 29 Sep 14:49 worktrees

Seul le worktree main est toujours en place, feat/phoenyx a été supprimé localement.

Avantages

En introduction de ce billet, je parle des avantages quant aux sous-modules.

Git, par défaut, ne clone pas les sous-modules. Et cela ne change pas lorsque l’on utilise les worktrees.

Par contre, il est désormais possible, dans un worktree, d’aller altérer les sous-modules (ajout, suppression, mise-à-jour) sans toucher à l’arborescence des autres worktrees. De fait, contrairement à l’état quantique dans lequel un dépôt avec sous-modules se trouve suite à un git switch, il y a une garantie nouvelle et bienvenue grâce à la séparation entre les différents worktrees.

Neovim

Que serait ce genre de fonctionnalité sans son intégration dans l’éditeur de code?

Installation

L’ajout du plugin git-worktree.nvim de ThePrimeagen via packer est extrêmement simple:

1use {
2 "ThePrimeagen/git-worktree.nvim",
3 as = "worktree",
4 config = function()
5 require("git-worktree").setup()
6 end
7}

Intégration

1use {
2 "nvim-telescope/telescope.nvim",
3 as = "telescope",
4 requires = "plenary",
5 config = function()
6 vim.keymap.set("n", "<Leader>|", ":Telescope git_files<CR>")
7 vim.keymap.set("n", "<Leader><Leader>", ":Telescope buffers<CR>")
8 vim.keymap.set("n", "<Leader>g", ":Telescope live_grep<CR>")
9
10 local telescope = require("telescope")
11 telescope.load_extension("git_worktree")
12 vim.keymap.set("n", "<Leader>w", function() telescope.extensions.git_worktree.git_worktrees() end)
13 vim.keymap.set("n", "<Leader>W", function() telescope.extensions.git_worktree.create_git_worktree() end)
14
15 telescope.load_extension('harpoon')
16 vim.keymap.set("n", "<Leader>:", function() telescope.extensions.harpoon.marks() end)
17 end
18}

Désormais, <Leader>w permet de naviguer d’un worktree à l’autre. C’est aussi dans cette interface qu’un Ctrl-d supprime un woorktree.

De plus, <Leader>W permet de créer un nouveau worktree depuis la branche courante.

Conclusion

Comme dit en préambule, cette fonctionnalité n’est pas la solution à tous les problèmes liés à l’usage des sous-modules.

Cependant, je trouve que j’ai largement gagné en efficacité grâce à elle dans les dépôts où beaucoup de développements ont lieu en parallèle et contenant des sous-modules, car je n’ai pas à gérer les problèmes liés aux changements de branche “sur place”.