4ca0c170b5 test(testing-utils): detach git fixtures from the contributor's global config (#14715)
* test(testing-utils): detach git fixtures from the contributor's global config

`pipeline_cache::symlinked_inputs_are_hashed_as_link_targets` builds a
project whose tracked input is a symlink named `CLAUDE.md`, and asserts
that retargeting the link invalidates the task. On a machine whose
`core.excludesFile` ignores that name, `git add -A` never stages the link
and the `git ls-files --cached --others --exclude-standard` pnpm runs to
collect cache inputs never lists it. The link is then not part of the
task's cache key, retargeting it changes nothing, and pnpm correctly
reports the hit the test refuses.

The symlink hashing the test covers is right, so the fixture is what
needs fixing. `detach_from_global_config` points `core.excludesFile` at a
path that does not exist, in the repo's own local configuration. Local
configuration also reaches the `git` pnpm itself spawns inside the repo,
so one setting covers the fixture's staging and the listing under test.
The path is inside `.git/info` rather than `/dev/null` so it holds on
Windows too. The `gpgsign` overrides `GitRepoFixture::init` already made
for the same reason move in beside it.

`init_isolated_repo` gives the two suites that call `git init` directly
the same isolation without the work tree and bare clone pair
`GitRepoFixture` exists to provide.

`symlinked_input_project` now checks that the link is staged before any
caching assertion runs, because the failure this produced named neither
git nor the ignore rule that caused it.

Closes pnpm/pnpm#14713

* fix(cargo): gate the non-UTF-8 name test's imports with the test

`import_package`, `CheckoutPackage`, and `BTreeSet` are referenced only
by `a_file_whose_name_is_not_utf8_is_refused`, which is compiled out on
macOS because APFS answers `EILSEQ` for the name it needs. At module
scope they were dead imports there, so `cargo clippy -D warnings` failed
on macOS while passing on Linux.

* test(testing-utils): narrow the fixture isolation contract to what it delivers

`init_isolated_repo` promised that a contributor's global git
configuration could not reach the repo, which overstated three
`git config` overrides. Full isolation is not available here: the point
of writing the overrides into the repo's local configuration is that they
also govern the `git` pnpm spawns inside it, which an environment
variable on the fixture's own commands would miss. So the doc now names
the settings it handles and says that anything else still reaches the
repo, and `detach_from_global_config` becomes `override_global_config` to
match.

`core.hooksPath` joins the overridden set. A contributor who points it at
a hooks directory globally would otherwise have their own hooks, which
are arbitrary code, run on a fixture's commits.

`init_isolated_repo` passes `-b main` rather than leaving the branch name
to `init.defaultBranch`, as `GitRepoFixture::init` already does.

`tracked_files` replaces the hand-rolled `git ls-files` in
`symlinked_input_project`, which read stdout and ignored the exit status.
A failing `git` would have surfaced as an empty listing, blaming the
ignore rule the assertion exists to detect for what was a git error. The
existing `git` helper already asserts the status and carries stderr into
the panic.

* Fix typo in documentation comment for git_repo.rs

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

* test(testing-utils): isolate the bare repo and every suite that commits

Running the git-backed suites with `GIT_CONFIG_GLOBAL` pointed at a
hostile configuration (a global excludes file, a `core.hooksPath` whose
hooks all fail, `init.defaultBranch = trunk`, `gpgsign = true`) showed
three gaps the fixture isolation still left open.

`GitRepoFixture` overrode the work tree only. `git push` into the bare
repo runs `pre-receive` and `update` from that repo's own
`core.hooksPath`, so a contributor's global hooks rejected every mirror.
The bare repo now gets the same overrides, and `override_global_config`
takes the git directory so the placeholder path lands inside it for
both repo shapes. The bare init also passes `-b main`: without it,
`HEAD` inherits `init.defaultBranch` and points at a branch the mirror
never pushes.

`pipeline_cargo_cache` and `exec_recursive` also `git init` their own
repos and commit into them, and both failed on the global `pre-commit`
hook. They now go through `init_isolated_repo`, which makes their
per-command `-c user.name`, `-c commit.gpgsign=false`, and
`--no-gpg-sign` overrides redundant.

`tracked_files` listed tracked and untracked-but-not-ignored files, so
it is now `unignored_files`.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XPJkFm8WwhDuQpvC5CAmBo

