Add pj project repair to fix worktrees after a move/rename#28
Merged
Conversation
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
commented
Jul 4, 2026
kevdoran
left a comment
Owner
Author
There was a problem hiding this comment.
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
.gitfile stays valid sogit statusinside it keeps working, but the repo's admin pointer (.git/worktrees/<id>/gitdir) goes stale andgit worktree listmarks the worktreeprunable— onegit worktree pruneaway from losing the registration. The command's exact invocation (git worktree repair <newPath>anchored atwt.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_RenamedProjectDirasserts the breakage (prunable, old path) before repair and its absence after; it was validated to FAIL when the repair call is stubbed out.TestWorktreeRepairis clearly commented as exercising the wrapper (repo+worktree moved together), not the command's discovery path. - Conventions: git op via
internal/gitwrapper; own subcommand file; sharedresolveProject;%wwrapping; 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),
DiscoverWorktreesreads the stale.gitfile 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 viarepo-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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What & why
Closes #23. When a project directory is moved or renamed, every git worktree inside it gets stale
gitdir/commondirpointers and git operations in those worktrees start failing. This PR streamlines fixing that.Changes
internal/git: new thin wrapperWorktreeRepair(repoPath string, worktreePaths ...string) errorthat runsgit worktree repair [paths...]viaRunGit. Passing the worktree paths lets git locate worktrees whose directory has moved.cmd/projector/repair.go: newpj project repair [project]subcommand (newRepairCmd), registered inprojects.goalongside the other project subcommands. It resolves the project via the sharedresolveProjecthelper (name arg or current directory, same asarchive/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.internal/git/git_test.go):TestWorktreeRepaircreates 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 checksgit worktree listnow references the new path.TestWorktreeRepair_NoWorktreesconfirms repair is a safe no-op. Added amakeRepoAt(t, dir)helper (used bycreateTestRepo) so a repo can be created at a specific path.docs/commands.md(full section with example output),pj-skill.md, andCLAUDE.md(cmd/projector row +WorktreeRepairin 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: callWorktreeRepairopportunistically whenDiscoverWorktreesor a status check fails to resolve a worktree, as a separate focused PR.Verification
go build -o /tmp/pj-repair ./cmd/projector— passgo vet ./...— passgo test -race -count=1 ./...— pass (all packages)pj project repair <name>— repaired cleanly with the expected per-worktree output.For a reviewer to scrutinize
repo-search-dirs), so renaming only the project dir does not break the worktree's.gitfile (it references the external repo by absolute path) — in that case repair is a harmless no-op. The breakagerepairactually 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.WorktreeRepairruns fromwt.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