// Corrélation session ↔ worktree par contenance de chemin. Source unique partagée par le daemon, le // front web et l'extension VS Code : la règle doit être identique partout, sinon un terminal apparaît // sous un worktree côté serveur et sous un autre côté UI. // // Historiquement la corrélation était une égalité stricte `session.cwd === worktree.path`. Deux cas // réels y échappent : // 1. « Démarrer le projet » autorise un sous-répertoire par commande (`LaunchCommand.cwd`, borné par // `resolveLaunchSubdir`) : le terminal tourne DANS le worktree, mais pas à sa racine ; // 2. une session de groupe (P6) couvre plusieurs repos via `--add-dir` : son cwd est le parent commun // et les worktrees couverts n'apparaissent que dans `addedDirs`. // Dans les deux cas la session appartient bel et bien au worktree. // // Aucune dépendance à `node:path` (le front tourne dans un navigateur) : la comparaison se fait par // segments, en tolérant les deux séparateurs pour rester correcte sur Windows. La comparaison reste // SENSIBLE à la casse : les chemins comparés viennent tous de la même source (git et la base), et // insensibiliser casserait deux répertoires ne différant que par la casse sous Linux. /** Segments non vides d'un chemin, séparateurs POSIX et Windows confondus. */ function segments(p: string): string[] { return p.split(/[\\/]+/).filter((s) => s.length > 0 && s !== '.'); } /** true si `child` est `parent` lui-même ou un descendant. */ export function containsPath(parent: string, child: string): boolean { const p = segments(parent); const c = segments(child); if (c.length < p.length) return false; return p.every((seg, i) => c[i] === seg); } /** Répertoires qu'une session occupe : son cwd, plus les répertoires reliés d'une session de groupe. */ export function sessionDirs(session: { cwd: string; addedDirs?: string[] }): string[] { return [session.cwd, ...(session.addedDirs ?? [])]; } /** * Attribue une session au worktree `worktreePath`. `others` = les autres worktrees connus (au moins * ceux du même repo) : sans eux, une session lancée dans un worktree imbriqué (`repo/.worktrees/x`) * serait aussi listée sous le checkout principal `repo`. Le worktree le plus spécifique gagne. */ export function sessionBelongsToWorktree( session: { cwd: string; addedDirs?: string[] }, worktreePath: string, others: string[] = [], ): boolean { const depth = segments(worktreePath).length; return sessionDirs(session).some((dir) => { if (!containsPath(worktreePath, dir)) return false; // un autre worktree plus profond contient aussi ce répertoire → il est le propriétaire légitime. return !others.some((o) => segments(o).length > depth && containsPath(o, dir)); }); } /** * Worktree auquel rattacher un répertoire, parmi une liste hétérogène (tous repos confondus) : le plus * spécifique qui le contient. Sert à épingler le watcher FS du worktree d'une session vivante et à * étiqueter un terminal. */ export function findWorktreeForCwd(cwd: string, worktrees: T[]): T | null { let best: T | null = null; let bestDepth = -1; for (const w of worktrees) { if (!containsPath(w.path, cwd)) continue; const depth = segments(w.path).length; if (depth > bestDepth) { best = w; bestDepth = depth; } } return best; }