Skip to content

feat(pool): seed worktrees with ignored files - #48

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

feat(pool): seed worktrees with ignored files#48
nikolauska wants to merge 3 commits into
kunchenguid:mainfrom
nikolauska:feature/worktreeinclude

Conversation

@nikolauska

@nikolauska nikolauska commented Jul 1, 2026

Copy link
Copy Markdown

Intent

Add .worktreeinclude support so Treehouse seeds new worktrees with selected ignored or untracked files safely and atomically, preserving file modes and symlinks, rejecting unsafe patterns and paths, remaining cross-platform including Windows, and documenting the feature. Validate the amended current local HEAD. Review or CI findings may be accepted without fixes because the user will inspect them later.

What Changed

  • Seed ignored or untracked files selected by a committed .worktreeinclude whenever a worktree is created or reused.
  • Preserve file permissions, safely materialize symlink targets, block tracked-path and checkout-escape writes, and quarantine worktrees after failed seeding or cleanup.
  • Document .worktreeinclude patterns, exclusions, refresh behavior, and failure handling.

Risk Assessment

🚨 High: The implementation contradicts two explicit acceptance criteria and contains a reachable tracked-path collision that can make acquisition repeatedly fail and quarantine worktrees.

Testing

Focused seeding and acquisition tests passed, Windows test binaries compiled, and an end-to-end lease acquisition demonstrated selected ignored files being seeded, exclusions remaining absent, executable mode preservation, and safe symlink flattening; the worktree remained unchanged.

Evidence: End-to-end .worktreeinclude CLI transcript
acquired_path=/tmp/treehouse-worktreeinclude-e2e.UnZuLO/pool/.treehouse/repo-bdee25/1/repo
seed.env=seed-value
excluded.env_exists=no
bin/tool_mode=700
bin/tool_output=seeded
linked.env_kind=regular-file
linked.env_contents=seed.env
tracked_manifest_present=yes

Pipeline

Updates from git push no-mistakes

✅ **intent** - passed

✅ No issues found.

✅ **Rebase** - passed

✅ No issues found.

⚠️ **Review** - 3 errors
  • 🚨 internal/git/git.go:280 - Intent requires “preserving file modes and symlinks,” but this branch converts every selected symlink into a regular 0666 file containing the target text. For a selected linked.env -> ../shared.env, the destination receives a plain text file rather than the symlink. Either preserve safe symlinks while rejecting escaping targets, or confirm that flattening symlinks is the intended requirement.
  • 🚨 internal/git/git.go:297 - Intent requires seeding “atomically,” but each destination is removed and recreated directly, and multiple files are installed sequentially. An observer can therefore see a missing, partial, or mixed old/new seed set; interruption after Remove can also leave the old file lost. Stage the complete seed set and commit it through an atomic replacement boundary, or explicitly confirm that quarantine-based containment is acceptable instead of atomic seeding.
  • 🚨 internal/git/git.go:223 - A selected source file is checked only against exact tracked paths and tracked ancestors, not tracked descendants. If the target branch tracks config/settings.json while the source checkout has a selected ignored file named config, isTracked("config") returns false; seeding then tries to replace the tracked config directory and quarantines every acquired worktree. Include tracked descendants in the collision check so the selected file is skipped.
✅ **Test** - passed

✅ No issues found.

  • go test ./internal/git ./internal/pool -run 'Test(SeedWorktree|Acquire_Seed|Acquire_Quarantine|Acquire_Seeding|Acquire_DoesNotSeed|AcquireDoesNotExecuteSeedFilter|AcquireIgnoresBrokenRequiredSeedFilter)' -count=1
  • Built Treehouse and ran treehouse get --lease --no-fetch --root <temporary-pool> against a temporary Git repository containing .worktreeinclude selections, negation, an executable file, and a symlink; inspected the acquired worktree and saved the transcript.
  • GOOS=windows GOARCH=amd64 go test -c ./internal/git -o <temporary>/git-tests.exe
  • GOOS=windows GOARCH=amd64 go test -c ./internal/pool -o <temporary>/pool-tests.exe
  • git status --short to confirm testing left the worktree unchanged.
✅ **Document** - passed

✅ No issues found.

✅ **Lint** - passed

✅ No issues found.

✅ **Push** - passed

✅ No issues found.

@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.

@nikolauska
nikolauska force-pushed the feature/worktreeinclude branch from f7b2fe2 to 4f7b2a7 Compare July 7, 2026 18:13
@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.

@nikolauska
nikolauska force-pushed the feature/worktreeinclude branch from 2bdbbfe to c650ff6 Compare July 20, 2026 21:08
@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
@mbrookson

Copy link
Copy Markdown

@kunchenguid can this be added to the next release? 🙏

@kunchenguid

Copy link
Copy Markdown
Owner

Speaking as Kun's firstmate: this branch currently has a merge conflict with main (mergeable_state: dirty). Please rebase onto latest main, resolve, and push so CI can re-run. The .worktreeinclude seeding is opt-in (missing manifest is a no-op) so I am not closing it; I will not merge until it is conflict-free and CI is green on current main.

@nikolauska
nikolauska force-pushed the feature/worktreeinclude branch from eacd8c8 to 7f6df41 Compare August 19, 2026 19:44
@greptile-apps

greptile-apps Bot commented Aug 19, 2026

Copy link
Copy Markdown

Confidence Score: 3/5

The PR is not yet safe to merge because reused worktrees can retain obsolete seeds and a failed quarantine write can leave a partially seeded worktree available.

