From 6a1ac641e9a1058b60a1cc6d4ee8e225598a3a85 Mon Sep 17 00:00:00 2001 From: elrrrrrrr Date: Fri, 19 Jun 2026 01:12:01 +0800 Subject: [PATCH 1/3] =?UTF-8?q?perf(pm):=20trim=20binary=20=E2=80=94=20sin?= =?UTF-8?q?gle=20TLS=20provider=20(ring),=20drop=20unused=20hickory?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Shrinks the release binary by removing redundant/dead dependency weight, validated by an A/B cold-resolve bench (see PR body for numbers): - TLS: drop the macOS-only `aws-lc-rs` override; unify every native target to rustls' default `ring` provider. The macOS binary was compiling BOTH crypto providers (aws-lc-rs forced by ruborist + ring pulled by pm's `rustls-tls`), ~610K of dead duplication. A same-machine A/B (cold resolve, ant-design-sized tree) showed ring is *faster* than aws-lc-rs on registry.npmmirror.com (-27%) and tied on registry.antgroup-inc.cn — the old "aws-lc ~40% faster" rationale did not reproduce on these registries. - DNS: drop reqwest's `hickory-dns` feature (~497K). Both clients already set `.dns_resolver(shared_resolver())` (custom getaddrinfo + cache), so hickory was compiled but never used at runtime — pure dead weight, zero behavior change, helps every platform. - HTTP/2: drop `http2` from our reqwest features (every client forces `.http1_only()`). NOTE: h2 still links in transitively via gix-transport's default-featured reqwest; fully removing it requires the native-git lever (see PR body). Local (macOS arm64, release profile): 10 MB -> 8.6 MB. Co-Authored-By: Claude Opus 4.8 (1M context) --- Cargo.lock | 126 ---------------------------- crates/pm/Cargo.toml | 2 - crates/ruborist/Cargo.toml | 31 +++---- crates/ruborist/src/service/http.rs | 43 ---------- next.js | 2 +- 5 files changed, 11 insertions(+), 193 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index bde2b819de..fc92630cc9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2277,18 +2277,6 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c34f04666d835ff5d62e058c3995147c06f42fe86ff053337632bca83e42702d" -[[package]] -name = "enum-as-inner" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1e6a265c649f3f5979b601d26f1d05ada116434c87741c9493cb56218f76cbc" -dependencies = [ - "heck 0.5.0", - "proc-macro2", - "quote", - "syn 2.0.106", -] - [[package]] name = "enum-iterator" version = "0.7.0" @@ -3727,52 +3715,6 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" -[[package]] -name = "hickory-proto" -version = "0.25.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8a6fe56c0038198998a6f217ca4e7ef3a5e51f46163bd6dd60b5c71ca6c6502" -dependencies = [ - "async-trait", - "cfg-if", - "data-encoding", - "enum-as-inner", - "futures-channel", - "futures-io", - "futures-util", - "idna", - "ipnet", - "once_cell", - "rand 0.9.2", - "ring", - "thiserror 2.0.17", - "tinyvec", - "tokio", - "tracing", - "url", -] - -[[package]] -name = "hickory-resolver" -version = "0.25.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc62a9a99b0bfb44d2ab95a7208ac952d31060efc16241c87eaf36406fecf87a" -dependencies = [ - "cfg-if", - "futures-util", - "hickory-proto", - "ipconfig", - "moka", - "once_cell", - "parking_lot", - "rand 0.9.2", - "resolv-conf", - "smallvec", - "thiserror 2.0.17", - "tokio", - "tracing", -] - [[package]] name = "hipstr" version = "0.6.0" @@ -4315,18 +4257,6 @@ version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06432fb54d3be7964ecd3649233cddf80db2832f47fec34c01f65b3d9d774983" -[[package]] -name = "ipconfig" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b58db92f96b720de98181bbbe63c831e87005ab460c1bf306eb2622b4707997f" -dependencies = [ - "socket2 0.5.10", - "widestring", - "windows-sys 0.48.0", - "winreg", -] - [[package]] name = "ipnet" version = "2.11.0" @@ -5262,24 +5192,6 @@ dependencies = [ "swc_ecma_visit 23.0.0", ] -[[package]] -name = "moka" -version = "0.12.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8261cd88c312e0004c1d51baad2980c66528dfdb2bee62003e643a4d8f86b077" -dependencies = [ - "crossbeam-channel", - "crossbeam-epoch", - "crossbeam-utils", - "equivalent", - "parking_lot", - "portable-atomic", - "rustc_version", - "smallvec", - "tagptr", - "uuid", -] - [[package]] name = "monch" version = "0.5.0" @@ -5606,10 +5518,6 @@ name = "once_cell" version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" -dependencies = [ - "critical-section", - "portable-atomic", -] [[package]] name = "once_cell_polyfill" @@ -7034,7 +6942,6 @@ dependencies = [ "futures-core", "futures-util", "h2 0.4.12", - "hickory-resolver", "http 1.3.1", "http-body 1.0.1", "http-body-util", @@ -7046,7 +6953,6 @@ dependencies = [ "log", "mime", "native-tls", - "once_cell", "percent-encoding", "pin-project-lite", "quinn", @@ -7110,12 +7016,6 @@ dependencies = [ "web-sys", ] -[[package]] -name = "resolv-conf" -version = "0.7.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b3789b30bd25ba102de4beabd95d21ac45b69b1be7d14522bab988c526d6799" - [[package]] name = "ring" version = "0.17.14" @@ -7282,7 +7182,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cd3c25631629d034ce7cd9940adc9d45762d46de2b0f57193c4443b92c6d4d40" dependencies = [ "aws-lc-rs", - "log", "once_cell", "ring", "rustls-pki-types", @@ -9656,12 +9555,6 @@ dependencies = [ "winx", ] -[[package]] -name = "tagptr" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b2093cf4c8eb1e67749a6762251bc9cd836b6fc171623bd0a9d324d37af2417" - [[package]] name = "tap" version = "1.0.1" @@ -11408,8 +11301,6 @@ dependencies = [ "petgraph 0.6.5", "rayon", "reqwest 0.12.24", - "rustls", - "rustls-native-certs", "serde", "serde_json", "sha2", @@ -11471,7 +11362,6 @@ version = "1.18.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2f87b8aa10b915a06587d0dec516c282ff295b475d94abf425d62b57710070a2" dependencies = [ - "getrandom 0.3.3", "js-sys", "wasm-bindgen", ] @@ -12609,12 +12499,6 @@ version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a751b3277700db47d3e574514de2eced5e54dc8a5436a3bf7a0b248b2cee16f3" -[[package]] -name = "widestring" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72069c3113ab32ab29e5584db3c6ec55d416895e60715417b5b883a357c3e471" - [[package]] name = "wiggle" version = "38.0.4" @@ -13107,16 +12991,6 @@ dependencies = [ "memchr", ] -[[package]] -name = "winreg" -version = "0.50.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" -dependencies = [ - "cfg-if", - "windows-sys 0.48.0", -] - [[package]] name = "winx" version = "0.36.4" diff --git a/crates/pm/Cargo.toml b/crates/pm/Cargo.toml index c83fb3d019..c09a75eb12 100644 --- a/crates/pm/Cargo.toml +++ b/crates/pm/Cargo.toml @@ -45,8 +45,6 @@ rayon = { workspace = true } regex = "1.10" reqwest = { version = "0.12", default-features = false, features = [ "gzip", - "hickory-dns", - "http2", "json", "rustls-tls", "socks", diff --git a/crates/ruborist/Cargo.toml b/crates/ruborist/Cargo.toml index cee6b29dd1..9a4d1fe5a3 100644 --- a/crates/ruborist/Cargo.toml +++ b/crates/ruborist/Cargo.toml @@ -28,9 +28,10 @@ thiserror = { workspace = true } tokio-fs-ext = { workspace = true } tokio-retry = "0.3" tracing = { workspace = true } -# TLS provider is selected per-target below: macOS overrides to -# aws-lc-rs (see service/http.rs); every other target keeps ring. -reqwest = { version = "0.12", default-features = false, features = ["http2", "json", "socks"] } +# TLS is rustls with the default `ring` provider on every native target (the +# per-target block below adds it). HTTP/2 is intentionally off: every client +# forces HTTP/1.1 (service/http.rs + pm downloader), so `http2` was dead weight. +reqwest = { version = "0.12", default-features = false, features = ["json", "socks"] } # Shared URL-hash + staging helpers for native-only resolvers (git / http) sha2 = { workspace = true, optional = true } tempfile = { workspace = true, optional = true } @@ -52,37 +53,25 @@ workspace = true [dev-dependencies] tokio = { workspace = true, features = ["macros", "rt"] } -# Native (non-macOS) targets: reqwest's default rustls + ring. +# All native targets: reqwest's default rustls + `ring` provider. (macOS used +# to override to aws-lc-rs for faster M-series cold resolves, but that pulled a +# second crypto backend into the binary; unified to ring to halve the TLS code.) # `gzip` lets the manifest client send Accept-Encoding and transparently # decompress packuments (~2.5x smaller on registry.npmjs.org); brotli is # only ~1% smaller than gzip there but decodes slower, so it stays off. -[target.'cfg(not(any(target_arch = "wasm32", target_os = "macos")))'.dependencies] -reqwest = { version = "0.12", default-features = false, features = ["gzip", "rustls-tls-native-roots"] } - -# Native-only dependencies (not compiled for WASM) [target.'cfg(not(target_arch = "wasm32"))'.dependencies] -libc = "0.2" +reqwest = { version = "0.12", default-features = false, features = ["gzip", "rustls-tls-native-roots"] } +libc = "0.2" # Dedicated CPU thread pool for simd_json manifest parsing — keeps the # tokio runtime free of CPU-bound work so in-flight network IO is not # blocked by sibling parses. WASM falls back to inline parse. -rayon = { workspace = true } +rayon = { workspace = true } # Browser handles TLS via the fetch API, so reqwest needs no rustls # feature on this target. [target.'cfg(target_arch = "wasm32")'.dependencies] js-sys = "0.3" -# macOS uses aws-lc-rs (BoringSSL primitives) for ~40 % faster cold -# resolves on M-series ARM. CI bench-phases regresses on Linux/x86_64 -# with this provider, so it's gated to macOS only. See service/http.rs. -[target.'cfg(target_os = "macos")'.dependencies] -reqwest = { version = "0.12", default-features = false, features = [ - "gzip", - "rustls-tls-native-roots-no-provider" -] } -rustls = { version = "0.23", default-features = false, features = ["aws-lc-rs", "logging", "std", "tls12"] } -rustls-native-certs = "0.8" - [features] default = [] # HTTP tarball resolver pre-extracts to //_http_/ diff --git a/crates/ruborist/src/service/http.rs b/crates/ruborist/src/service/http.rs index 24a6b311f1..1fbaeabe2f 100644 --- a/crates/ruborist/src/service/http.rs +++ b/crates/ruborist/src/service/http.rs @@ -136,44 +136,6 @@ pub(crate) fn get_client() -> Result<&'static reqwest::Client> { HTTP_CLIENT.as_ref().map_err(|e| anyhow!("{e}")) } -/// Override reqwest's default `ring` with `aws-lc-rs` on macOS. Local -/// M-series benchmarks show ~40 % faster cold resolves; CI bench-phases -/// regresses on Linux/x86_64, so non-macOS targets keep ring via -/// `rustls-tls-native-roots`. -#[cfg(target_os = "macos")] -fn build_rustls_config() -> Result { - // `install_default` consumes the provider by value, so we can't share - // it with the `Arc` below — `default_provider()` is cheap (a struct of - // function pointers), so the duplicate call is fine. Idempotent across - // the process; only the first call wins. Sets the default for any - // other rustls consumer (e.g. `gix`). - let _ = rustls::crypto::aws_lc_rs::default_provider().install_default(); - - let roots = rustls_native_certs::load_native_certs(); - let mut root_store = rustls::RootCertStore::empty(); - for cert in roots.certs { - // Tolerate individual bad roots so one broken cert in the OS - // trust store doesn't brick all requests. - let _ = root_store.add(cert); - } - if !roots.errors.is_empty() { - tracing::debug!( - "rustls-native-certs reported {} non-fatal load issues", - roots.errors.len() - ); - } - - let config = rustls::ClientConfig::builder_with_provider(std::sync::Arc::new( - rustls::crypto::aws_lc_rs::default_provider(), - )) - .with_safe_default_protocol_versions() - .context("rustls safe_default_protocol_versions")? - .with_root_certificates(root_store) - .with_no_client_auth(); - - Ok(config) -} - /// Create a [`reqwest::ClientBuilder`] with TLS, DNS caching, and proxy /// from environment variables. /// @@ -202,11 +164,6 @@ pub fn client_builder() -> Result { // manifest requests open independent TCP streams instead. .http1_only(); - #[cfg(target_os = "macos")] - { - builder = builder.use_preconfigured_tls(build_rustls_config()?); - } - match env_var("ALL_PROXY") { Some(url) => { builder = builder.proxy( diff --git a/next.js b/next.js index e4f62aaf16..8deb31c8d5 160000 --- a/next.js +++ b/next.js @@ -1 +1 @@ -Subproject commit e4f62aaf1606e8a6819192c61b52769c786099ab +Subproject commit 8deb31c8d5dd212afa422e0f4ea17c3cefa4ab91 From 4acc5bdd16ece324be39385f76a95fae1d41d5f6 Mon Sep 17 00:00:00 2001 From: elrrrrrrr Date: Fri, 19 Jun 2026 01:37:09 +0800 Subject: [PATCH 2/3] ci(pm): report release binary size on linux + macos Builds the release `utoo` binary on ubuntu + macos and reports its size to the job summary, plus asserts the trimmed dependency set (no aws-lc-sys / hickory, ring present). Gives size-focused PRs a shipped-artifact size signal on both platforms instead of relying on a one-arch local build. Co-Authored-By: Claude Opus 4.8 (1M context) --- .github/workflows/pm-binary-size.yml | 69 ++++++++++++++++++++++++++++ 1 file changed, 69 insertions(+) create mode 100644 .github/workflows/pm-binary-size.yml diff --git a/.github/workflows/pm-binary-size.yml b/.github/workflows/pm-binary-size.yml new file mode 100644 index 0000000000..54b9878fca --- /dev/null +++ b/.github/workflows/pm-binary-size.yml @@ -0,0 +1,69 @@ +name: utoopm-binary-size + +# Reports the release `utoo` binary size on Linux + macOS and asserts that the +# trimmed dependency set (no aws-lc-rs, no hickory) holds. Lets a size-focused +# PR show the real shipped-artifact size on both platforms, since local builds +# are usually one arch only. Manual + on PRs that touch the relevant crates. +on: + workflow_dispatch: + pull_request: + paths: + - "crates/pm/**" + - "crates/ruborist/**" + - "Cargo.toml" + - "Cargo.lock" + - ".github/workflows/pm-binary-size.yml" + +jobs: + size: + name: ${{ matrix.target }} + strategy: + fail-fast: false + matrix: + include: + - os: ubuntu-latest + target: x86_64-unknown-linux-gnu + - os: macos-latest + target: aarch64-apple-darwin + runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@v4 + + - name: Install Rust + uses: dtolnay/rust-toolchain@stable + with: + toolchain: nightly-2026-04-02 + targets: ${{ matrix.target }} + + - name: Init git submodules + run: git submodule update --init --recursive --depth 1 + + - name: Build release binary + run: cargo build --release --bin utoo --target ${{ matrix.target }} -p utoo-pm + + - name: Report size + run: | + BIN="target/${{ matrix.target }}/release/utoo" + echo "## utoo release binary — ${{ matrix.target }}" >> "$GITHUB_STEP_SUMMARY" + ls -lh "$BIN" + BYTES=$(wc -c < "$BIN") + echo "- size: **$(numfmt --to=iec "$BYTES" 2>/dev/null || echo "$BYTES bytes")** ($BYTES bytes)" >> "$GITHUB_STEP_SUMMARY" + + - name: Assert trimmed dependency set + run: | + # These must NOT be linked: aws-lc (second crypto backend) and hickory + # (DNS resolver replaced by the custom getaddrinfo resolver). + fail=0 + for crate in aws-lc-sys hickory-resolver hickory-proto; do + if cargo tree -p utoo-pm --target ${{ matrix.target }} -i "$crate" >/dev/null 2>&1; then + echo "::error::$crate is still linked — trimming regressed" + fail=1 + else + echo "ok: $crate absent" + fi + done + # ring must be the sole TLS provider. + cargo tree -p utoo-pm --target ${{ matrix.target }} -i ring >/dev/null 2>&1 \ + && echo "ok: ring present (sole TLS provider)" \ + || { echo "::error::ring (TLS provider) missing"; fail=1; } + exit $fail From eb0bd813e732deb8b3461fa77dcc8c15e6e75ba4 Mon Sep 17 00:00:00 2001 From: elrrrrrrr Date: Fri, 19 Jun 2026 10:40:37 +0800 Subject: [PATCH 3/3] perf(pm): resolve git deps via system `git`, drop bundled gix (and h2) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Git dependencies (`git:`/`github:`) were cloned by the bundled `gix` (a full Rust git implementation). Switch to shelling out to the user's `git` — the same thing npm/pnpm/yarn do — and drop gix entirely. This is the single biggest binary win: gix pulls a ~20-crate family, plus `gix-transport`'s default-featured reqwest (which is what kept `h2`/HTTP-2 in the binary), plus `bstr`'s regex-automata. Removing it drops all of that. - `resolver/git.rs` reimplemented on `std::process::Command("git")`: shallow `clone --depth 1 --branch ` for branch/tag/HEAD, full clone + `checkout` for a SHA (or a ref `--branch` can't name); `rev-parse HEAD` pins the `///` dir; the working tree is copied (exec bits + symlinks preserved, `.git` skipped, path-traversal guard kept). Same public API + cache layout — no façade/pm changes. - `git` is now required for git deps; a missing binary errors with a clear message naming it (npm behaves the same). - `native-git` feature drops `gix`, keeping only `tempfile`. - CI size-assert extended to gix/gix-transport/h2 (production tree). Local (macOS arm64, release): 8.6 MB -> **6.4 MB** (10 MB baseline -> 6.4 MB, -36% overall). Verified: e2e git-deps resolves all three spec forms (`github:owner/repo`, `git+https#tag`, `owner/repo#tag`) via system git. Co-Authored-By: Claude Opus 4.8 (1M context) --- .github/workflows/pm-binary-size.yml | 14 +- Cargo.lock | 835 ------------------------ Cargo.toml | 1 - crates/ruborist/Cargo.toml | 10 +- crates/ruborist/src/resolver/builder.rs | 2 +- crates/ruborist/src/resolver/git.rs | 313 +++++---- 6 files changed, 162 insertions(+), 1013 deletions(-) diff --git a/.github/workflows/pm-binary-size.yml b/.github/workflows/pm-binary-size.yml index 54b9878fca..9a29e32765 100644 --- a/.github/workflows/pm-binary-size.yml +++ b/.github/workflows/pm-binary-size.yml @@ -51,12 +51,16 @@ jobs: - name: Assert trimmed dependency set run: | - # These must NOT be linked: aws-lc (second crypto backend) and hickory - # (DNS resolver replaced by the custom getaddrinfo resolver). + # None of these may be linked into the production (non-dev) binary: + # aws-lc-sys second crypto backend (unified to ring) + # hickory-* DNS resolver (replaced by custom getaddrinfo) + # gix/gix-* bundled git (replaced by system `git`) + # h2 HTTP/2 (clients force h1; gix-transport was the + # only non-dev puller) fail=0 - for crate in aws-lc-sys hickory-resolver hickory-proto; do - if cargo tree -p utoo-pm --target ${{ matrix.target }} -i "$crate" >/dev/null 2>&1; then - echo "::error::$crate is still linked — trimming regressed" + for crate in aws-lc-sys hickory-resolver hickory-proto gix gix-transport h2; do + if cargo tree -p utoo-pm --target ${{ matrix.target }} -e no-dev -i "$crate" >/dev/null 2>&1; then + echo "::error::$crate is still linked into the release binary — trimming regressed" fail=1 else echo "ok: $crate absent" diff --git a/Cargo.lock b/Cargo.lock index fc92630cc9..66caad003e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -596,7 +596,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "234113d19d0d7d613b40e86fb654acf958910802bcceab913a4f9e7cda03b1a4" dependencies = [ "memchr", - "regex-automata", "serde", ] @@ -1012,15 +1011,6 @@ version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675" -[[package]] -name = "clru" -version = "0.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "197fd99cb113a8d5d9b6376f3aa817f32c1078f2343b714fff7d2ca44fdf67d5" -dependencies = [ - "hashbrown 0.16.1", -] - [[package]] name = "cmake" version = "0.1.54" @@ -2362,16 +2352,6 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649" -[[package]] -name = "faster-hex" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7223ae2d2f179b803433d9c830478527e92b8117eab39460edae7f1614d9fb73" -dependencies = [ - "heapless 0.8.0", - "serde", -] - [[package]] name = "fastrand" version = "2.3.0" @@ -2435,7 +2415,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bfe33edd8e85a12a67454e37f8c75e730830d83e313556ab9ebf9ee7fbeb3bfb" dependencies = [ "crc32fast", - "libz-rs-sys", "miniz_oxide", ] @@ -2732,757 +2711,6 @@ dependencies = [ "stable_deref_trait", ] -[[package]] -name = "gix" -version = "0.72.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01237e8d3d78581f71642be8b0c2ae8c0b2b5c251c9c5d9ebbea3c1ea280dce8" -dependencies = [ - "gix-actor", - "gix-attributes", - "gix-command", - "gix-commitgraph", - "gix-config", - "gix-credentials", - "gix-date", - "gix-diff", - "gix-discover", - "gix-features", - "gix-filter", - "gix-fs", - "gix-glob", - "gix-hash", - "gix-hashtable", - "gix-ignore", - "gix-index", - "gix-lock", - "gix-negotiate", - "gix-object", - "gix-odb", - "gix-pack", - "gix-path", - "gix-pathspec", - "gix-prompt", - "gix-protocol", - "gix-ref", - "gix-refspec", - "gix-revision", - "gix-revwalk", - "gix-sec", - "gix-shallow", - "gix-submodule", - "gix-tempfile", - "gix-trace", - "gix-transport", - "gix-traverse", - "gix-url", - "gix-utils", - "gix-validate", - "gix-worktree", - "once_cell", - "smallvec", - "thiserror 2.0.17", -] - -[[package]] -name = "gix-actor" -version = "0.35.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "987a51a7e66db6ef4dc030418eb2a42af6b913a79edd8670766122d8af3ba59e" -dependencies = [ - "bstr", - "gix-date", - "gix-utils", - "itoa", - "thiserror 2.0.17", - "winnow", -] - -[[package]] -name = "gix-attributes" -version = "0.26.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f50d813d5c2ce9463ba0c29eea90060df08e38ad8f34b8a192259f8bce5c078" -dependencies = [ - "bstr", - "gix-glob", - "gix-path", - "gix-quote", - "gix-trace", - "kstring", - "smallvec", - "thiserror 2.0.17", - "unicode-bom", -] - -[[package]] -name = "gix-bitmap" -version = "0.2.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e150161b8a75b5860521cb876b506879a3376d3adc857ec7a9d35e7c6a5e531" -dependencies = [ - "thiserror 2.0.17", -] - -[[package]] -name = "gix-chunk" -version = "0.4.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c356b3825677cb6ff579551bb8311a81821e184453cbd105e2fc5311b288eeb" -dependencies = [ - "thiserror 2.0.17", -] - -[[package]] -name = "gix-command" -version = "0.6.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46f9c425730a654835351e6da8c3c69ba1804f8b8d4e96d027254151138d5c64" -dependencies = [ - "bstr", - "gix-path", - "gix-quote", - "gix-trace", - "shell-words", -] - -[[package]] -name = "gix-commitgraph" -version = "0.28.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e05050fd6caa6c731fe3bd7f9485b3b520be062d3d139cb2626e052d6c127951" -dependencies = [ - "bstr", - "gix-chunk", - "gix-hash", - "memmap2 0.9.8", - "thiserror 2.0.17", -] - -[[package]] -name = "gix-config" -version = "0.45.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48f3c8f357ae049bfb77493c2ec9010f58cfc924ae485e1116c3718fc0f0d881" -dependencies = [ - "bstr", - "gix-config-value", - "gix-features", - "gix-glob", - "gix-path", - "gix-ref", - "gix-sec", - "memchr", - "once_cell", - "smallvec", - "thiserror 2.0.17", - "unicode-bom", - "winnow", -] - -[[package]] -name = "gix-config-value" -version = "0.15.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c489abb061c74b0c3ad790e24a606ef968cebab48ec673d6a891ece7d5aef64" -dependencies = [ - "bitflags 2.9.4", - "bstr", - "gix-path", - "libc", - "thiserror 2.0.17", -] - -[[package]] -name = "gix-credentials" -version = "0.29.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce1c7307e36026b6088e5b12014ffe6d4f509c911ee453e22a7be4003a159c9b" -dependencies = [ - "bstr", - "gix-command", - "gix-config-value", - "gix-path", - "gix-prompt", - "gix-sec", - "gix-trace", - "gix-url", - "thiserror 2.0.17", -] - -[[package]] -name = "gix-date" -version = "0.10.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "661245d045aa7c16ba4244daaabd823c562c3e45f1f25b816be2c57ee09f2171" -dependencies = [ - "bstr", - "itoa", - "jiff", - "smallvec", - "thiserror 2.0.17", -] - -[[package]] -name = "gix-diff" -version = "0.52.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e9b43e95fe352da82a969f0c84ff860c2de3e724d93f6681fedbcd6c917f252" -dependencies = [ - "bstr", - "gix-hash", - "gix-object", - "thiserror 2.0.17", -] - -[[package]] -name = "gix-discover" -version = "0.40.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dccfe3e25b4ea46083916c56db3ba9d1e6ef6dce54da485f0463f9fc0fe1837c" -dependencies = [ - "bstr", - "dunce", - "gix-fs", - "gix-hash", - "gix-path", - "gix-ref", - "gix-sec", - "thiserror 2.0.17", -] - -[[package]] -name = "gix-features" -version = "0.42.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56f4399af6ec4fd9db84dd4cf9656c5c785ab492ab40a7c27ea92b4241923fed" -dependencies = [ - "bytes", - "crc32fast", - "flate2", - "gix-path", - "gix-trace", - "gix-utils", - "libc", - "once_cell", - "prodash", - "thiserror 2.0.17", - "walkdir", -] - -[[package]] -name = "gix-filter" -version = "0.19.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ecf004912949bbcf308d71aac4458321748ecb59f4d046830d25214208c471f1" -dependencies = [ - "bstr", - "encoding_rs", - "gix-attributes", - "gix-command", - "gix-hash", - "gix-object", - "gix-packetline-blocking", - "gix-path", - "gix-quote", - "gix-trace", - "gix-utils", - "smallvec", - "thiserror 2.0.17", -] - -[[package]] -name = "gix-fs" -version = "0.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67a0637149b4ef24d3ea55f81f77231401c8463fae6da27331c987957eb597c7" -dependencies = [ - "bstr", - "fastrand", - "gix-features", - "gix-path", - "gix-utils", - "thiserror 2.0.17", -] - -[[package]] -name = "gix-glob" -version = "0.20.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90181472925b587f6079698f79065ff64786e6d6c14089517a1972bca99fb6e9" -dependencies = [ - "bitflags 2.9.4", - "bstr", - "gix-features", - "gix-path", -] - -[[package]] -name = "gix-hash" -version = "0.18.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d4900562c662852a6b42e2ef03442eccebf24f047d8eab4f23bc12ef0d785d8" -dependencies = [ - "faster-hex", - "gix-features", - "sha1-checked", - "thiserror 2.0.17", -] - -[[package]] -name = "gix-hashtable" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5b5cb3c308b4144f2612ff64e32130e641279fcf1a84d8d40dad843b4f64904" -dependencies = [ - "gix-hash", - "hashbrown 0.14.5", - "parking_lot", -] - -[[package]] -name = "gix-ignore" -version = "0.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae358c3c96660b10abc7da63c06788dfded603e717edbd19e38c6477911b71c8" -dependencies = [ - "bstr", - "gix-glob", - "gix-path", - "gix-trace", - "unicode-bom", -] - -[[package]] -name = "gix-index" -version = "0.40.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b38e919efd59cb8275d23ad2394b2ab9d002007b27620e145d866d546403b665" -dependencies = [ - "bitflags 2.9.4", - "bstr", - "filetime", - "fnv", - "gix-bitmap", - "gix-features", - "gix-fs", - "gix-hash", - "gix-lock", - "gix-object", - "gix-traverse", - "gix-utils", - "gix-validate", - "hashbrown 0.14.5", - "itoa", - "libc", - "memmap2 0.9.8", - "rustix 1.1.2", - "smallvec", - "thiserror 2.0.17", -] - -[[package]] -name = "gix-lock" -version = "17.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "570f8b034659f256366dc90f1a24924902f20acccd6a15be96d44d1269e7a796" -dependencies = [ - "gix-tempfile", - "gix-utils", - "thiserror 2.0.17", -] - -[[package]] -name = "gix-negotiate" -version = "0.20.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e1ea901acc4d5b44553132a29e8697210cb0e739b2d9752d713072e9391e3c9" -dependencies = [ - "bitflags 2.9.4", - "gix-commitgraph", - "gix-date", - "gix-hash", - "gix-object", - "gix-revwalk", - "smallvec", - "thiserror 2.0.17", -] - -[[package]] -name = "gix-object" -version = "0.49.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d957ca3640c555d48bb27f8278c67169fa1380ed94f6452c5590742524c40fbb" -dependencies = [ - "bstr", - "gix-actor", - "gix-date", - "gix-features", - "gix-hash", - "gix-hashtable", - "gix-path", - "gix-utils", - "gix-validate", - "itoa", - "smallvec", - "thiserror 2.0.17", - "winnow", -] - -[[package]] -name = "gix-odb" -version = "0.69.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "868f703905fdbcfc1bd750942f82419903ecb7039f5288adb5206d6de405e0c9" -dependencies = [ - "arc-swap", - "gix-date", - "gix-features", - "gix-fs", - "gix-hash", - "gix-hashtable", - "gix-object", - "gix-pack", - "gix-path", - "gix-quote", - "parking_lot", - "tempfile", - "thiserror 2.0.17", -] - -[[package]] -name = "gix-pack" -version = "0.59.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d49c55d69c8449f2a0a5a77eb9cbacfebb6b0e2f1215f0fc23a4cb60528a450" -dependencies = [ - "clru", - "gix-chunk", - "gix-features", - "gix-hash", - "gix-hashtable", - "gix-object", - "gix-path", - "gix-tempfile", - "memmap2 0.9.8", - "parking_lot", - "smallvec", - "thiserror 2.0.17", -] - -[[package]] -name = "gix-packetline" -version = "0.19.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64286a8b5148e76ab80932e72762dd27ccf6169dd7a134b027c8a262a8262fcf" -dependencies = [ - "bstr", - "faster-hex", - "gix-trace", - "thiserror 2.0.17", -] - -[[package]] -name = "gix-packetline-blocking" -version = "0.19.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89c59c3ad41e68cb38547d849e9ef5ccfc0d00f282244ba1441ae856be54d001" -dependencies = [ - "bstr", - "faster-hex", - "gix-trace", - "thiserror 2.0.17", -] - -[[package]] -name = "gix-path" -version = "0.10.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cb06c3e4f8eed6e24fd915fa93145e28a511f4ea0e768bae16673e05ed3f366" -dependencies = [ - "bstr", - "gix-trace", - "gix-validate", - "thiserror 2.0.17", -] - -[[package]] -name = "gix-pathspec" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce061c50e5f8f7c830cacb3da3e999ae935e283ce8522249f0ce2256d110979d" -dependencies = [ - "bitflags 2.9.4", - "bstr", - "gix-attributes", - "gix-config-value", - "gix-glob", - "gix-path", - "thiserror 2.0.17", -] - -[[package]] -name = "gix-prompt" -version = "0.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "868e6516dfa16fdcbc5f8c935167d085f2ae65ccd4c9476a4319579d12a69d8d" -dependencies = [ - "gix-command", - "gix-config-value", - "parking_lot", - "rustix 1.1.2", - "thiserror 2.0.17", -] - -[[package]] -name = "gix-protocol" -version = "0.50.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5c17d78bb0414f8d60b5f952196dc2e47ec320dca885de9128ecdb4a0e38401" -dependencies = [ - "bstr", - "gix-credentials", - "gix-date", - "gix-features", - "gix-hash", - "gix-lock", - "gix-negotiate", - "gix-object", - "gix-ref", - "gix-refspec", - "gix-revwalk", - "gix-shallow", - "gix-trace", - "gix-transport", - "gix-utils", - "maybe-async", - "thiserror 2.0.17", - "winnow", -] - -[[package]] -name = "gix-quote" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e912ec04b7b1566a85ad486db0cab6b9955e3e32bcd3c3a734542ab3af084c5b" -dependencies = [ - "bstr", - "gix-utils", - "thiserror 2.0.17", -] - -[[package]] -name = "gix-ref" -version = "0.52.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1b7985657029684d759f656b09abc3e2c73085596d5cdb494428823970a7762" -dependencies = [ - "gix-actor", - "gix-features", - "gix-fs", - "gix-hash", - "gix-lock", - "gix-object", - "gix-path", - "gix-tempfile", - "gix-utils", - "gix-validate", - "memmap2 0.9.8", - "thiserror 2.0.17", - "winnow", -] - -[[package]] -name = "gix-refspec" -version = "0.30.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "445ed14e3db78e8e79980085e3723df94e1c8163b3ae5bc8ed6a8fe6cf983b42" -dependencies = [ - "bstr", - "gix-hash", - "gix-revision", - "gix-validate", - "smallvec", - "thiserror 2.0.17", -] - -[[package]] -name = "gix-revision" -version = "0.34.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78d0b8e5cbd1c329e25383e088cb8f17439414021a643b30afa5146b71e3c65d" -dependencies = [ - "bstr", - "gix-commitgraph", - "gix-date", - "gix-hash", - "gix-object", - "gix-revwalk", - "thiserror 2.0.17", -] - -[[package]] -name = "gix-revwalk" -version = "0.20.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bc756b73225bf005ddeb871d1ca7b3c33e2417d0d53e56effa5a36765b52b28" -dependencies = [ - "gix-commitgraph", - "gix-date", - "gix-hash", - "gix-hashtable", - "gix-object", - "smallvec", - "thiserror 2.0.17", -] - -[[package]] -name = "gix-sec" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0dabbc78c759ecc006b970339394951b2c8e1e38a37b072c105b80b84c308fd" -dependencies = [ - "bitflags 2.9.4", - "gix-path", - "libc", - "windows-sys 0.59.0", -] - -[[package]] -name = "gix-shallow" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b9a6f6e34d6ede08f522d89e5c7990b4f60524b8ae6ebf8e850963828119ad4" -dependencies = [ - "bstr", - "gix-hash", - "gix-lock", - "thiserror 2.0.17", -] - -[[package]] -name = "gix-submodule" -version = "0.19.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f51472f05a450cc61bc91ed2f62fb06e31e2bbb31c420bc4be8793f26c8b0c1" -dependencies = [ - "bstr", - "gix-config", - "gix-path", - "gix-pathspec", - "gix-refspec", - "gix-url", - "thiserror 2.0.17", -] - -[[package]] -name = "gix-tempfile" -version = "17.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c750e8c008453a2dba67a2b0d928b7716e05da31173a3f5e351d5457ad4470aa" -dependencies = [ - "gix-fs", - "libc", - "once_cell", - "parking_lot", - "tempfile", -] - -[[package]] -name = "gix-trace" -version = "0.1.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f69a13643b8437d4ca6845e08143e847a36ca82903eed13303475d0ae8b162e0" - -[[package]] -name = "gix-transport" -version = "0.47.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edfe22ba26d4b65c17879f12b9882eafe65d3c8611c933b272fce2c10f546f59" -dependencies = [ - "base64 0.22.1", - "bstr", - "gix-command", - "gix-credentials", - "gix-features", - "gix-packetline", - "gix-quote", - "gix-sec", - "gix-url", - "reqwest 0.12.24", - "thiserror 2.0.17", -] - -[[package]] -name = "gix-traverse" -version = "0.46.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8648172f85aca3d6e919c06504b7ac26baef54e04c55eb0100fa588c102cc33" -dependencies = [ - "bitflags 2.9.4", - "gix-commitgraph", - "gix-date", - "gix-hash", - "gix-hashtable", - "gix-object", - "gix-revwalk", - "smallvec", - "thiserror 2.0.17", -] - -[[package]] -name = "gix-url" -version = "0.31.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42a1ad0b04a5718b5cb233e6888e52a9b627846296161d81dcc5eb9203ec84b8" -dependencies = [ - "bstr", - "gix-features", - "gix-path", - "percent-encoding", - "thiserror 2.0.17", - "url", -] - -[[package]] -name = "gix-utils" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "befcdbdfb1238d2854591f760a48711bed85e72d80a10e8f2f93f656746ef7c5" -dependencies = [ - "fastrand", - "unicode-normalization", -] - -[[package]] -name = "gix-validate" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b1e63a5b516e970a594f870ed4571a8fdcb8a344e7bd407a20db8bd61dbfde4" -dependencies = [ - "bstr", - "thiserror 2.0.17", -] - -[[package]] -name = "gix-worktree" -version = "0.41.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54f1916f8d928268300c977d773dd70a8746b646873b77add0a34876a8c847e9" -dependencies = [ - "bstr", - "gix-attributes", - "gix-features", - "gix-fs", - "gix-glob", - "gix-hash", - "gix-ignore", - "gix-index", - "gix-object", - "gix-path", - "gix-validate", -] - [[package]] name = "glob" version = "0.3.3" @@ -4473,15 +3701,6 @@ dependencies = [ "libc", ] -[[package]] -name = "kstring" -version = "2.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "558bf9508a558512042d3095138b1f7b8fe90c5467d94f9f1da28b3731c5dbd1" -dependencies = [ - "static_assertions", -] - [[package]] name = "lazy_static" version = "1.5.0" @@ -4649,15 +3868,6 @@ version = "1.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c6639b70a7ce854b79c70d7e83f16b5dc0137cc914f3d7d03803b513ecc67ac" -[[package]] -name = "libz-rs-sys" -version = "0.5.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c10501e7805cee23da17c7790e59df2870c0d4043ec6d03f67d31e2b53e77415" -dependencies = [ - "zlib-rs", -] - [[package]] name = "lightningcss" version = "1.0.0-alpha.71" @@ -4854,17 +4064,6 @@ version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94" -[[package]] -name = "maybe-async" -version = "0.2.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cf92c10c7e361d6b99666ec1c6f9805b0bea2c3bd8c78dc6fe98ac5bd78db11" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.106", -] - [[package]] name = "maybe-owned" version = "0.3.4" @@ -6363,16 +5562,6 @@ dependencies = [ "unicode-ident", ] -[[package]] -name = "prodash" -version = "29.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f04bb108f648884c23b98a0e940ebc2c93c0c3b89f04dbaf7eb8256ce617d1bc" -dependencies = [ - "log", - "parking_lot", -] - [[package]] name = "prost" version = "0.13.5" @@ -6938,7 +6127,6 @@ dependencies = [ "base64 0.22.1", "bytes", "encoding_rs", - "futures-channel", "futures-core", "futures-util", "h2 0.4.12", @@ -7631,16 +6819,6 @@ dependencies = [ "digest", ] -[[package]] -name = "sha1-checked" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89f599ac0c323ebb1c6082821a54962b839832b03984598375bff3975b804423" -dependencies = [ - "digest", - "sha1", -] - [[package]] name = "sha2" version = "0.10.9" @@ -11103,12 +10281,6 @@ version = "2.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "75b844d17643ee918803943289730bec8aac480150456169e647ed0b576ba539" -[[package]] -name = "unicode-bom" -version = "2.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7eec5d1121208364f6793f7d2e222bf75a915c19557537745b195b253dd64217" - [[package]] name = "unicode-id" version = "0.3.6" @@ -11292,7 +10464,6 @@ dependencies = [ "deno_semver", "dirs 5.0.1", "futures", - "gix", "js-sys", "libc", "libdeflater", @@ -13248,12 +12419,6 @@ dependencies = [ "syn 2.0.106", ] -[[package]] -name = "zlib-rs" -version = "0.5.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40990edd51aae2c2b6907af74ffb635029d5788228222c4bb811e9351c0caad3" - [[package]] name = "zmij" version = "1.0.21" diff --git a/Cargo.toml b/Cargo.toml index eb2b7bba32..135af01f10 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -43,7 +43,6 @@ napi = { version = "2", default-features = false, f "tokio_rt", ] } napi-derive = "2" -gix = { version = "0.72", default-features = false } once_cell = "1.17.1" owo-colors = "4.2.0" pathdiff = "0.2.1" diff --git a/crates/ruborist/Cargo.toml b/crates/ruborist/Cargo.toml index 9a4d1fe5a3..d05b9481b3 100644 --- a/crates/ruborist/Cargo.toml +++ b/crates/ruborist/Cargo.toml @@ -35,11 +35,9 @@ reqwest = { version = "0.12", default-features = false, features = ["json", # Shared URL-hash + staging helpers for native-only resolvers (git / http) sha2 = { workspace = true, optional = true } tempfile = { workspace = true, optional = true } -# Git clone (gated on `native-git`) -gix = { workspace = true, features = [ - "blocking-http-transport-reqwest-rust-tls", - "blocking-network-client", -], optional = true } +# Git deps shell out to the system `git` (see resolver/git.rs) — no bundled git +# implementation, which also keeps gix-transport's HTTP/2 reqwest stack out of +# the binary. `native-git` now only needs `tempfile` for the clone scratch dir. # HTTP tarball extraction (gated on `http-tarball`) # Uses libdeflater to match pm's extractor and avoid pulling a second gzip # implementation (flate2) into the workspace. @@ -79,4 +77,4 @@ default = [] # URL-hash disambiguator also keeps HTTP tarball slots from colliding with # registry tarball slots (//). http-tarball = ["libdeflater", "sha2", "tar", "tempfile"] -native-git = ["gix", "tempfile"] +native-git = ["tempfile"] diff --git a/crates/ruborist/src/resolver/builder.rs b/crates/ruborist/src/resolver/builder.rs index ba57da84f0..506dd045e5 100644 --- a/crates/ruborist/src/resolver/builder.rs +++ b/crates/ruborist/src/resolver/builder.rs @@ -30,7 +30,7 @@ use crate::spec::{Catalogs, PackageSpec, Protocol}; use crate::traits::progress::{BuildEvent, EventReceiver, NoopReceiver}; use crate::traits::registry::ResolvedPackage; -/// Dispatch a git/github spec to the real `gix`-backed resolver when the +/// Dispatch a git/github spec to the system-`git`-backed resolver when the /// `native-git` feature is enabled, otherwise error with a hint. async fn resolve_git_dep( cache_dir: Option<&std::path::Path>, diff --git a/crates/ruborist/src/resolver/git.rs b/crates/ruborist/src/resolver/git.rs index 3fa0e52394..30b810ff9c 100644 --- a/crates/ruborist/src/resolver/git.rs +++ b/crates/ruborist/src/resolver/git.rs @@ -1,16 +1,18 @@ -//! Git clone backend powered by `gix`. +//! Git clone backend powered by the system `git` binary. //! //! Git packages are stored under `///`, the same //! `//` layout used by registry tarballs, so `utoo clean` works //! uniformly without any git-specific knowledge. //! -//! The `gix` crate uses blocking HTTP transport; all network and heavy I/O -//! is run in a `tokio::task::spawn_blocking` thread so the async executor is -//! never blocked. +//! Rather than bundling a Rust git implementation, we shell out to the user's +//! `git` (the same approach npm/pnpm/yarn take) — it keeps the binary small, +//! reuses the user's existing git auth/config, and avoids carrying a second +//! HTTP+TLS stack. `git` is required only for `git:`/`github:` dependencies; if +//! it is missing, a clear error names it. All clone/checkout work runs in a +//! `tokio::task::spawn_blocking` thread so the async executor is never blocked. -#[cfg(unix)] -use std::os::unix::fs::PermissionsExt; use std::path::Path; +use std::process::Command; use std::sync::Arc; use anyhow::{Context, Result, anyhow}; @@ -62,32 +64,34 @@ fn try_inject_token_into_https_url(url: &str, token: &str) -> String { } } -/// Read `package.json` from the root of a git tree and deserialize it as a -/// [`CoreVersionManifest`]. +/// Run `git` with the given args (optionally in `cwd`), returning trimmed stdout. /// -/// Only reads the single `package.json` blob — does **not** extract the full -/// tree, so this is cheap even for large repositories. -fn read_pkg_manifest( - repo: &gix::Repository, - tree_id: gix::ObjectId, -) -> Result { - let tree = repo - .find_object(tree_id)? - .try_into_tree() - .map_err(|e| anyhow!("expected tree object: {e}"))?; - - for entry in tree.iter() { - let entry = entry?; - if entry.filename() == b"package.json" { - let obj = repo.find_object(entry.object_id())?; - let manifest: CoreVersionManifest = serde_json::from_slice(&obj.data) - .context("Failed to parse package.json from git tree")?; - return Ok(manifest); +/// `GIT_TERMINAL_PROMPT=0` keeps a missing-credentials clone from hanging on an +/// interactive prompt. A missing `git` binary is reported as a clear, named +/// error rather than a generic spawn failure. +fn run_git(cwd: Option<&Path>, args: &[&str]) -> Result { + let mut cmd = Command::new("git"); + if let Some(dir) = cwd { + cmd.current_dir(dir); + } + cmd.args(args).env("GIT_TERMINAL_PROMPT", "0"); + + let output = cmd.output().map_err(|e| { + if e.kind() == std::io::ErrorKind::NotFound { + anyhow!("`git` command not found in PATH — it is required to resolve git dependencies") + } else { + anyhow!("failed to run git: {e}") } + })?; + + if !output.status.success() { + anyhow::bail!( + "git {} failed: {}", + args.join(" "), + String::from_utf8_lossy(&output.stderr).trim() + ); } - Err(anyhow!( - "package.json not found in git repo root (does the repo have a package.json?)" - )) + Ok(String::from_utf8_lossy(&output.stdout).trim().to_string()) } /// Build a [`GitCloneResult`] from a cached package directory on disk. @@ -114,86 +118,104 @@ fn read_cached_git_result( )) } -/// Recursively extract a git tree to a directory on disk. +/// Copy a checked-out working tree into `dest`, skipping `.git` and preserving +/// executable bits (via `std::fs::copy`) and symlinks. /// -/// `root_dest` is the top-level extraction directory; symlinks that would -/// escape it are skipped to prevent path-traversal attacks. -fn extract_tree_to_dir( - repo: &gix::Repository, - tree_id: gix::ObjectId, - dest: &Path, - root_dest: &Path, -) -> Result<()> { - let tree = repo - .find_object(tree_id)? - .try_into_tree() - .map_err(|e| anyhow!("expected tree object: {e}"))?; - - for entry in tree.iter() { +/// `root_dest` is the top-level destination; symlinks whose target would escape +/// it are skipped to prevent path-traversal attacks (matching the previous +/// tree-extraction behaviour). +fn copy_worktree(src: &Path, dest: &Path, root_dest: &Path) -> Result<()> { + for entry in std::fs::read_dir(src)? { let entry = entry?; - let name = - std::str::from_utf8(entry.filename()).context("non-UTF-8 filename in git tree")?; - let entry_path = dest.join(name); - - match entry.mode().kind() { - gix::object::tree::EntryKind::Tree => { - std::fs::create_dir_all(&entry_path)?; - extract_tree_to_dir(repo, entry.object_id(), &entry_path, root_dest)?; + let name = entry.file_name(); + // Skip git's own metadata (only present at the repo root, but a + // submodule gitlink could leave a `.git` file deeper — skip defensively). + if name == ".git" { + continue; + } + let from = entry.path(); + let to = dest.join(&name); + let file_type = entry.file_type()?; + + if file_type.is_symlink() { + let target = std::fs::read_link(&from)?; + // Validate that the symlink target does not escape the destination + // root to prevent path-traversal attacks. + let resolved = to.parent().unwrap_or(dest).join(&target); + if !resolved.starts_with(root_dest) { + tracing::debug!( + "Skipping symlink {} -> {} (escapes extraction dir)", + to.display(), + target.display() + ); + continue; } - gix::object::tree::EntryKind::Blob | gix::object::tree::EntryKind::BlobExecutable => { - let obj = repo.find_object(entry.object_id())?; - std::fs::write(&entry_path, &obj.data)?; - - // Set executable permission on Unix - #[cfg(unix)] - if entry.mode().kind() == gix::object::tree::EntryKind::BlobExecutable { - let perms = std::fs::Permissions::from_mode(0o755); - if let Err(e) = std::fs::set_permissions(&entry_path, perms) { - tracing::debug!( - "Failed to set executable permission on {}: {e}", - entry_path.display() - ); - } - } + #[cfg(unix)] + if let Err(e) = std::os::unix::fs::symlink(&target, &to) { + tracing::debug!("Failed to create symlink {}: {e}", to.display()); } - gix::object::tree::EntryKind::Link => { - let obj = repo.find_object(entry.object_id())?; - let target = std::str::from_utf8(&obj.data).context("non-UTF-8 symlink target")?; - - // Validate that the symlink target does not escape the - // extraction root to prevent path-traversal attacks. - let resolved = entry_path.parent().unwrap_or(dest).join(target); - if !resolved.starts_with(root_dest) { - tracing::debug!( - "Skipping symlink {} -> {} (escapes extraction dir)", - entry_path.display(), - target - ); - continue; - } - - #[cfg(unix)] - if let Err(e) = std::os::unix::fs::symlink(target, &entry_path) { - tracing::debug!("Failed to create symlink {}: {e}", entry_path.display()); - } - #[cfg(not(unix))] - { - let _ = target; - } + #[cfg(not(unix))] + { + let _ = target; } - gix::object::tree::EntryKind::Commit => { - // Submodule reference – skip silently - tracing::debug!( - "Skipping submodule entry at {:?}", - std::str::from_utf8(entry.filename()).unwrap_or("") - ); + } else if file_type.is_dir() { + std::fs::create_dir_all(&to)?; + copy_worktree(&from, &to, root_dest)?; + } else { + // `std::fs::copy` preserves the permission bits (incl. the exec bit) + // on Unix, so an executable in the repo stays executable. + std::fs::copy(&from, &to)?; + } + } + Ok(()) +} + +/// Clone `clone_url` (optionally at `commit_ish`) into `dest`, shelling out to +/// `git`. Prefers a shallow clone for branch/tag/HEAD; a full SHA (or a ref a +/// shallow `--branch` can't name) falls back to a full clone + checkout. +fn git_clone_into( + dest: &Path, + clone_url: &str, + commit_ish: Option<&str>, + is_full_sha: bool, +) -> Result<()> { + let dest_str = dest + .to_str() + .ok_or_else(|| anyhow!("non-UTF-8 clone path"))?; + + // Shallow fast path: a named branch/tag, or the default HEAD. + if !is_full_sha { + let shallow = match commit_ish { + Some(ref_name) => run_git( + None, + &[ + "clone", "--quiet", "--depth", "1", "--branch", ref_name, clone_url, dest_str, + ], + ), + None => run_git( + None, + &["clone", "--quiet", "--depth", "1", clone_url, dest_str], + ), + }; + match shallow { + Ok(_) => return Ok(()), + // `--branch` only names branches/tags; a short SHA (or other ref) + // falls through to a full clone + checkout below. + Err(_) => { + let _ = std::fs::remove_dir_all(dest); } } } + + // Full clone, then check out the requested commit-ish (SHA, short SHA, …). + run_git(None, &["clone", "--quiet", clone_url, dest_str])?; + if let Some(target) = commit_ish { + run_git(Some(dest), &["checkout", "--quiet", target])?; + } Ok(()) } -/// Blocking core that does the actual clone + extraction via gix. +/// Blocking core that does the actual clone + checkout via the `git` binary. fn clone_repo_blocking( cache_dir: &Path, clone_url: &str, @@ -205,14 +227,14 @@ fn clone_repo_blocking( // This MUST run before the early cache check which does cache_dir.join(name). validate_package_name(name)?; - // A full 40-hex SHA might not be reachable at depth 1 (it may not be - // HEAD), so only use shallow fetch for branch/tag refs and bare HEAD. + // A full 40-hex SHA can't be named by a shallow `--branch`, so it takes the + // full-clone path. let is_full_sha = commit_ish.is_some_and(|c| c.len() == 40 && c.chars().all(|ch| ch.is_ascii_hexdigit())); - // Early cache check for full SHA specs — skip network fetch entirely. - // The package name is known at call site so we check the exact path - // rather than scanning directories. + // Early cache check for full SHA specs — skip the clone entirely. The + // package name is known at the call site so we check the exact path rather + // than scanning directories. if is_full_sha { let sha = commit_ish.unwrap(); let package_dir = cache_dir.join(name).join(sha); @@ -242,71 +264,34 @@ fn clone_repo_blocking( None => clone_url.to_string(), }; - // Clone to a temporary directory (bare) + // Clone into a temporary working tree. let temp_dir = tempfile::tempdir().context("Failed to create temp directory for git clone")?; + let checkout = temp_dir.path().join("repo"); - let url = gix::url::parse(effective_url.as_str().into())?; - - let mut prepare = gix::prepare_clone_bare(url, temp_dir.path())?; - - if !is_full_sha { - prepare = prepare.with_shallow(gix::remote::fetch::Shallow::DepthAtRemote( - std::num::NonZeroU32::MIN, - )); - } - - // For branch/tag refs, tell gix which ref to fetch. - // Full-SHA requests have no corresponding ref name. - if let Some(ref_name) = commit_ish - && !is_full_sha - { - prepare = prepare.with_ref_name(Some(ref_name))?; - } - - let (checkout, _outcome) = prepare - .fetch_only(gix::progress::Discard, &gix::interrupt::IS_INTERRUPTED) - .map_err(|e| { - let mut msg = format!("git fetch failed for '{}': {e}", clone_url); - if token.is_none() - && (clone_url.contains("github.com") || clone_url.contains("gitlab.com")) - { - msg.push_str( - "\n\nTip: set GITHUB_TOKEN or GH_TOKEN for private repos / higher rate limits", - ); - } - anyhow!(msg) - })?; - - // Resolve the commit: if we have a full 40-char hex SHA use it directly, - // otherwise resolve HEAD of the fetched ref. - let commit_id = match commit_ish { - Some(sha) if sha.len() == 40 && sha.chars().all(|ch| ch.is_ascii_hexdigit()) => { - gix::ObjectId::from_hex(sha.as_bytes()) - .map_err(|e| anyhow!("invalid commit SHA '{}': {e}", sha))? + git_clone_into(&checkout, &effective_url, commit_ish, is_full_sha).map_err(|e| { + let mut msg = format!("git clone failed for '{clone_url}': {e}"); + if token.is_none() && (clone_url.contains("github.com") || clone_url.contains("gitlab.com")) + { + msg.push_str( + "\n\nTip: set GITHUB_TOKEN or GH_TOKEN for private repos / higher rate limits", + ); } - _ => checkout - .head_id() - .map_err(|e| anyhow!("could not resolve HEAD after fetch: {e}"))? - .detach(), - }; - - let commit_hex = commit_id.to_string(); - - // Get the commit tree - let commit = checkout - .find_object(commit_id)? - .try_into_commit() - .map_err(|e| anyhow!("object is not a commit: {e}"))?; + anyhow!(msg) + })?; - let tree_id = commit.tree_id()?; + // The checked-out commit SHA pins the cache directory. + let commit_hex = run_git(Some(&checkout), &["rev-parse", "HEAD"]) + .context("could not resolve HEAD after clone")?; - // Deserialize package.json from the git blob directly into a CoreVersionManifest. - // This is cheap (single object read) and avoids any serde_json::Value round-trip. - let mut manifest = read_pkg_manifest(&checkout, tree_id.into())?; + // Read package.json straight from the working tree. + let pkg_bytes = std::fs::read(checkout.join("package.json")) + .context("package.json not found in git repo root (does the repo have a package.json?)")?; + let mut manifest: CoreVersionManifest = + serde_json::from_slice(&pkg_bytes).context("Failed to parse package.json from git repo")?; - // Cache path: /// - // Using the full commit SHA as the version directory avoids collisions - // with registry versions and between different commits of the same repo. + // Cache path: ///. Using the full commit SHA as + // the version directory avoids collisions with registry versions and between + // different commits of the same repo. let pinned_url = format!("{}#{}", resolved_url, commit_hex); finalize_non_registry_manifest(&mut manifest, pinned_url.clone())?; @@ -315,9 +300,7 @@ fn clone_repo_blocking( return Ok(GitCloneResult::new(package_dir, pinned_url, manifest)); } - commit_cache_dir_atomic(&package_dir, |stage| { - extract_tree_to_dir(&checkout, tree_id.into(), stage, stage) - })?; + commit_cache_dir_atomic(&package_dir, |stage| copy_worktree(&checkout, stage, stage))?; Ok(GitCloneResult::new(package_dir, pinned_url, manifest)) }