Le repliage de dossiers de GNU Stow
💡 Sujet
On croit que stow crée un lien symbolique par fichier. C'est faux : il lui arrive de lier le
dossier entier. Le résultat est le même à l'usage, mais pas du tout pour la suite — un fichier
créé plus tard dans un dossier replié se retrouve physiquement dans le dépôt sans qu'on ait rien
copié, alors que dans un dossier réel il reste dehors, ignoré de Git.
Savoir lequel des deux on a en face change le réflexe à adopter.
🌳 La règle : le dossier cible existe-t-il déjà ?
Quand stow traite un dossier d'un paquet, il regarde la cible correspondante :
- elle n'existe pas → il crée un lien vers le dossier (tree folding). Un seul lien, et tout le contenu suit ;
- elle existe déjà comme vrai dossier → il y descend et lie fichier par fichier.
C'est visible immédiatement :
$ ls -l ~/.claude
lrwxr-xr-x commands -> ../dotfiles/boulot/.claude/commands # replié : lien de dossier
drwxr-xr-x rules # vrai dossier🔀 Le dépliage : quand deux paquets visent le même dossier
Un dossier replié appartient à un seul paquet — c'est un lien, il ne peut pointer que vers un endroit. Dès qu'un second paquet doit alimenter le même chemin, stow déplie : il remplace le lien par un vrai dossier et relie le contenu fichier par fichier.
$ ls -l ~/.claude/rules
context7.md -> ../../dotfiles/claude/.claude/rules/context7.md
equipe.md -> ../../dotfiles/boulot/.claude/rules/equipe.md
methodes.md -> ../../dotfiles/boulot/.claude/rules/methodes.mdDeux paquets (claude/ et boulot/) contribuent à ~/.claude/rules/ : le repliage est impossible,
donc liens de fichiers. À l'inverse ~/.claude/commands/ n'est alimenté que par boulot/, donc
replié.
✅ Ce que le repliage fait gagner
Dans un dossier replié, écrire c'est déjà versionner. Créer un fichier dans
~/.claude/commands/ le fait apparaître directement dans git status du dépôt : rien à copier,
juste à committer.
$ vim ~/.claude/commands/ma-commande.md
$ cd ~/dotfiles && git status -s
?? boulot/.claude/commands/ma-commande.mdLe corollaire est moins agréable : le dossier absorbe tout ce qui y atterrit, y compris ce qui
n'a rien à faire dans un dépôt. Un outil qui écrit son cache ou son jeton dans un dossier replié
l'écrit dans le dépôt. D'où l'intérêt d'un .gitignore qui couvre ces chemins.
⚠️ Le piège inverse : ne pas replier un dossier habité
Si le dossier cible contient déjà des fichiers qui ne sont pas dans le dépôt, il ne faut surtout pas le laisser se replier : ils seraient masqués par le lien, donc invisibles.
$ ls -l ~/.ssh
config -> ../dotfiles/ssh/.ssh/config # géré par stow
known_hosts # vrai fichier, hors dépôt~/.ssh existait déjà : stow y est descendu et n'a lié que config. S'il n'avait pas existé au
premier stow, il aurait été replié, et le known_hosts créé ensuite serait parti dans le dépôt.
Même logique pour ~/.config/gh et son hosts.yml porteur d'un jeton.
⚠️ Ce qui compte est l'existence, pas le contenu : un dossier cible déjà présent mais vide n'est pas replié non plus — stow y descend et lie fichier par fichier.
🔎 Vérifier avant d'agir
stow -n -v -t ~ mon-paquet # simulation : montre LINK / UNLINK / folding sans rien faire
readlink ~/.claude/commands # non vide = dossier replié
stow -R -t ~ mon-paquet # restow : à faire après avoir déplacé un fichier entre paquetsPour forcer stow à ne jamais replier, --no-folding lie systématiquement fichier par fichier.
🧩 Résumé
- 📁 Stow lie le dossier entier si la cible n'existe pas, fichier par fichier sinon.
- 🔀 Deux paquets sur le même chemin ⇒ dépliage automatique en vrai dossier.
- ✍️ Dans un dossier replié, un fichier créé est déjà dans le dépôt — il ne reste qu'à committer.
- ⚠️ Un dossier qui contient des fichiers hors dépôt (
known_hosts, jetons) doit rester un vrai dossier : le repliage les masquerait ou les aspirerait dans Git. - 🔎
readlinkrépond en une seconde ;stow -n -vmontre tout avant d'agir.