worktrees: sidestep path collisions with a numeric suffix, never adopt #77

Closed
opened 2026-08-24 09:38:40 +01:00 by naps62-yolo · 0 comments
Collaborator

Today <repo>/worktrees/<branch> is both where a human expects to find a worktree and the daemon's identity key for one. Those two jobs conflict: because the path is derived, different requests map onto it; because it is predictable, it lands exactly where the operator already keeps their own worktrees. run_add then tries to recover by asking registered_for() whether the occupant is "ours", a question it cannot actually answer — which is how #69 came about.

Settled: the daemon never adopts anything, and a path collision is sidestepped rather than resolved.

  • Placement stays <repo>/worktrees/<branch>. If anything at all occupies that path — a worktree of ours, one the operator made, an unrelated directory — append -2, then -3, and so on until the path is free.
  • The suffix names the directory only. The branch is whatever was requested. If that branch is already checked out somewhere git refuses, which is correct and already surfaced by the branch picker's is_head / worktree flags.
  • Delete registered_for(), the adoption branch in run_add, and WorktreeError::PathExists along with its tests.
  • The [daemon].worktree_root override keeps its current shape (repo basename plus a digest of the full path) and gains the same suffix behaviour.

This also dissolves #71 rather than fixing it: two concurrent creates for the same repo and branch no longer contend for one directory, they simply get different ones. The live-session check added in SessionManager::create becomes unnecessary and should go with it.

Non-goals: no ownership marker, no registry of created worktrees for the purposes of adoption, no change to where worktrees live.

Supersedes #69 and #71.

Today `<repo>/worktrees/<branch>` is both where a human expects to find a worktree and the daemon's identity key for one. Those two jobs conflict: because the path is derived, different requests map onto it; because it is predictable, it lands exactly where the operator already keeps their own worktrees. `run_add` then tries to recover by asking `registered_for()` whether the occupant is "ours", a question it cannot actually answer — which is how #69 came about. Settled: the daemon never adopts anything, and a path collision is sidestepped rather than resolved. - Placement stays `<repo>/worktrees/<branch>`. If anything at all occupies that path — a worktree of ours, one the operator made, an unrelated directory — append `-2`, then `-3`, and so on until the path is free. - The suffix names the *directory* only. The branch is whatever was requested. If that branch is already checked out somewhere git refuses, which is correct and already surfaced by the branch picker's `is_head` / `worktree` flags. - Delete `registered_for()`, the adoption branch in `run_add`, and `WorktreeError::PathExists` along with its tests. - The `[daemon].worktree_root` override keeps its current shape (repo basename plus a digest of the full path) and gains the same suffix behaviour. This also dissolves #71 rather than fixing it: two concurrent creates for the same repo and branch no longer contend for one directory, they simply get different ones. The live-session check added in `SessionManager::create` becomes unnecessary and should go with it. Non-goals: no ownership marker, no registry of created worktrees for the purposes of adoption, no change to where worktrees live. Supersedes #69 and #71.
naps62-yolo added this to the Worktree lifecycle milestone 2026-08-24 09:38:40 +01:00
naps62-yolo added the area/daemontype/refactordifficulty/medium labels 2026-08-24 09:38:40 +01:00
naps62-yolo added a new dependency 2026-08-24 09:39:01 +01:00
This repo is archived. You cannot comment on issues.
1 Participants
Due Date
No due date set.
Reference: naps62/maestro#77