Skip to content

Add pj project repair to fix worktrees after a move/rename#28

Merged
kevdoran merged 2 commits into
mainfrom
project-repair
Jul 4, 2026
Merged

Add pj project repair to fix worktrees after a move/rename#28
kevdoran merged 2 commits into
mainfrom
project-repair

Conversation

@kevdoran

Copy link
Copy Markdown
Owner

What & why

Closes #23. When a project directory is moved or renamed, every git worktree inside it gets stale gitdir/commondir pointers and git operations in those worktrees start failing. This PR streamlines fixing that.

Changes

  • internal/git: new thin wrapper WorktreeRepair(repoPath string, worktreePaths ...string) error that runs git worktree repair [paths...] via RunGit. Passing the worktree paths lets git locate worktrees whose directory has moved.
  • cmd/projector/repair.go: new pj project repair [project] subcommand (newRepairCmd), registered in projects.go alongside the other project subcommands. It resolves the project via the shared resolveProject helper (name arg or current directory, same as archive/desc), then runs repair on every discovered worktree and prints a clear per-worktree summary. Non-active projects are rejected; an empty project is reported and left untouched; per-worktree failures are collected and surfaced.
  • Tests (internal/git/git_test.go): TestWorktreeRepair creates a repo + worktree under a common parent, renames the parent (the real "project moved" case), asserts the worktree is broken before repair and healthy after, and checks git worktree list now references the new path. TestWorktreeRepair_NoWorktrees confirms repair is a safe no-op. Added a makeRepoAt(t, dir) helper (used by createTestRepo) so a repo can be created at a specific path.
  • Docs: README command table, docs/commands.md (full section with example output), pj-skill.md, and CLAUDE.md (cmd/projector row + WorktreeRepair in the internal/git list).

Optional auto-repair (secondary scope) — NOT included

I deliberately did not add opportunistic auto-repair into the archive flow. Detecting "stale vs. genuinely missing/dirty" worktrees during archive would mean touching the pre-validation and rollback logic in archive.go, which is the most fragile path in the codebase (it captures detached HEAD SHAs and rolls back partial removals). Per the issue's scope guidance, I kept it out to avoid scope creep. Follow-up idea: call WorktreeRepair opportunistically when DiscoverWorktrees or a status check fails to resolve a worktree, as a separate focused PR.

Verification

  • go build -o /tmp/pj-repair ./cmd/projector — pass
  • go vet ./... — pass
  • go test -race -count=1 ./... — pass (all packages)
  • Manual smoke test: built binary, created a project layout with a worktree, renamed the project dir, ran pj project repair <name> — repaired cleanly with the expected per-worktree output.

For a reviewer to scrutinize

  • In the common pj layout the source repo lives outside the project dir (in repo-search-dirs), so renaming only the project dir does not break the worktree's .git file (it references the external repo by absolute path) — in that case repair is a harmless no-op. The breakage repair actually fixes is when the repo and worktree move together, which the unit test exercises. Worth confirming this matches the intended use case in Automate git worktree repair #23.
  • WorktreeRepair runs from wt.RepoPath (the underlying repo) with the worktree path as an argument; double-check that's the right anchor vs. running from the worktree itself.

🤖 Generated with Claude Code

kevdoran and others added 2 commits June 22, 2026 20:33
Moving or renaming a project directory leaves each worktree's internal
gitdir/commondir pointers stale, breaking git operations inside them. Add
a `pj project repair [project]` subcommand that discovers every live
worktree in an active project and runs `git worktree repair` on each,
printing a per-worktree summary.

- internal/git: add `WorktreeRepair(repoPath, worktreePaths...)` wrapper
  plus integration tests that move a project (repo + worktree) and verify
  the worktree is broken before repair and healthy after.
- cmd/projector: add `repair.go` (`newRepairCmd`), registered in
  projects.go. Resolves the project via the shared `resolveProject`
  helper; rejects non-active projects and handles the empty-project case.
- docs: README, docs/commands.md, pj-skill.md, CLAUDE.md updated.

Closes #23

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The existing test passes the post-move repo path directly, which
the command never does. Add a test for pj's common layout (external
repo, renamed project dir) where the repo's worktree admin pointer
goes stale and `git worktree repair` fixes it.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

@kevdoran kevdoran left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review: LGTM ✅ (comment-only — reviewer account is also the PR author, so GitHub disallows a formal approval)

What was verified

  • It fixes a real breakage, not a no-op. Reproduced pj's common layout (repo external in a repo-search-dir, worktree under the project dir, then only the project dir renamed): the worktree's own .git file stays valid so git status inside it keeps working, but the repo's admin pointer (.git/worktrees/<id>/gitdir) goes stale and git worktree list marks the worktree prunable — one git worktree prune away from losing the registration. The command's exact invocation (git worktree repair <newPath> anchored at wt.RepoPath) repairs that pointer. Confirmed with raw git and with the built binary.
  • Anchor choice is correct. Running repair from inside the worktree would not fix the repo-side admin file in the moved case; anchoring at the repo with the worktree path as arg does.
  • Tests are real. TestWorktreeRepair_RenamedProjectDir asserts the breakage (prunable, old path) before repair and its absence after; it was validated to FAIL when the repair call is stubbed out. TestWorktreeRepair is clearly commented as exercising the wrapper (repo+worktree moved together), not the command's discovery path.
  • Conventions: git op via internal/git wrapper; own subcommand file; shared resolveProject; %w wrapping; per-worktree failure aggregation (one failure doesn't abort the rest; exit code reflects it); non-active and empty projects handled. Docs updated in README/docs/commands.md/pj-skill.md/CLAUDE.md.
  • Build/vet/go test -race ./... pass locally post-rebase; CI Build + Test green. Rebased onto main after #27; the CLAUDE.md conflict was resolved to keep both changes.

Known limitations (documented, acceptable)

  • If the repo itself moves (repo inside the project dir), DiscoverWorktrees reads the stale .git file and can't find the live repo, so repair fails with a path error. Normal pj usage keeps repos external, so this is an edge case — reasonable follow-up would be repo rediscovery via repo-search-dirs.
  • The final summary prints the project name stored in .projector.toml, which a directory rename doesn't update — pre-existing pj behavior, not introduced here.

Ready to merge.

🤖 Generated with Claude Code

@kevdoran
kevdoran merged commit f29e195 into main Jul 4, 2026
2 checks passed
@kevdoran
kevdoran deleted the project-repair branch July 4, 2026 11:48
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.

Automate git worktree repair

1 participant