Aller au contenu
Organiser ses arbres de travail git

Organiser ses arbres de travail git

·7 mins
Sommaire

Si vous avez déjà maintenu plusieurs versions d’un même logiciel, vous savez certainement que jongler entre les branches peut s’avérer pénible. Bien souvent on a recours à un mix de branches temporaires et de git stash pas forcément bien clair. Le pire est lorsque le dépôt contient des fichiers exclus du suivi de versions dont le format ou contenu change en fonction des branches, par exemple un fichier de configuration. Là il vous faut manuellement corriger à chaque changement de branche, ce qui est très fastidieux.

Les personnes les moins expérimentées avec git ont une solution toute trouvée pour parer à ce problème : cloner plusieurs fois le dépôt. On se retrouve ainsi avec deux dossiers distincts, un pour chaque branche. Ainsi on peut tranquillement travailler en parallèle sur chacune des branches. Malynx le lynx !

Bien que ça fonctionne, ça vient avec quelques imperfections. Déjà, ça nous fait maintenir plusieurs clones, ce qui signifie qu’il faudra manuellement fetch ou pull les changements sur chaque copie. Et bien que l’espace disque ne soit plus trop un problème de nos jours, un dépôt git volumineux peut rapidement prendre beaucoup de place s’il est cloné plusieurs fois.

Qu’est-ce qu’un arbre de travail ?
#

En 1670, Molière nous expliquait déjà le sujet dans sa célèbre pièce « Le Bourgeois gentilhomme » :

MAÎTRE GITS — Est-ce un arbre de travail que vous souhaitez cloner ?
MONSIEUR JOURDAIN — Non, non, point d’arbre de travail.
MAÎTRE GITS — Vous ne voulez que d’un dépôt nu ?
MONSIEUR JOURDAIN — Non, je ne veux ni dépôt nu ni arbre de travail.
MAÎTRE GITS — Il faut bien que ce soit l’un, ou l’autre.
MONSIEUR JOURDAIN — Pourquoi ?
MAÎTRE GITS — Par la raison, Monsieur, qu’il n’y a pour développer avec git que les dépôts nu, ou les arbres de travail.
MONSIEUR JOURDAIN — Il n’y a que les dépôts nu, ou les arbres de travail ?
MAÎTRE GITS — Non, Monsieur : tout ce qui n’est point dépôt nu est arbre de travail ; et tout ce qui n’est point arbre de travail est dépôt nu.
MONSIEUR JOURDAIN — Et comme l’on développe habituellement avec git qu’est-ce que c’est donc que cela ?
MAÎTRE GITS — Sur un arbre de travail.
MONSIEUR JOURDAIN — Quoi ! Quand je fais : git commit -m "Ajout de mes pantoufles et de mon bonnet de nuit", c’est sur un arbre de travail ?
MAÎTRE GITS — Oui, Monsieur.
MONSIEUR JOURDAIN — Par ma foi ! il y a plus de quarante ans que j’utilise les arbres de travail sans que j’en susse rien, et je vous suis le plus obligé du monde de m’avoir appris cela.

Lorsque vous clonez un dépôt git, vous vous retrouvez avec un dossier contenant deux choses :

  • l’ensemble des fichiers du projet ;
  • un dossier .git.

L’ensemble des fichiers du projet est ce que l’on appelle un arbre de travail.

Maintenant, si vous clonez le même dépôt avec l’option --bare, vous vous retrouvez avec un dossier contenant exactement la même chose que le dossier .git précédent. Un tel dépôt où il n’y a que les fichiers nécessaires au fonctionnement de git est appelé un dépôt nu.

Le truc c’est que les fichiers du dépôt nu (ou du dossier .git) contiennent absolument tout ce qui est nécessaire pour construire un arbre de travail que nous pourrons utiliser pour développer. Et pas juste un seul arbre de travail : tous les arbres de travail possibles, ce qui veut dire que l’on peut par exemple construire on arbre de travail correspondant à n’importe quelle branche, tag ou commit.

Créer de nouveaux arbres de travail
#

Normalement vous devriez avoir compris l’idée derrière cet article. À la place de cloner plusieurs fois un même dépôt, ce qui nous donnerait pour chaque clone un arbre de travail et l’ensemble du dépôt git, nous allons cloner une seule fois le dépôt et l’utiliser afin de générer plusieurs arbres de travail distincts sur lesquels nous pourrons travailler.

Avant d’aller plus loin, il nous faut réfléchir au résultat final attendu. Personnellement, j’aime bien avoir un dossier avec le nom du projet contenant un dossier par arbre de travail. Ça donne quelque chose de ce genre :

my-app/
├── 1.0
├── 2.0
├── dev
└── main

