Skip to content

feat(pacquet): port resolving/git-resolver and wire it into the install chain#11779

Merged
zkochan merged 5 commits into
mainfrom
git-resolver
May 20, 2026
Merged

feat(pacquet): port resolving/git-resolver and wire it into the install chain#11779
zkochan merged 5 commits into
mainfrom
git-resolver

Conversation

@zkochan

@zkochan zkochan commented May 20, 2026

Copy link
Copy Markdown
Member

Summary

  • Ports @pnpm/resolving.git-resolver to a new pacquet-resolving-git-resolver crate: GitHub / GitLab / Bitbucket shortcut parsing, git+ssh: / git+https: / ssh: / plain https://…/repo.git URLs, git ls-remote ref resolution (partial commits, annotated-tag dereference, #semver: range matching), and tarball-vs-Git{repo,commit} resolution selection. Production runners shell out to the system git binary via tokio::task::spawn_blocking and use the install-wide ThrottledClient for the HEAD probe.
  • Widens ResolveResult.id to a new PkgResolutionId newtype on resolver-base (rule-3 branded string) so URL-shaped IDs from git / tarball / local resolvers fit. Adds ResolveResult.name_ver: Option<PkgNameVer> for callers that need the structured name@version form; npm-resolver fills both, git-resolver leaves name_ver None.
  • Implements Resolver for DefaultResolver and uses it in install_without_lockfile.rs to chain npm + git resolvers, mirroring upstream's createResolver order.

Paired with the Stage-1 pacquet-git-fetcher; resolutions emitted here are exactly what GitFetcher / GitHostedTarballFetcher consume.

