← Articles

Git worktrees à l’ère des agents

Avant l’agentique, je ne m’étais jamais servi des worktrees Git. Je savais que ça existait, rangé dans la case des trucs de niche, utile pour hotfixer main sans abandonner une feature à moitié écrite. Mais la plupart du temps, un git stash faisait l’affaire.

J’ai redécouvert cette fonctionnalité quand je me suis demandé comment isoler correctement le travail en parallèle de plusieurs agents sur une même codebase, en local.

Le problème n’est pas l’agent, c’est le dossier

Un agent fait le même travail qu’un développeur. Il lit des fichiers, les modifie, lance des commandes, commit parfois. Par contre, il a un pouvoir que je n’aurai jamais : le don d’ubiquité. Autrement dit, il peut lancer des sous-agents ou collaborer avec d’autres agents plus ou moins similaires, sur la même codebase et en même temps.

Le modèle de Git est décentralisé côté serveur. Chacun travaille sur sa copie locale et, de temps en temps, se synchronise avec un remote.

L’idée du worktree, c’est de décentraliser aussi le travail local. Chaque branche vient avec son propre répertoire. Plus besoin de stash ni de checkout à tout bout de champ.

Avant, si je voulais travailler sur tout autre chose, je faisais simplement un deuxième clone. Ce n’était pas très élégant, mais ça dépannait.

Ce qu’apporte un worktree

Un worktree, c’est un deuxième (ou n-ième) répertoire de travail branché sur le même dépôt. Même historique, mêmes objets Git, mais un checkout indépendant.

En pratique :

git worktree add ../mon-repo-feat-auth -b feat/auth

Je me retrouve avec un dossier voisin, sur sa propre branche, prêt à recevoir des commits. Le dépôt principal reste là où je l’ai laissé.

Pas besoin de recloner. Pas besoin de resynchroniser des remotes.

C’est pareil qu’un clone distinct, en fait ?

Non, et c’est tout l’intérêt.

Dans le nouveau dossier, .git n’est pas un répertoire mais un simple fichier d’une ligne :

$ cat ../mon-repo-feat-auth/.git
gitdir: /chemin/vers/mon-repo/.git/worktrees/mon-repo-feat-auth

Il pointe vers un petit répertoire créé dans le dépôt principal, qui contient uniquement ce qui est propre au worktree :

$ ls .git/worktrees/mon-repo-feat-auth
HEAD  ORIG_HEAD  commondir  gitdir  index  logs  refs

Ça signifie que chaque worktree a son HEAD (la branche checkoutée), son index (la staging area) et ses logs. Le fichier commondir renvoie vers le dépôt principal pour tout le reste.

Ce qui est partagé :

  • les objets (commits, arbres, blobs), donc tout l’historique
  • les branches et les tags
  • la config et les hooks
  • les remotes, donc un fetch dans un worktree profite à tous
  • le stash, ce qui surprend la première fois

Ce qui est dupliqué, c’est uniquement le working tree : les fichiers suivis par Git, extraits depuis les objets au moment du checkout. Un worktree pèse donc le poids d’un checkout, pas celui du dépôt. Sur un repo avec dix ans d’historique, la différence est énorme.

Et ce qui n’est pas copié du tout : tout ce que Git ne suit pas. Les .env, node_modules, .venv, caches de build et autres fichiers ignorés restent dans le dossier d’origine. Le nouveau worktree démarre sans eux. J’y reviendrai dans les pièges.

Ce modèle explique aussi la seule vraie limitation (assez naturelle, au final) : une branche ne peut être checkoutée que dans un worktree à la fois.

$ git worktree add ../autre feat/auth
fatal: 'feat/auth' is already used by worktree at '../mon-repo-feat-auth'

Une branche n’est qu’un pointeur vers un commit, et ce pointeur est commun à tous les worktrees. Si je commite sur feat/auth dans un dossier, la branche avance aussi pour l’autre, alors que ses fichiers n’ont pas bougé. Son git status afficherait des changements qui défont le commit que je viens de faire.

Contrairement à ce que je pensais, git worktree n’a rien de récent : il existe depuis Git 2.5, en 2015.

J’ai donc passé dix ans à côté sans en ressentir le besoin, et aujourd’hui je ne passe pas une journée sans 😅.

Ma règle : une feature, un worktree

Depuis que je m’appuie sur des agents pour produire du code, j’ai une règle simple : chaque feature passe par un worktree.

Concrètement, le cycle ressemble à ça :

  1. je crée une branche et un worktree dédiés
  2. j’ouvre la session agent dans ce dossier
  3. l’agent (ou moi, ou les deux) itère là-dedans
  4. je pousse, j’ouvre la PR
  5. une fois mergé, je supprime le worktree

Le dépôt principal reste mon ancrage, souvent sur main ou develop.

L’intérêt, c’est que les interruptions ne coûtent plus grand-chose. Si un sujet prioritaire passe en haut de la pile, j’ouvre immédiatement un worktree depuis un état stable, sans toucher à mon travail en cours.

Ce que ça change au quotidien

Le gain évident, et c’est ce qui rend les worktrees pertinents aujourd’hui, c’est de pouvoir paralléliser les sujets.

Avant, « travailler en parallèle » voulait souvent dire empiler des contextes dans ma tête et dans le stash. Avec un worktree par feature, paralléliser veut dire : deux dossiers, deux branches, deux objectifs. L’agent A ne voit pas les fichiers à moitié écrits de la feature B.

Je peux aussi faire de la revue pendant qu’une feature avance ailleurs. Plus besoin de choisir entre les deux.

Les pièges (parce qu’il y en a)

Ce n’est pas magique.

Les conflits. Travailler en parallèle sur des sujets qui ont des adhérences ne fait pas disparaître les conflits. Ils arrivent simplement plus tard, au moment du merge, et il faut toujours les résoudre.

Les fichiers ignorés. Comme dit plus haut, rien de ce que Git ne suit pas n’est recopié. Les dépendances (node_modules, .venv, caches de build) sont à réinstaller, et sur un gros monorepo ça se sent. Ça peut aussi provoquer des effets de bord, comme des tests qui échouent parce que la configuration locale manque : seuls les fichiers versionnés sont là, pas les .env.

Le stash partagé. Un git stash fait dans un worktree apparaît dans tous les autres. Avec un worktree par feature, j’en ai beaucoup moins besoin, mais je préfère le savoir avant de faire un stash pop au mauvais endroit.

Le ménage. Les worktrees s’accumulent. git worktree list, puis git worktree remove une fois la PR mergée, ça fait partie du rituel. Si on l’oublie, on se retrouve avec cinq dossiers fantômes.

Ce n’est pas un sandbox. Un worktree isole le checkout, pas les secrets, pas le réseau, pas les effets de bord d’une commande destructive. L’agent reste dans ma machine, avec mes droits.

Pourquoi maintenant

Dès que je délègue une partie de l’écriture à un agent, je n’ai plus un développeur face à un working tree : j’ai une petite équipe temporaire qui partage ma machine.

Comme je le disais plus haut, ma machine devient finalement l’équivalent d’un serveur partagé entre plusieurs intervenants.

Sans séparation explicite des espaces de travail, cette équipe se marche dessus.

Comme souvent, la conclusion c’est que l’outil doit répondre à un besoin. Je ne regrette pas d’être passé à côté des worktrees tant que le contexte ne s’y prêtait pas.

Aujourd’hui, en revanche, c’est un usage sur lequel je ne reviendrais pas.

Martin Catty
J’écris sur l’infrastructure, l’automatisation, la sécurité, l’IA, l’entrepreneuriat et le management.