ResetWorktree preserves ignored files while SeedWorktree only refreshes current selections, so removed or negated seeds survive reuse; additionally, quarantine of a failed reused seed is not durable when WriteState fails, allowing the persisted unleased entry to be selected again.

Files Needing Attention: internal/git/git.go and internal/pool/pool.go

Reviews (15): Last reviewed commit: "no-mistakes(document): Document .worktre..." | Re-trigger Greptile

Comment thread internal/git/git.go Outdated
Comment thread internal/git/git.go Outdated
@nikolauska
nikolauska force-pushed the feature/worktreeinclude branch 2 times, most recently from f2cc1b9 to a1a6e95 Compare August 19, 2026 21:49
@kunchenguid

Copy link
Copy Markdown
Owner

Speaking as Kun's firstmate: thanks for the rebase. This is now mergeable (not dirty). I approved fork CI for a1a6e953. Greptile is 5/5 on that SHA.

no-mistakes is red: "This PR was not raised through no-mistakes." Contributions need git push no-mistakes so the body gets the ## Pipeline section. That is blocking here (#51 is the only known pre-requirement exception). I will not merge until that gate is green. OS tests are still running.

@nikolauska nikolauska changed the title feat(pool): Add .worktreeinclude support feat(pool): seed gitignored files into pooled worktrees Aug 20, 2026
@kunchenguid

Copy link
Copy Markdown
Owner

Speaking as Kun's firstmate: newer activity is the no-mistakes raise (Pipeline section is now on the body) plus Greptile still 5/5 on 573e7c6. Opt-in .worktreeinclude remains in scope.

VISION (per rule):

  • Isolation and reuse: aligns if seeding does not change ownership or recycle a dirty/in-use slot.
  • Safe lifecycle operations: aligns only if destinations stay inside the worktree (symlink-safe) and tracked files are not overwritten.
  • Ownership and interface: aligns as opt-in manifest; missing file is a no-op.
  • Scope and evaluation: aligns as optional local seeding, not a Git workflow manager. Repository-controlled code must not execute automatically; this copies files, it does not run them.

Approved current-head CI 32408269816 and no-mistakes 32408269766. Not merging until those runs are actually green. Helping this PR; not opening a second path for #39.

@nikolauska nikolauska changed the title feat(pool): seed gitignored files into pooled worktrees feat(git): seed ignored files into acquired worktrees Aug 20, 2026
Comment thread internal/git/git.go Outdated
Comment thread internal/pool/pool.go Outdated
Comment thread internal/pool/pool.go Outdated
Comment thread internal/git/git.go Outdated
Comment thread internal/pool/pool.go Outdated
Comment thread internal/git/git.go Outdated
Comment thread internal/pool/pool.go Outdated
Comment thread internal/git/git.go Outdated
Comment thread internal/git/git.go Outdated
Comment thread internal/git/git.go Outdated
@nikolauska
nikolauska force-pushed the feature/worktreeinclude branch from 2821c7d to bbe58c4 Compare August 20, 2026 22:37
Comment thread internal/git/git.go
@greptile-apps

greptile-apps Bot commented Aug 20, 2026

Copy link
Copy Markdown

Want your agent to iterate on Greptile's feedback? Try greploops.

@nikolauska
nikolauska force-pushed the feature/worktreeinclude branch from bbe58c4 to 52e6b80 Compare August 20, 2026 22:45
Comment thread internal/git/git.go Outdated
@nikolauska
nikolauska force-pushed the feature/worktreeinclude branch from 52e6b80 to 5c6e7c3 Compare August 20, 2026 22:53
Comment thread internal/git/git.go
@nikolauska
nikolauska force-pushed the feature/worktreeinclude branch from 5c6e7c3 to f5449ea Compare August 20, 2026 23:05
Comment thread internal/git/git.go Outdated
Comment thread internal/pool/pool.go Outdated
@kunchenguid

Copy link
Copy Markdown
Owner

Speaking as Kun's firstmate: approved current-head CI 32427178130 and Require no-mistakes 32427178081 for f5449ea. I inspected SeedWorktree and acquire on this SHA: seeding now runs inside WithStateLock; a failed reuse quarantines the slot; a failed new slot is removed; copies go through os.OpenRoot / Lstat and do not run Git content filters.

VISION (per rule):

  • Isolation and reuse: mixed. The old post-lock write race is closed on this head, but Greptile is still 3/5 with open P1s (unselected ignored files, tracked-ancestor replacement, partial seeds on reuse).
  • Safe lifecycle operations: mixed. Rooted copies stay inside the worktree if OpenRoot holds. ensureRootedDir can still RemoveAll a non-directory ancestor; isTracked now walks parents so a tracked file ancestor should skip, but Greptile still flags that path.
  • Ownership and interface: aligns as opt-in .worktreeinclude; missing file is a no-op. Failed seed quarantines rather than recycling.
  • Scope and evaluation: aligns as optional local file copy, not a Git workflow manager. This head does not execute repository-configured filters.

test (macos-latest) is red and Greptile is blocking (including a security-tagged thread on unselected ignored files). Please address those P1s and the macos failure. Not merging. Helping this PR; not opening a second path for #39.

@nikolauska
nikolauska force-pushed the feature/worktreeinclude branch from f5449ea to c8110ee Compare August 20, 2026 23:24
Comment thread internal/git/git.go
Comment thread internal/pool/pool.go
@nikolauska nikolauska changed the title feat(git): seed ignored files into acquired worktrees feat(pool): seed worktrees with ignored files Aug 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.

3 participants