Skip to content

feat(pool): Add .worktreeinclude support#48

Open
nikolauska wants to merge 6 commits into
kunchenguid:mainfrom
nikolauska:feature/worktreeinclude
Open

feat(pool): Add .worktreeinclude support#48
nikolauska wants to merge 6 commits into
kunchenguid:mainfrom
nikolauska:feature/worktreeinclude

Conversation

@nikolauska

@nikolauska nikolauska commented Jul 1, 2026

Copy link
Copy Markdown

Intent

Resolve #39 by allowing repositories to seed required gitignored files into pooled worktrees through a committed .worktreeinclude manifest.

Changes

  • Copy matching gitignored files from the main checkout on worktree creation and reuse
  • Use Git to enumerate ignored candidates, including file-level negations
  • Support negated subtrees such as !local/archive/
  • Honor later include patterns after a negated subtree
  • Check candidates against the acquired worktree’s index so tracked files are never overwritten, even when the source and target branches differ
  • Reject destination paths containing symlinks so seeded writes cannot escape the worktree
  • Preserve copied file permissions
  • Run seeding after releasing the pool state lock
  • Read .worktreeinclude once per acquisition
  • Document the feature and add regression coverage for creation, reuse, negations, pattern precedence, tracked files, cross-branch tracking differences, and symlink safety

Failure behavior

Seeding is fail-open:

  • A missing .worktreeinclude manifest is a no-op
  • Selection, read, write, permission, or unsafe-symlink failures emit a warning to stderr
  • A failure stops the current seed pass, so files copied earlier remain while later candidates may be unavailable
  • Acquisition still succeeds, post-create hooks still run, and the worktree path is returned

Risk assessment

Low to moderate.

  • The feature is opt-in; repositories without .worktreeinclude retain existing behavior
  • Files tracked by the acquired worktree cannot be overwritten
  • Existing symlinks in destination paths are rejected before writing
  • A seeding error can leave the worktree only partially seeded, but acquisition remains usable and reports the warning
  • Cross-platform path handling uses Go’s filepath package

Validation

  • Regression tests cover symlink safety, target-branch tracked files, and ordered pattern precedence
  • make lint passes
  • go test ./... passes
  • GOOS=windows go build ./... passe

@mbrookson mbrookson left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Nice implementation — using git ls-files + check-ignore for pattern matching is the right call over rolling our own glob expansion. A few observations from independently building the same feature (closed #50 in favour of this):

Seeding runs inside the state lock

Both call sites invoke SeedWorktree inside the WithStateLock closure, which means the flock is held for the duration of the file copies. For repos with large ignored files this could block concurrent treehouse commands unnecessarily. The post-create hooks already run after the lock is released — seeding feels like it belongs there too, since it's not mutating pool state. Something like:

// after WithStateLock returns, before hooks.Run
if err := git.SeedWorktree(repoRoot, acquired); err != nil {
    fmt.Fprintf(os.Stderr, "warning: .worktreeinclude: %v\n", err)
}

(Whether to warn or hard-fail is a separate question — see below.)

excludedIncludeSubtree only handles !dir/ negations, not !file

The custom parser skips lines matching !<dir>/ but a bare negation like !.env.local won't be excluded. Given the function comment says "Git does not descend into an excluded directory", I think this is intentional — but it's worth a doc comment clarifying that file-level negations aren't supported so users don't write !secrets/prod.json and wonder why it still gets copied.

Seed errors abort acquire for the missing-file case

If .worktreeinclude lists .env but a contributor doesn't have that file locally (common on a fresh clone), git ls-files returns nothing for it and the seed silently skips it — so this is actually fine in practice. The error path that would bite is an OS-level failure mid-copy. Current behaviour (abort acquire) is defensible, though a warning-and-continue would be more resilient. Worth a deliberate decision either way.

@nikolauska

Copy link
Copy Markdown
Author

Thanks for the review. I moved seeding outside the state lock and changed OS-level copy failures to warn without failing the acquire. I also clarified that Git handles file-level negations directly and added coverage for !.env.local.

@kunchenguid

kunchenguid commented Jul 20, 2026

Copy link
Copy Markdown
Owner

Automated reminder: thanks for the PR! This branch currently has a merge conflict with the base branch.

When you get a chance, please rebase onto (or merge) the latest base branch, resolve the conflict, and push. After that, checks will re-run and the PR will get looked at again.

Noted for treehouse#48 at 2bdbbfe7.

Seed acquired worktrees after releasing the pool lock to avoid blocking concurrent
commands during file copies. Treat seeding failures as warnings and document and test
file-negation behavior
Keep text assertions portable across Git's Windows checkout conversion.
@nikolauska
nikolauska force-pushed the feature/worktreeinclude branch from 2bdbbfe to c650ff6 Compare July 20, 2026 21:08
Prevent reused ignored paths from redirecting seeded writes outside the worktree.
Filter seed candidates against the acquired worktree index so branch differences cannot dirty it.
Apply later include patterns after directory negations and read the manifest only once.
@nikolauska

nikolauska commented Jul 20, 2026

Copy link
Copy Markdown
Author

Rebased onto the latest main and fixed several issues found during review of .worktreeinclude seeding:

  • Symlink safety: Reused worktrees preserve ignored files. If a seeded destination had been replaced with a symlink, the next acquire could follow it and overwrite a file outside the worktree. Seeding now rejects paths containing symlinks.
  • Tracked-file protection: Seed candidates were selected using the source checkout’s index. When the acquired worktree targeted a different branch, a candidate could be tracked there and get overwritten. Candidates are now checked against the acquired worktree’s index first.
  • Pattern precedence: A directory negation such as !local/archive/ permanently excluded that subtree, even when a later pattern explicitly included a file within it. Later matching patterns now take precedence as expected.
  • Manifest handling: .worktreeinclude was checked and then read separately. It is now read once, removing the redundant filesystem operation.

@kunchenguid kunchenguid removed the wheelhouse:pending-contributor-action Managed by Wheelhouse label Jul 20, 2026
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.

Feature request: .worktreeinclude manifest for seeding gitignored files into pooled worktrees

3 participants