Skip to content

Enforce one Space per Git repository with worktree adoption - #4

Merged
alexandreroman merged 2 commits into
mainfrom
claude/casper-workspace-space-add-vrqomi
Jul 28, 2026
Merged

Enforce one Space per Git repository with worktree adoption#4
alexandreroman merged 2 commits into
mainfrom
claude/casper-workspace-space-add-vrqomi

Conversation

@alexandreroman

Copy link
Copy Markdown
Owner

Implement the core grouping logic for Git worktrees: a folder that is a linked worktree of a repository already open as a Space is now adopted into that Space as a linked workspace, and opening a repository whose worktrees are already open as Spaces reunifies them into a single Space.

Summary

This change enforces the invariant that Casper maintains one Space per Git repository, identified by the repository's common .git directory (shared by all working trees). The implementation handles three scenarios:

  1. Worktree adoption: When adding a folder that is a linked worktree of an already-open repository, it joins that repository's Space as a linked workspace instead of becoming a separate Space.
  2. Reunification: When opening a repository whose worktrees are already open as Spaces, those worktree Spaces are folded into the new repository Space, with their workspaces moving whole (same ids, ports, layouts, live terminals).
  3. Deduplication: A folder already tracked (as a Space or workspace) is selected instead of added twice.

Key changes

  • Repository identity: Extended Repository to expose commonDirPath (the shared .git directory) and isLinkedWorktree flag via libgit2. Updated WorkspaceFactory.GitInfo to carry these fields for routing decisions.

  • Worktree adoption: AppModel.addSpace now detects when a folder is a linked worktree of an open repository and calls adoptWorktree to add it as a linked workspace instead of creating a new Space. The adopted workspace is named after its branch, inherits the primary workspace's branch as its base, and runs no setup hook (the worktree already exists).

  • Reunification: When opening a repository, addSpace identifies any Spaces rooted at that repository's worktrees via worktreeSpaces and folds them into the new Space via reunify. Absorbed workspaces move whole; an ex-primary becomes a linked workspace named after its branch, and workspaces with no recorded base branch inherit the primary's branch as their base (those with a recorded base keep it).

  • Deduplication: trackedWorkspaceID checks both Space roots and workspace worktree paths (canonicalized to handle symlink variations) and selects the workspace instead of adding a duplicate.

  • Worktree removal: WorktreeManager.registeredName resolves a worktree's admin entry by path, so removing an adopted worktree (which may carry a name other than its branch) finds the correct entry to prune.

Implementation details

  • Paths are canonicalized (symlinks resolved) for comparison across different spellings of the same folder.
  • The main working tree of a repository takes precedence when both it and a worktree are open as Spaces (the main tree is where worktree operations run).
  • Reunification happens in a single write, so absorbed workspaces never momentarily disappear from the model.
  • Absorbed workspaces that cannot be moved (e.g., a second workspace on the absorbing Space's own working tree) are torn down: their ports released, surface views discarded, and transient state pruned.
  • Tests cover adoption, reunification, deduplication, and edge cases (unopened repositories, different sessions, Casper-created worktrees dropped and re-added).

https://claude.ai/code/session_01K9NWyCjBz5QJd37EP1n2YJ

claude added 2 commits July 28, 2026 09:16
"Add Folder…" on a folder that is a linked worktree of a repository already
open as a Space now adds it to that Space as a linked workspace instead of
creating a second Space for the same repository. Repository identity comes
from libgit2's common `.git` directory, which the main working tree and every
linked worktree share; when both are open, the main working tree wins, since
its folder is what worktree operations run against.

Nothing is created on disk — the branch and worktree already exist — so no
`setup` hook runs; the workspace takes the worktree's branch as its name and
the Space's primary branch as its merge base. Re-adding a folder Casper
already tracks now selects it rather than being silently dropped.

Deleting such a workspace resolves the worktree's registered admin-entry name
by path instead of assuming it matches the branch, so pruning an adopted
worktree leaves no dangling `.git/worktrees` entry.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01K9NWyCjBz5QJd37EP1n2YJ
The mirror image of worktree adoption: opening a folder that is a Git
repository whose worktrees are already open as Spaces of their own folds those
Spaces into the one being created, so a repository is a single Space either way
round — whichever of its working trees the user adds first.

The absorbed workspaces move whole: same ids, ports, layouts and live
terminals, nothing torn down or respawned. Only the fields that make a
workspace linked are normalized — each absorbed Space's own worktree stops
being a primary and takes its branch as its name (a Space is named after its
repository, which would merely duplicate the new primary's name), and a
workspace with no base branch of its own inherits the new primary's, while one
that already recorded a base keeps it. The Space list is rewritten in a single
assignment, so a reunified workspace is never momentarily absent from the
model.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01K9NWyCjBz5QJd37EP1n2YJ
@alexandreroman
alexandreroman merged commit ac2ebbb into main Jul 28, 2026
1 check passed
@alexandreroman
alexandreroman deleted the claude/casper-workspace-space-add-vrqomi branch July 28, 2026 10:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants