Skip to content

fix(projects): resolve git worktree .git files in detect_project - #12

Open
GetsEclectic wants to merge 1 commit into
dschartman:mainfrom
GetsEclectic:fix/worktree-detect-project
Open

fix(projects): resolve git worktree .git files in detect_project#12
GetsEclectic wants to merge 1 commit into
dschartman:mainfrom
GetsEclectic:fix/worktree-detect-project

Conversation

@GetsEclectic

Copy link
Copy Markdown

Problem (data loss)

Any trc create / close / update run from inside a git worktree
silently truncates the parent checkout's .trace/issues.jsonl to a per-worktree
(usually empty) subset — dropping every issue the main checkout had.

Reproduction:

cd repo                       # main checkout: N issues
git worktree add wt && cd wt
trc create "anything"         # any write-path command
wc -l ../.trace/issues.jsonl  # → 1   (N-1 issues silently lost)

This bites hard with parallel agents, each working in its own worktree.

Root cause

In a worktree, .git is a file containing gitdir: <path>, not a
directory. detect_project() walked up to that .git and Path.exists()
returned True, so it treated the worktree dir as a repo root:

  • project_path became the worktree dir.
  • _extract_*_from_git_remote(git_dir) opened git_dir / "config", which
    doesn't exist (git_dir is a file), so both returned None and the fallbacks
    assigned the worktree path/name as a brand-new project identity.

Every worktree thus registered as a new project, and the next write called
export_to_jsonl, which does path.open("w") and writes only the
(new, empty) worktree-project's issues — clobbering the real file.

Fix

Add _resolve_git_dir() which resolves a .git pointer file the way git
itself does — follow gitdir: → read commondir → locate the canonical
.git → return the main checkout root. detect_project() now uses it, so a
worktree resolves to the same id / name / path as the main checkout.
config is then read from the canonical .git, restoring correct remote-based
identity. Concurrent worktrees agree on one project identity, matching the
JSONL-line-merge model the project already relies on.

Tests

tests/test_worktree_detect_project.py:

  1. _resolve_git_dir resolves a hand-built worktree pointer to canonical
    .git + main root (no git binary needed).
  2. detect_project() from inside a real git worktree add returns identity
    identical to the main checkout (same id, name, and path).

Follow-up (separate PR)

Even with identity fixed, .trace/issues.jsonl still physically lives under
whatever .trace/ is in the current worktree. Teaching sync_project /
export_to_jsonl to always write to the project root's .trace/ would
fully de-race concurrent worktrees. Gated on this PR.

Any `trc` write from inside a git worktree silently truncated the parent
checkout's .trace/issues.jsonl. In a worktree, `.git` is a *file*
(`gitdir: <path>`), not a directory; detect_project() accepted that file as
a repo root, so _extract_*_from_git_remote() found no config, the fallbacks
registered the worktree as a brand-new project, and the next export_to_jsonl
clobbered the real file with the (empty) worktree-project's issue set.

Add _resolve_git_dir() which resolves a worktree pointer the way git does
(follow gitdir: -> read commondir -> canonical .git -> main checkout root),
so a worktree resolves to the same id/name/path as the main checkout.

Adds tests/test_worktree_detect_project.py (no git binary needed for the
unit-level resolver test; a real `git worktree add` for the integration test).
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.

1 participant