Files
Maximilian Roos 736606bac2 fix(copy-ignored): reach .worktreeinclude paths inside a wholly ignored directory (#4751)
`.worktreeinclude` now selects by git's own gitignore rules, including
paths inside a wholly ignored directory that an include line names, as
Claude Code does. `git ls-files --directory` lists such a directory as
one entry, so `.cache/claude-code-types/` under an ignored `.cache/`
matched nothing, and `copy-ignored` and `promote` skipped it. Claude
Code's own `.worktreeinclude` copy reaches it, and a team trialling the
Claude Code plugin lost that cache in every new worktree
(Rikmorn/sidekick#287).

Git now does all the matching. Discovery runs `git ls-files -o -i
--directory` once with the standard exclude sources and, when
`.worktreeinclude` exists, again with that file as the only source. An
entry is selected where the two listings overlap, taking the deeper path
of each pair where one holds the other. Inside a directory git lists
whole, only lines that name a path (a slash before the end, past any
leading `/` or `**/`) select, as found by a third listing of those lines
plus the negations. A name matched at any depth (`.env`, `types/`)
selects only outside such directories. Claude Code, which defines the
file, agrees on path-naming lines; it differs in opening a directory
from a line's text and then applying every line inside, so for
`packages/*/.cache/types/` plus `.env` it also copies
`packages/foo/node_modules/pkg/.env`. The `.worktreeinclude` parse error
goes away, since git takes any line as a pattern. So:

- A pattern naming a path inside an ignored directory (`.cache/types/`)
selects it, and its siblings stay behind.
- A pattern that matches at any depth (`.env`) selects the root `.env`
and any `.env` git lists on its own, but never a package's `.env` from
`node_modules/`.
- A negation can't re-include a path inside a selected directory.
- An include line naming a partly ignored directory (`build/` with only
`build/*.o` ignored) selects only its ignored files.

The cost is that each include listing walks every untracked directory:
0.33 s over 124k ignored files with a warm cache, against 0.03 s for the
standard listing. Without `.worktreeinclude`, discovery runs one listing
as before.

Reaching inside ignored directories needed boundaries that
whole-directory entries had made unnecessary:

- Built-in VCS and tool-state directories now exclude everything inside
them too, so `promote` no longer moves `.jj/repo`.
- `promote` refuses a destination that resolves outside its worktree
through a symlinked directory, and `copy-ignored` checks before creating
parent directories. The containment error now says "write" rather than
"copy".

The Claude Code page now says that hook-made worktrees ignore
`worktree.baseRef`, since the hook input carries the worktree's name and
no base.

Left alone, and older than this change: copying or promoting into a
destination where another worktree is checked out inside an ignored
directory writes into that worktree, with or without `.worktreeinclude`.

> _This was written by Claude Code on behalf of max-sixty_
2026-11-12 01:33:49 -08:00
..