* test(testing-utils): keep global templates and attributes out of fixtures

Two more user-global settings reached the fixture repos.

`init.templateDir` seeds every new repo's `info/exclude`, and no
configuration setting overrides that file, so a template that ignores a
fixture name hid it from `git add -A` and from the listing pnpm hashes
exactly as the global excludes file did. Every fixture `git init` now
passes `--template=`, which skips the template directory entirely.

`core.attributesFile` can assign a `clean` filter to a fixture's files,
and git runs that filter, the contributor's own command, on `git add`.
The override now points it at the same absent path as the excludes file
and hooks directory.

Verified under a global config carrying a template whose `info/exclude`
lists `CLAUDE.md` and a required `*.md` clean filter that exits 1: the
seven git-backed suites and the `cargo_deps::git` unit tests pass, and
a repo initialised the old way loses the file and fails the add.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XPJkFm8WwhDuQpvC5CAmBo

* test(testing-utils): keep a global fsmonitor out of fixtures

A user-global `core.fsmonitor` names a command git consults whenever it
refreshes the index, so `git add` and commit in a fixture repo ran the
contributor's own program. The fixture override now sets it to `false`
locally, beside the hooks and attributes overrides that close the same
class of setting.

Verified with a global `core.fsmonitor` that prints and exits 1: a repo
initialised the old way runs it on `git add`, the fixture does not, and
the seven git-backed suites and `cargo_deps::git` unit tests pass under
the full hostile configuration.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XPJkFm8WwhDuQpvC5CAmBo

---------

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Zoltan Kochan <z@kochan.io>
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-09 18:44:13 +00:00

简体中文 | 日本語 | 한국어 | Italiano | Português Brasileiro

pnpm

Fast, disk space efficient package manager:

  • Fast. Up to 2x faster than the alternatives (see benchmark).
  • Efficient. Files inside node_modules are linked from a single content-addressable storage.
  • Great for monorepos.
  • Strict. A package can access only dependencies that are specified in its package.json.
  • Deterministic. Has a lockfile called pnpm-lock.yaml.
  • Works as a Node.js version manager. See pnpm runtime.
  • Works everywhere. Supports Windows, Linux, and macOS.
  • Battle-tested. Used in production by teams of all sizes since 2016.
  • Experimental Rust port. Includes pacquet, an experimental port of the CLI written in Rust.
  • See the full feature comparison with npm and Yarn.

To quote the Rush team:

Microsoft uses pnpm in Rush repos with hundreds of projects and hundreds of PRs per day, and weve found it to be very fast and reliable.

npm version Ecosystem E2E OpenCollective OpenCollective X Follow Stand With Ukraine

Platinum Sponsors

Bit OpenAI Notion
CodeRabbit

Gold Sponsors

Sanity Discord Vite
SerpApi Stackblitz Workleap
Nx Latitude

Silver Sponsors

Replit Cybozu BairesDev
Thesys devowl.io u|screen
Leniolabs_ Depot Cerbos
⏱️ Time.now

Support this project by becoming a sponsor.

Background

pnpm uses a content-addressable filesystem to store all files from all module directories on a disk. When using npm, if you have 100 projects using lodash, you will have 100 copies of lodash on disk. With pnpm, lodash will be stored in a content-addressable storage, so:

  1. If you depend on different versions of lodash, only the files that differ are added to the store. If lodash has 100 files, and a new version has a change only in one of those files, pnpm update will only add 1 new file to the storage.
  2. All the files are saved in a single place on the disk. When packages are installed, their files are linked from that single place consuming no additional disk space. Linking is performed using either hard-links or reflinks (copy-on-write).

As a result, you save gigabytes of space on your disk and you have a lot faster installations! If you'd like more details about the unique node_modules structure that pnpm creates and why it works fine with the Node.js ecosystem, read this small article: Flat node_modules is not the only way.

💖 Like this project? Let people know with a tweet

Getting Started

Benchmark

pnpm is up to 2x faster than npm and Yarn classic. See all benchmarks here.

Benchmarks on an app with lots of dependencies:

License

MIT, except the pnpr/ directory, which is source-available under the PolyForm Shield License 1.0.0.

S
Description
No description provided
Readme MIT
461 MiB
0 Stars 1 Watchers 0 Forks
Languages
Rust 69.8%
TypeScript 29.2%
JavaScript 0.8%