What is not in this PR

  • The install path that materialises git/tarball/local resolutions from scratch. install_without_lockfile.rs / install_package_from_registry.rs still need a structured name@version, so they .expect() result.name_ver and panic with a TODO message when the field is None. Lockfile-driven installs are unaffected (they don't go through the resolver).
  • The currentPkg && !update short-circuit. Pacquet doesn't thread currentPkg through the resolver seam yet — every resolve re-runs ls-remote.
  • The gist host, the upstream browse / bugs / docs / file / git URL templates, and proxy / TLS plumbing on the HEAD probe (it uses the install-wide ThrottledClient directly).

Test plan

  • cargo nextest run --workspace — 1635 tests pass (51 new in pacquet-resolving-git-resolver, including the full SCP-style URL repair matrix ported from parsePref.test.ts and the GitLab /-/archive/ regression for Errors when a dependency is specified as https://gitlab.com/owner/repo" #11533).
  • cargo clippy --workspace --all-targets -- --deny warnings — clean.
  • cargo fmt --all + taplo format — clean.
  • just integrated-benchmark against npm-only manifests (sanity check the chain wiring doesn't regress the existing npm path).
  • Manual install of a git-shorthand dep (pacquet install zkochan/is-negative) against a real registry, once the install path that consumes a git ResolveResult.name_ver = None is wired.

Written by an agent (Claude Code, claude-opus-4-7).

Summary by CodeRabbit

  • New Features

    • Adds full Git dependency support (GitHub/GitLab/Bitbucket), shorthand parsing, tarball vs git selection, optional path suffixes, and git probing to resolve fetch specs.
    • Supports resolving refs: full/partial commits, branches, tags and semver ranges with proper pinning.
  • Bug Fixes / Improvements

    • Installer and virtual-store naming now prefer structured name@version when available.
    • Installers now return a clear "unsupported resolution" error for resolutions lacking name@version.
    • Git resolver is included in no-lockfile resolution flow.

Review Change Stack

@coderabbitai

coderabbitai Bot commented May 20, 2026

Copy link
Copy Markdown

Caution

Review failed

Pull request was closed or merged during review

📝 Walkthrough

Walkthrough

This PR adds a new git-resolver crate (parsing, hosted rendering, ref resolution, runners), introduces PkgResolutionId and ResolveResult.name_ver, implements Resolver for DefaultResolver to enable resolver chains, and wires npm+git resolver chains into install flows with virtual-store naming updated to use name_ver.

Changes

Git resolver and resolver composition

Layer / File(s) Summary
Resolution identifier schema and base contracts
pacquet/crates/resolving-resolver-base/src/resolve.rs, pacquet/crates/resolving-resolver-base/src/lib.rs, pacquet/crates/resolving-resolver-base/Cargo.toml, pacquet/crates/resolving-resolver-base/src/tests.rs
Add PkgResolutionId branded string type and ResolveResult.name_ver: Option<PkgNameVer>; update exports and workspace deps.
Git resolver crate: manifest & entry
pacquet/crates/resolving-git-resolver/Cargo.toml, pacquet/crates/resolving-git-resolver/src/lib.rs
New crate manifest and lib entrypoint re-exporting internal modules.
HostedGit parsing & rendering
pacquet/crates/resolving-git-resolver/src/hosted_git.rs
Implement HostedGit type, parsing of shortcuts/URLs, rendering of https/ssh/tarball forms, percent-encoding helpers, and tests.
Bare specifier parser & probe finalize
pacquet/crates/resolving-git-resolver/src/parse_bare_specifier.rs
Two-phase parser: sync classification and async finalize using GitProbe to pick fetch_spec; fragment parsing and path/semver handling with tests.
Ref resolution via git ls-remote
pacquet/crates/resolving-git-resolver/src/resolve_ref.rs
Add GitCommandRunner trait, resolve_ref implementation covering full/partial commits, branches, annotated tags, semver ranges, helpers and tests.
Runners (RealGitProbe / RealGitRunner)
pacquet/crates/resolving-git-resolver/src/runners.rs
Production implementations performing HEAD probes and git ls-remote via spawn_blocking with retry semantics.
GitResolver orchestration
pacquet/crates/resolving-git-resolver/src/git_resolver.rs, pacquet/crates/resolving-git-resolver/src/create_git_hosted_pkg_id.rs
GitResolver wiring, Resolver impl, pick_resolution/tarball choice, ID construction (git+...#commit[&path:...]), and tests for end-to-end behaviors.
DefaultResolver trait impl & resolver composition
pacquet/crates/resolving-default-resolver/src/lib.rs, pacquet/crates/resolving-default-resolver/src/tests.rs
DefaultResolver now implements Resolver trait so resolver chains (npm + git) can be composed and used where a Resolver is expected.
NPM resolver: populate name_ver and tests
pacquet/crates/resolving-npm-resolver/src/npm_resolver.rs, pacquet/crates/resolving-npm-resolver/src/npm_resolver/tests.rs
build_resolve_result stores a PkgNameVer in name_ver and sets ResolveResult.name_ver; tests updated to assert via name_ver.
Dependency-tree alias logic
pacquet/crates/resolving-deps-resolver/src/resolve_dependency_tree.rs, pacquet/crates/resolving-deps-resolver/src/tests.rs
Alias computation now prefers result.alias, wanted.alias, then name_ver.name, then id string; tests updated to populate name_ver in fake results.
Peer resolution and parent refs
pacquet/crates/resolving-deps-resolver/src/resolve_peers.rs, pacquet/crates/resolving-deps-resolver/src/tests.rs
Introduce pkg_name_version helper to extract name/version from ResolveResult.name_ver with fallbacks; update PeerId/ParentRef construction to use it.
Package manager install integration
Cargo.toml, pacquet/crates/package-manager/Cargo.toml, pacquet/crates/package-manager/src/install_without_lockfile.rs, pacquet/crates/package-manager/src/install_package_from_registry.rs, pacquet/crates/package-manager/src/install_package_from_registry/tests.rs
Add git resolver to resolver chain for without-lockfile installs; install now derives virtual-store names from ResolveResult.name_ver and returns UnsupportedResolution when name_ver is missing; drop combined resolver after resolution; tests added/updated.

Sequence Diagram(s)

sequenceDiagram
  participant WantedDep
  participant DefaultResolver
  participant GitResolver
  participant ParseBareSpecifier
  participant GitProbe
  participant GitRunner
  participant ResolveRef
  participant PackageManager

  WantedDep->>DefaultResolver: resolve(wanted_dependency)
  DefaultResolver->>GitResolver: delegate resolve
  GitResolver->>ParseBareSpecifier: parse_bare_specifier()
  ParseBareSpecifier->>GitProbe: finalize(hosted) probe
  GitResolver->>GitRunner: ls-remote (via ResolveRef)
  GitRunner-->>ResolveRef: ls-remote output
  ResolveRef-->>GitResolver: resolved commit
  GitResolver-->>DefaultResolver: ResolveResult (id + name_ver)
  DefaultResolver-->>PackageManager: resolved tree
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related issues

Possibly related PRs

  • pnpm/pnpm#11772: Overlaps changes to resolving-npm-resolver build_resolve_result and name_ver usage.
  • pnpm/pnpm#11755: Related resolver-seam and DefaultResolver scaffolding work.
  • pnpm/pnpm#11551: Related GitLab tarball URL shape fixes referenced by hosted-git tarball rendering tests.

Poem

🐰 I stitched a git resolver, neat and spry,
parsing shorthands and commits that fly.
Claimed deps and pinned tarballs, names made clear —
DefaultResolver hops them into install cheer!
A tiny rabbit cheers for every passing try.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and specifically describes the main accomplishment: porting a git-resolver and integrating it into the install chain.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch git-resolver

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@codecov-commenter

codecov-commenter commented May 20, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 84.76062% with 226 lines in your changes missing coverage. Please review.
✅ Project coverage is 89.67%. Comparing base (a1f91e1) to head (a7ff276).

Files with missing lines Patch % Lines
...et/crates/resolving-git-resolver/src/hosted_git.rs 83.53% 81 Missing ⚠️
...cquet/crates/resolving-git-resolver/src/runners.rs 7.79% 71 Missing ⚠️
...resolving-git-resolver/src/parse_bare_specifier.rs 92.72% 27 Missing ⚠️
.../crates/resolving-git-resolver/src/git_resolver.rs 89.05% 22 Missing ⚠️
...cquet/crates/resolving-default-resolver/src/lib.rs 55.00% 9 Missing ⚠️
...t/crates/resolving-git-resolver/src/resolve_ref.rs 96.81% 7 Missing ⚠️
...quet/crates/resolving-resolver-base/src/resolve.rs 60.00% 6 Missing ⚠️
...rates/resolving-deps-resolver/src/resolve_peers.rs 75.00% 3 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main   #11779      +/-   ##
==========================================
- Coverage   90.05%   89.67%   -0.39%     
==========================================
  Files         163      170       +7     
  Lines       18851    20318    +1467     
==========================================
+ Hits        16977    18220    +1243     
- Misses       1874     2098     +224     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@github-actions

github-actions Bot commented May 20, 2026

Copy link
Copy Markdown
Contributor

Micro-Benchmark Results

Linux

group                          main                                   pr
-----                          ----                                   --
tarball/download_dependency    1.01      7.6±0.36ms   569.4 KB/sec    1.00      7.5±0.31ms   574.9 KB/sec

@github-actions

github-actions Bot commented May 20, 2026

Copy link
Copy Markdown
Contributor

Integrated-Benchmark Report (Linux)

Scenario: Frozen Lockfile

Command Mean [s] Min [s] Max [s] Relative
pacquet@HEAD 2.424 ± 0.148 2.297 2.803 1.03 ± 0.07
pacquet@main 2.347 ± 0.063 2.238 2.422 1.00
pnpm 4.498 ± 0.057 4.415 4.574 1.92 ± 0.06
BENCHMARK_REPORT.json
{
  "results": [
    {
      "command": "pacquet@HEAD",
      "mean": 2.4238598509200004,
      "stddev": 0.14796998878635223,
      "median": 2.38213720222,
      "user": 2.80677126,
      "system": 3.5737683200000006,
      "min": 2.29656711722,
      "max": 2.80257137122,
      "times": [
        2.48570114922,
        2.36349335722,
        2.39144764822,
        2.3728267562200003,
        2.80257137122,
        2.40070068022,
        2.48631146922,
        2.31301202622,
        2.3259669342200002,
        2.29656711722
      ]
    },
    {
      "command": "pacquet@main",
      "mean": 2.34743767502,
      "stddev": 0.0632444987149618,
      "median": 2.36546619422,
      "user": 2.7577365599999992,
      "system": 3.5711329199999993,
      "min": 2.23835572522,
      "max": 2.42164576422,
      "times": [
        2.38430890022,
        2.2816797862200002,
        2.23835572522,
        2.2753686442200003,
        2.38238714022,
        2.41284598222,
        2.3361227012200003,
        2.39311685822,
        2.34854524822,
        2.42164576422
      ]
    },
    {
      "command": "pnpm",
      "mean": 4.497790352819999,
      "stddev": 0.057330578151770235,
      "median": 4.48447060172,
      "user": 7.512217059999999,
      "system": 3.97266982,
      "min": 4.4146280322199996,
      "max": 4.57372525922,
      "times": [
        4.5712933822199995,
        4.4146280322199996,
        4.49077588422,
        4.43190812922,
        4.525448648219999,
        4.45890896322,
        4.55978494422,
        4.4732649662199995,
        4.4781653192199995,
        4.57372525922
      ]
    }
  ]
}

Scenario: Frozen Lockfile (Hot Cache)

Command Mean [ms] Min [ms] Max [ms] Relative
pacquet@HEAD 705.4 ± 31.1 670.9 786.2 1.00
pacquet@main 729.7 ± 73.9 672.7 900.3 1.03 ± 0.11
pnpm 2389.1 ± 75.7 2309.4 2571.9 3.39 ± 0.18
BENCHMARK_REPORT.json
{
  "results": [
    {
      "command": "pacquet@HEAD",
      "mean": 0.70540355532,
      "stddev": 0.031060851565946825,
      "median": 0.6968091792200001,
      "user": 0.39750566000000004,
      "system": 1.5691996599999998,
      "min": 0.67087535622,
      "max": 0.78615518222,
      "times": [
        0.78615518222,
        0.6913724762200001,
        0.68604944922,
        0.6950482292200001,
        0.70899670822,
        0.6985701292200001,
        0.67087535622,
        0.71454598522,
        0.7087255422200001,
        0.69369649522
      ]
    },
    {
      "command": "pacquet@main",
      "mean": 0.7296857136200001,
      "stddev": 0.07385930297345797,
      "median": 0.6959964897200001,
      "user": 0.37431865999999997,
      "system": 1.59666106,
      "min": 0.67266505722,
      "max": 0.90030861122,
      "times": [
        0.74807831822,
        0.68815541522,
        0.68939187822,
        0.90030861122,
        0.69359643922,
        0.67266505722,
        0.7133518482200001,
        0.8169399582200001,
        0.6983965402200001,
        0.6759730702200001
      ]
    },
    {
      "command": "pnpm",
      "mean": 2.3890530720200003,
      "stddev": 0.0756980438255328,
      "median": 2.38173912522,
      "user": 2.93750096,
      "system": 2.17833876,
      "min": 2.30936904222,
      "max": 2.5718829312200002,
      "times": [
        2.40949393122,
        2.32955478422,
        2.42162822922,
        2.5718829312200002,
        2.41362644922,
        2.33685037122,
        2.36794752822,
        2.33464673122,
        2.30936904222,
        2.39553072222
      ]
    }
  ]
}

@zkochan zkochan marked this pull request as ready for review May 20, 2026 20:32
@qodo-code-review

Copy link
Copy Markdown

Qodo reviews are paused for this user.

Troubleshooting steps vary by plan Learn more →

On a Teams plan?
Reviews resume once this user has a paid seat and their Git account is linked in Qodo.
Link Git account →

Using GitHub Enterprise Server, GitLab Self-Managed, or Bitbucket Data Center?
These require an Enterprise plan - Contact us
Contact us →

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

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@pacquet/crates/package-manager/src/install_without_lockfile.rs`:
- Around line 177-187: The code registers GitResolver into the without-lockfile
resolver chain (DefaultResolver constructed with npm_resolver and git_resolver)
but Git resolutions still call expect on missing name_ver and can panic; either
stop adding GitResolver here until materialization support exists or change the
git path to propagate a typed error: update the registration in the resolver
chain (the code that constructs Box::new(DefaultResolver::new(...))) to
conditionally include Box::new(GitResolver::new(...)) based on the
materialization/feature flag, or refactor GitResolver (and any callers used by
install_without_lockfile.rs and install_package_from_registry.rs) so its
resolve/install methods return Result<..., InstallError> instead of calling
expect on name_ver and ensure the calling flow handles and forwards that error
instead of panicking.

In `@pacquet/crates/resolving-git-resolver/src/hosted_git.rs`:
- Around line 639-657: The percent_decode function currently decodes each %XX as
a char which breaks multi-byte UTF-8 sequences; change percent_decode to
accumulate raw bytes (push decoded byte values into a Vec<u8> instead of casting
to char), also push non-% bytes as their byte values, then convert the collected
Vec<u8> to a UTF-8 String (e.g., String::from_utf8 or from_utf8_lossy) before
returning; update the percent_decode implementation to use this byte-buffer
approach so multibyte UTF-8 sequences are reconstructed correctly.

In `@pacquet/crates/resolving-git-resolver/src/parse_bare_specifier.rs`:
- Around line 317-335: The percent_decode_str function currently converts bytes
to char directly which corrupts multibyte UTF-8 sequences; instead, accumulate
decoded bytes into a Vec<u8> (push decoded byte when a %HH sequence is found,
otherwise push the raw byte), then convert that byte vector to a String using a
UTF-8-safe conversion (e.g., String::from_utf8_lossy().into_owned()) so
percent-decoded multi-byte UTF-8 is preserved; update percent_decode_str to use
this byte-buffer-then-decode approach.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 428a7479-e0a0-4a77-8ea8-aaf5b038d510

📥 Commits

Reviewing files that changed from the base of the PR and between f807f6d and 722a712.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (23)
  • Cargo.toml
  • pacquet/crates/package-manager/Cargo.toml
  • pacquet/crates/package-manager/src/install_package_from_registry.rs
  • pacquet/crates/package-manager/src/install_package_from_registry/tests.rs
  • pacquet/crates/package-manager/src/install_without_lockfile.rs
  • pacquet/crates/resolving-default-resolver/src/lib.rs
  • pacquet/crates/resolving-default-resolver/src/tests.rs
  • pacquet/crates/resolving-deps-resolver/src/resolve_dependency_tree.rs
  • pacquet/crates/resolving-deps-resolver/src/tests.rs
  • pacquet/crates/resolving-git-resolver/Cargo.toml
  • pacquet/crates/resolving-git-resolver/src/create_git_hosted_pkg_id.rs
  • pacquet/crates/resolving-git-resolver/src/git_resolver.rs
  • pacquet/crates/resolving-git-resolver/src/hosted_git.rs
  • pacquet/crates/resolving-git-resolver/src/lib.rs
  • pacquet/crates/resolving-git-resolver/src/parse_bare_specifier.rs
  • pacquet/crates/resolving-git-resolver/src/resolve_ref.rs
  • pacquet/crates/resolving-git-resolver/src/runners.rs
  • pacquet/crates/resolving-npm-resolver/src/npm_resolver.rs
  • pacquet/crates/resolving-npm-resolver/src/npm_resolver/tests.rs
  • pacquet/crates/resolving-resolver-base/Cargo.toml
  • pacquet/crates/resolving-resolver-base/src/lib.rs
  • pacquet/crates/resolving-resolver-base/src/resolve.rs
  • pacquet/crates/resolving-resolver-base/src/tests.rs
📜 Review details
🧰 Additional context used
📓 Path-based instructions (1)
pacquet/**/*.rs

📄 CodeRabbit inference engine (pacquet/AGENTS.md)

pacquet/**/*.rs: When porting a function that fires pnpm:<channel> events through globalLogger, logger.debug(), or streamParser.write(), mirror the call site, payload, and ordering so the reporter parses pacquet's NDJSON the same way it parses pnpm's.
Declare a newtype wrapper for branded string types. Do not collapse the brand into a plain String or &str.
If upstream always validates before construction, validate in pacquet's wrapper too. The wrapper must construct only via TryFrom<String> and/or FromStr. Do not provide an infallible public constructor.
If upstream never validates, just brand for type-safety. Expose an infallible From<String> (and From<&str> when convenient).
If upstream occasionally constructs without validation, expose from_str_unchecked as an escape hatch alongside the validating constructor.
Match upstream serde behavior for branded types that cross JSON, YAML, or INI boundaries. Use #[serde(try_from = "String")] for deserialization and #[serde(into = "String")] for serialization.
Use #[derive(derive_more::From)] and #[derive(derive_more::Into)] for mechanical conversion impls. Fall back to manual impl only when conversion needs custom logic.
String-literal unions should become enums, not newtype wrappers. Model closed sets of valid string values as enums.
Template literal types should be treated as branded strings with validation discipline from rules 2-5.
Choose owned vs. borrowed parameters to minimize copies. Widen to the most encompassing type (&Path over &PathBuf, &str over &String) when it doesn't force extra copies.
Prefer Arc::clone(&x) / Rc::clone(&x) over x.clone() for reference-counted types, so the cost is visible at the call site.
Follow Rust API Guidelines for naming conventions.
Do not use star imports inside module bodies. Write use super::{Foo, bar} instead of use super::*;. Two forms stay allowed: external-crate preludes like use rayon::prelude::*; and root-of-module re-...

Files:

  • pacquet/crates/resolving-git-resolver/src/create_git_hosted_pkg_id.rs
  • pacquet/crates/resolving-npm-resolver/src/npm_resolver.rs
  • pacquet/crates/package-manager/src/install_package_from_registry/tests.rs
  • pacquet/crates/package-manager/src/install_package_from_registry.rs
  • pacquet/crates/resolving-deps-resolver/src/tests.rs
  • pacquet/crates/resolving-deps-resolver/src/resolve_dependency_tree.rs
  • pacquet/crates/resolving-default-resolver/src/tests.rs
  • pacquet/crates/resolving-resolver-base/src/resolve.rs
  • pacquet/crates/resolving-npm-resolver/src/npm_resolver/tests.rs
  • pacquet/crates/resolving-resolver-base/src/tests.rs
  • pacquet/crates/resolving-git-resolver/src/lib.rs
  • pacquet/crates/resolving-resolver-base/src/lib.rs
  • pacquet/crates/resolving-default-resolver/src/lib.rs
  • pacquet/crates/resolving-git-resolver/src/runners.rs
  • pacquet/crates/package-manager/src/install_without_lockfile.rs
  • pacquet/crates/resolving-git-resolver/src/parse_bare_specifier.rs
  • pacquet/crates/resolving-git-resolver/src/git_resolver.rs
  • pacquet/crates/resolving-git-resolver/src/resolve_ref.rs
  • pacquet/crates/resolving-git-resolver/src/hosted_git.rs
🧠 Learnings (1)
📚 Learning: 2026-05-20T19:40:55.051Z
Learnt from: zkochan
Repo: pnpm/pnpm PR: 11774
File: pacquet/crates/resolving-deps-resolver/src/resolve_peers.rs:0-0
Timestamp: 2026-05-20T19:40:55.051Z
Learning: In the pacquet Rust code, ensure the semver implementation uses the `node-semver` crate (not `nodejs-semver`). `node-semver`’s public API does not include a `satisfies_with_prerelease`-style method; prerelease-tolerant matching should be implemented inline by first calling `Range::satisfies`, and when it rejects a prerelease version, retry matching against a stripped `MAJOR.MINOR.PATCH` base of the prerelease version.

Applied to files:

  • pacquet/crates/resolving-git-resolver/src/create_git_hosted_pkg_id.rs
  • pacquet/crates/resolving-npm-resolver/src/npm_resolver.rs
  • pacquet/crates/package-manager/src/install_package_from_registry/tests.rs
  • pacquet/crates/package-manager/src/install_package_from_registry.rs
  • pacquet/crates/resolving-deps-resolver/src/tests.rs
  • pacquet/crates/resolving-deps-resolver/src/resolve_dependency_tree.rs
  • pacquet/crates/resolving-default-resolver/src/tests.rs
  • pacquet/crates/resolving-resolver-base/src/resolve.rs
  • pacquet/crates/resolving-npm-resolver/src/npm_resolver/tests.rs
  • pacquet/crates/resolving-resolver-base/src/tests.rs
  • pacquet/crates/resolving-git-resolver/src/lib.rs
  • pacquet/crates/resolving-resolver-base/src/lib.rs
  • pacquet/crates/resolving-default-resolver/src/lib.rs
  • pacquet/crates/resolving-git-resolver/src/runners.rs
  • pacquet/crates/package-manager/src/install_without_lockfile.rs
  • pacquet/crates/resolving-git-resolver/src/parse_bare_specifier.rs
  • pacquet/crates/resolving-git-resolver/src/git_resolver.rs
  • pacquet/crates/resolving-git-resolver/src/resolve_ref.rs
  • pacquet/crates/resolving-git-resolver/src/hosted_git.rs
🔇 Additional comments (20)
pacquet/crates/resolving-resolver-base/src/resolve.rs (1)

14-63: LGTM!

Also applies to: 237-247

pacquet/crates/resolving-resolver-base/Cargo.toml (1)

17-20: LGTM!

pacquet/crates/resolving-resolver-base/src/lib.rs (1)

30-33: LGTM!

pacquet/crates/resolving-resolver-base/src/tests.rs (1)

137-140: LGTM!

pacquet/crates/resolving-git-resolver/Cargo.toml (1)

1-30: LGTM!

pacquet/crates/resolving-git-resolver/src/lib.rs (1)

36-50: LGTM!

pacquet/crates/resolving-git-resolver/src/runners.rs (1)

31-35: LGTM!

Also applies to: 37-71, 86-96, 98-113, 115-148

pacquet/crates/resolving-git-resolver/src/create_git_hosted_pkg_id.rs (1)

15-29: LGTM!

Also applies to: 35-94

pacquet/crates/resolving-git-resolver/src/git_resolver.rs (1)

30-39: LGTM!

Also applies to: 41-59, 62-95, 98-141, 146-174, 226-317

pacquet/crates/resolving-default-resolver/src/lib.rs (1)

20-22: LGTM!

Also applies to: 81-112

pacquet/crates/resolving-default-resolver/src/tests.rs (1)

18-19: LGTM!

Also applies to: 41-45

pacquet/crates/resolving-deps-resolver/src/resolve_dependency_tree.rs (1)

145-150: LGTM!

pacquet/crates/resolving-deps-resolver/src/tests.rs (1)

47-53: LGTM!

pacquet/crates/resolving-git-resolver/src/resolve_ref.rs (1)

270-279: ⚡ Quick win

This suggestion is incorrect and contradicts SemVer semantics.

The review proposes retrying prerelease versions against their base MAJOR.MINOR.PATCH when range.satisfies() rejects them. This is not how pnpm or semantic versioning works. Per the SemVer specification, prerelease versions are intentionally excluded from range matching unless explicitly requested. Retrying a prerelease against its base version after rejection would violate the spec's contract—it would force an unstable version upon a user who has not opted in. pnpm relies on standard semver logic to exclude unintended prerelease versions. The current implementation using only range.satisfies(&parsed) is correct.

			> Likely an incorrect or invalid review comment.
pacquet/crates/package-manager/src/install_package_from_registry.rs (1)

107-112: Duplicate: same panic-path concern already raised in install_without_lockfile.rs.

This expect is part of the same resolver/install mismatch and should be fixed together to avoid runtime panics.

pacquet/crates/resolving-npm-resolver/src/npm_resolver.rs (1)

346-347: LGTM!

Also applies to: 374-374

pacquet/crates/resolving-npm-resolver/src/npm_resolver/tests.rs (1)

118-121: LGTM!

Also applies to: 155-155, 253-255, 283-286

Cargo.toml (1)

42-42: LGTM!

pacquet/crates/package-manager/Cargo.toml (1)

14-40: LGTM!

pacquet/crates/package-manager/src/install_package_from_registry/tests.rs (1)

155-157: LGTM!

Comment thread pacquet/crates/package-manager/src/install_without_lockfile.rs
Comment thread pacquet/crates/resolving-git-resolver/src/hosted_git.rs
Comment thread pacquet/crates/resolving-git-resolver/src/parse_bare_specifier.rs
zkochan added 3 commits May 20, 2026 22:51
…ll chain

Adds `pacquet-resolving-git-resolver`, the Rust port of pnpm's
`@pnpm/resolving.git-resolver`. Recognises GitHub / GitLab / Bitbucket
shortcut forms and full `git+ssh:` / `git+https:` / `ssh:` / plain
`https://…/repo.git` URLs, runs `git ls-remote` to pin the commit
(partial commit search, annotated-tag dereference, semver-range matching),
and emits either a git-hosted tarball resolution or a `Git{repo,commit}`
resolution. Production runners shell out to the system `git` binary via
`tokio::task::spawn_blocking` and use the install-wide
`ThrottledClient` for the HEAD probe.

Widens the resolver-base contract so URL-shaped IDs fit: adds a
`PkgResolutionId` newtype (rule-3 branded string, infallible
`From<String>`/`From<&str>`/`From<&PkgNameVer>`), changes
`ResolveResult.id` to that type, and adds `name_ver: Option<PkgNameVer>`
so callers that need the structured `name@version` form keep working.
npm-resolver fills both fields; git-resolver leaves `name_ver` `None`
(the install path that consumes git resolutions hasn't landed yet, so
those call sites panic with a TODO message until then).

`DefaultResolver` now implements `Resolver` too (returns `Ok(None)`
when no resolver in the chain claims), letting `resolve_dependency_tree`
accept the chain directly. The install-side wiring in
`install_without_lockfile.rs` constructs
`DefaultResolver::new(vec![Box::new(npm_resolver), Box::new(git_resolver)])`
with `RealGitProbe` / `RealGitRunner`, mirroring upstream's
`createResolver` chain order.

Test coverage: 51 unit tests in the new crate, including the full
SCP-style URL repair matrix ported from `parsePref.test.ts` and the
GitLab `/-/archive/` tarball regression for pnpm #11533. Full
workspace `cargo nextest run` is green at 1635 tests.

---
Written by an agent (Claude Code, claude-opus-4-7).
…port

* Reorder `#[derive(...)]` on `PkgResolutionId` to match the
  `prefix_then_alphabetical` rule the dylint Perfectionist lint
  enforces (`From` last after `Serialize`/`Deserialize`).
* Add `()` to function intra-doc links that collide with same-named
  modules (`create_git_hosted_pkg_id`, `parse_bare_specifier`) so
  rustdoc's `broken-intra-doc-links` lint stops treating them as
  ambiguous.

---
Written by an agent (Claude Code, claude-opus-4-7).
CI's `just ready` doesn't surface Perfectionist (it runs only as a
dedicated dylint job on a nightly toolchain). Fixes:

* Rename single-letter generics `P`/`R` → `Probe`/`Runner` on
  `GitResolver`, `PartialSpec::finalize`, `from_hosted_git`, and
  `resolve_ref`.
* Rename single-letter closure / function / let-binding params
  (`s`/`h`/`c`/`p`/`i`/`g`/...) to descriptive names.
* Replace Unicode ellipsis (`…`, U+2026) with ASCII `...` in comments.
* Add trailing commas to multi-line `assert_eq!` / `assert!`
  invocations, and remove the stray trailing comma on a single-line
  one.

Also fix follow-on JSR-resolver test cases that still read
`result.id.{name,suffix}`: switch them to `result.name_ver.as_ref()...`
to match the post-widening `ResolveResult` shape.

---
Written by an agent (Claude Code, claude-opus-4-7).
zkochan added 2 commits May 20, 2026 22:54
* Replace the two `.expect()` calls on `ResolveResult.name_ver` in the
  install path with `.ok_or_else()` that surfaces a typed error:
  `InstallPackageFromRegistryError::UnsupportedResolution` and a new
  `InstallWithoutLockfileError::UnsupportedInstallResolution`. Now
  that the git resolver is in the chain, a git/tarball/local
  resolution reaching the without-lockfile install path returns an
  error end-to-end instead of panicking. Add a regression test
  pinning the contract.
* Make `percent_decode` (in `hosted_git.rs`) and `percent_decode_str`
  (in `parse_bare_specifier.rs`) UTF-8 aware: collect decoded bytes
  into a `Vec<u8>` and reassemble via `String::from_utf8`, falling
  back to the original input on malformed UTF-8 (matches Node's
  `decodeURIComponent` throwing a `URIError` that upstream's
  `try/catch` swallows). The byte→`char` cast was corrupting any
  multi-byte sequence (e.g., `%E2%80%A6` → ellipsis); regression test
  added.

---
Written by an agent (Claude Code, claude-opus-4-7).
Main's `feat(pacquet): peer-dependency resolution stage` reworked
`install_without_lockfile.rs` to derive the virtual-store name from
the resolved depPath via `pacquet_deps_path::dep_path_to_filename`
instead of reading `result.name_ver`. That removed the `.expect()` /
`.ok_or_else()` site this error variant was added for; with no
remaining callers, drop the dead variant.

---
Written by an agent (Claude Code, claude-opus-4-7).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants