Skip to content

Make forks leave out what .gitignore files ignore - #6

Merged
penberg merged 2 commits into
mainfrom
fork-skip-ignored
Nov 1, 2026
Merged

penberg merged 2 commits into
mainfrom
fork-skip-ignored

Conversation

@penberg

@penberg penberg commented Oct 30, 2026 •

Copy link
Copy Markdown
Owner

A fork was everything in the directory, so a large node_modules or target made forking slow. Now a fork and its base leave out what .gitignore files ignore and git does not track. --no-ignore, for fsfork and confine, forks everything as before. The name is the one rg and fd use.

Measured on APFS with a debug build:

Directory --no-ignore Default
500 source files, 200,000 ignored in node_modules 4.7–7.1 s 0.10–0.13 s
This repository (244 MB target, about 6,000 files) 0.29–0.48 s 0.09–0.17 s

Asking git adds a fixed cost, so a directory with very few files forks more slowly than before: about 0.15 s for a one-file directory.

Behaviour changes

  • Cold builds: builds in a fork no longer start warm unless --no-ignore is given.
  • Ignored files are missing: this includes files such as .env, which a command in the fork may need.

How it works

  • What counts as ignored: only .gitignore files, as in fsdiff, not .git/info/exclude or a global excludes file. Tracked files stay even if a pattern matches them.
  • Subdirectories: a subdirectory of a repository also goes by the .gitignore files above it. A directory that is itself ignored goes by its own only.
  • If git can't answer: if git is missing or fails, for example on a corrupt index, fsfork warns on standard error and forks everything.
  • Unchanged parts: directories with nothing ignored in them are still cloned whole, and the fork is still cloned from the base.

Security

confine forks outside the sandbox, and the directory may be a fork that a confined command changed, so git must not use its configuration: core.fsmonitor alone would run a command. Git runs on a temporary repository with no configuration and reads only .gitignore files and the index. There is a test that a configured core.fsmonitor does not run.

Limitations

  • A directory with something ignored in it is made anew, keeping its permissions and modification time but not its other attributes.
  • On macOS, clonefile rejects a FIFO or socket on its own, though it takes them inside a directory. Next to something ignored, FIFOs are remade, and sockets and devices are left out.
  • This branches from main, and will touch the same lines of confine.rs and confine.1 as Make confine run gh for the command, each time you allow it #5, so whichever lands second needs a small rebase.

Testing

cargo test, cargo clippy and cargo fmt pass on macOS, and I checked by hand that repositories with a split or sparse index fork correctly. CI runs the same tests on Linux, where the path through cp is exercised; speed there is unmeasured.

A fork is everything in the directory, so a directory with a large
`node_modules` or `target` takes seconds to fork, twice over, as the fork
and its base each clone every file in it. On APFS, 200,000 ignored files
took about 5 seconds.

With the new `--skip-ignored` option of `fsfork` and `confine`, what
`.gitignore` files ignore and git does not track is left out of the fork
and its base, which took about 0.1 seconds on the same directory. It is
an option, because warm builds are why a fork has build output.

Git says what is ignored, so that the rules are its own, and the same as
`fsdiff` goes by. It runs on a repository of `fsfork`'s own with no
configuration, as `fsdiff`'s does, because the directory could be a fork
that a confined command changed, and `confine` forks outside the sandbox.
It reads the directory's index, so that tracked files stay. Directories
with nothing ignored in them are still cloned whole, at once.
A fork of a directory with a large `node_modules` or `target` should be
quick without having to ask for it, so what `.gitignore` files ignore is
now left out unless `--no-ignore` is given, named as in `rg` and `fd`.
`--skip-ignored` is gone.

As `fsfork` now needs git by default, it warns and forks everything if
git is not there or cannot tell what is ignored: a fork with too much in
it works, and refusing to make one would not.
@penberg penberg changed the title Make fsfork and confine able to leave out what .gitignore files ignore Make forks leave out what .gitignore files ignore Oct 30, 2026
@penberg
penberg merged commit aa59cc4 into main Nov 1, 2026
2 checks passed
@penberg
penberg deleted the fork-skip-ignored branch November 1, 2026 15:40
penberg added a commit that referenced this pull request Nov 3, 2026
A fork is otherwise named after the directory and the first number not
taken, `turso.1`, which says nothing about what it is for. With `-n
swebench`, it is `turso.swebench`, and its base `.turso.swebench.base`,
so that forks made for different things can be told apart, and a script
can know where its fork is without parsing `fsfork`'s output. A named
fork takes no number.

```sh
cd $(fsfork -n swebench)
confine -n swebench -w ~/.claude claude
```

The name cannot be empty or have `/` in it, as the fork goes next to the
directory, and cannot be given with a second argument, which says where
the fork goes instead. In `confine`, it cannot be given with `-d`, which
runs the command without a fork.

Stacked on #6, as it builds on `Contents` and `--no-ignore` from there.
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.

1 participant