J’aime bien que la branche principale (ici main) soit le clone complet (un arbre de travail plus l’ensemble des fichiers git), mais si vous préférez il est possible d’utiliser un dépôt nu (ici bare) et d’en dériver l’ensemble des arbres de travail :

my-app/
├── 1.0
├── 2.0
├── bare
├── dev
└── main

C’est à votre convenance, les deux se justifient. En revanche, je vous déconseille de faire comme ceux qui font des dossiers d’arbre de travail à l’intérieur d’un dépôt nu. C’est juste immonde, et le pire c’est que parfois ils en font une vidéo afin de montrer à tout le monde qu’ils font n’importe quoi.

Commençons par cloner notre dépôt directement dans le sous-dossier qui nous intéresse et positionnons nous dedans :

$ git clone "ssh://git.example.org/my-user/my-app.git" "my-app/main"
$ tree -L1 my-app/
my-app/
└── main
$ cd "my-app/main"
Pour la variante utilisant un dépôt nu, penser à ajouter l’option --bare et à changer le destination pour my-app/bare ou équivalent.

De là, nous pouvons créer un nouvel arbre de travail sur une nouvelle branche déjà existante. Pour cela on utilise git worktree en lui passant d’abord le chemin vers le nouvel arbre de travail puis le nom de la branche à utiliser dans cet arbre de travail :

$ git worktree add "../dev" "dev"
Il est également possible de passer autre chose qu’une branche, par exemple un tag ou un commit en particulier.

Si vous souhaitez créer un nouvel arbre de travail ainsi qu’une nouvelle branche, rien de plus simple. Par défaut git créé une nouvelle branche dont le nom est le dernier composant du chemin vers le nouvel espace de travail. Vous pouvez également changer le nom en utilisant -b.

$ git worktree add "../hotfix-01"
$ git worktree add "../hotfix-02" -b "super-patch-02"
$ ls ../
dev  hotfix-01  hotfix-02  main
$ git branch
+ dev
+ hotfix-01
* main
+ super-patch-02

Fonctionnement d’un arbre de travail
#

Souvenez-vous que vous utilisiez déjà un arbre de travail sans forcément le savoir : en changeant de dossier d’arbre de travail tout fonctionne exactement comme vous en aviez l’habitude. Les arbres de travail utilisant chacun une branche différente, je vous recommande de définir l’upstream par défaut dans chacun d’entre eux :

$ git push --set-upstream "origin" "nom_de_la_branche"

La seule différence concerne le .git qui se trouve à l’intérieur de l’arbre de travail. Regardez un peu le .git des arbres de travail supplémentaires que nous avons créé : il ne s’agit plus d’un dossier mais d’un fichier dont le contenu contient un lien vers l’arbre de travail principal (main ou bare suivant le cas).

$ cat ".git"
gitdir: /home/rodolphe/tmp/my-app/main/.git/worktrees/hotfix-01

Ce lien est ce qui permet à notre arbre de travail d’utiliser les données de l’arbre de travail principal. Notons que le chemin est absolu : si vous déplacez vos dossiers vous casserez ce lien. De plus, ce lien n’est pas à sens unique mais à double sens : l’arbre de travail principal enregistre le chemin vers chaque nouvel arbre de travail.

En conséquent, pour déplacer ou supprimer un espace de travail il est préférable d’utiliser la commande prévue à cet effet afin que l’ensemble des liens soient mis à jour.

$ git worktree move "../hotfix-01" "../toto"
$ git worktree remove "../toto"

S’il est trop tard et que vous n’avez pas utilisé ces commandes, vous pouvez regarder le documentation de git worktree repair afin de réparer ce qui est possible ou bien de git worktree prune afin de supprimer les arbres de travail dont le dossier a été supprimé. Dans tous les cas, n’hésitez pas à lister l’ensemble des arbres de travail de votre dépôt à l’aide de la commande git worktree list.

Bonus : facilement changer de dossier
#

Utiliser plusieurs espaces de travail c’est bien, mais ça a pour conséquence que nos projets se retrouvent dans un sous-dossier, ce qui est légèrement moins ergonomique. Afin de résoudre cet inconvénient mineur, je vous suggère d’utiliser zoxide qui est une amélioration de la commande cd. En particulier, vous n’avez plus besoin de taper le chemin complet vers l’emplacement où vous souhaitez aller, zoxide mémorise là où vous êtes déjà allé et est capable de déduire la destination depuis un chemin partiel.

$ pwd
/home/rodolphe/tmp/my-app/main
$ z hotfix
$ pwd
/home/rodolphe/tmp/my-app/hotfix-01

Ça simplifie grandement la vie.