diff --git a/.github/workflows/pm-ci.yml b/.github/workflows/pm-ci.yml index a691c2c223..170df6d09d 100644 --- a/.github/workflows/pm-ci.yml +++ b/.github/workflows/pm-ci.yml @@ -14,6 +14,51 @@ permissions: contents: read jobs: + binary-size: + if: (github.event_name == 'push' && contains(github.event.head_commit.message, '(pm)')) || (github.event_name == 'pull_request' && contains(github.event.pull_request.title, '(pm)')) + name: utoopm-binary-size + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Init git submodules + run: git submodule update --init --recursive --depth 1 + + - name: Install Rust + uses: dtolnay/rust-toolchain@stable + with: + toolchain: nightly-2026-04-02 + targets: x86_64-unknown-linux-gnu + + - name: Assert release dependency trim + shell: bash + run: | + set -euo pipefail + mkdir -p target + cargo tree -p utoo-pm --target x86_64-unknown-linux-gnu -e normal,build --prefix none > target/utoo-pm-release-tree.txt + + if grep -E '^(h2|hickory-resolver|gix|aws-lc-rs) ' target/utoo-pm-release-tree.txt; then + echo "Unexpected heavyweight dependency found in utoo-pm release tree" >&2 + exit 1 + fi + + - name: Build release binary + run: cargo build -p utoo-pm --release --bin utoo --target x86_64-unknown-linux-gnu + + - name: Report binary size + shell: bash + run: | + set -euo pipefail + binary="target/x86_64-unknown-linux-gnu/release/utoo" + bytes="$(stat -c '%s' "$binary")" + { + echo "### utoo-pm binary size" + echo + echo "- target: x86_64-unknown-linux-gnu" + echo "- bytes: $bytes" + echo "- binary: $binary" + } >> "$GITHUB_STEP_SUMMARY" + test: if: (github.event_name == 'push' && contains(github.event.head_commit.message, '(pm)')) || (github.event_name == 'pull_request' && contains(github.event.pull_request.title, '(pm)')) strategy: diff --git a/Cargo.lock b/Cargo.lock index bde2b819de..47d1331683 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" @@ -2123,33 +2113,13 @@ dependencies = [ "crypto-common", ] -[[package]] -name = "dirs" -version = "4.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca3aa72a6f96ea37bbc5aa912f6788242832f75369bdfdadcb0e38423f100059" -dependencies = [ - "dirs-sys 0.3.7", -] - [[package]] name = "dirs" version = "5.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225" dependencies = [ - "dirs-sys 0.4.1", -] - -[[package]] -name = "dirs-sys" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b1d1d91c932ef41c0f2663aa8b0ca0342d444d842c06914aa0a7e352d0bada6" -dependencies = [ - "libc", - "redox_users", - "winapi", + "dirs-sys", ] [[package]] @@ -2277,18 +2247,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" @@ -2374,16 +2332,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" @@ -2447,7 +2395,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bfe33edd8e85a12a67454e37f8c75e730830d83e313556ab9ebf9ee7fbeb3bfb" dependencies = [ "crc32fast", - "libz-rs-sys", "miniz_oxide", ] @@ -2623,876 +2570,125 @@ dependencies = [ [[package]] name = "futures-sink" version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" - -[[package]] -name = "futures-task" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" - -[[package]] -name = "futures-util" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" -dependencies = [ - "futures-channel", - "futures-core", - "futures-io", - "futures-macro", - "futures-sink", - "futures-task", - "memchr", - "pin-project-lite", - "pin-utils", - "slab", -] - -[[package]] -name = "fuzzy-matcher" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54614a3312934d066701a80f20f15fa3b56d67ac7722b39eea5b4c9dd1d66c94" -dependencies = [ - "thread_local", -] - -[[package]] -name = "generic-array" -version = "0.14.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" -dependencies = [ - "typenum", - "version_check", -] - -[[package]] -name = "getrandom" -version = "0.2.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" -dependencies = [ - "cfg-if", - "js-sys", - "libc", - "wasi 0.11.1+wasi-snapshot-preview1", - "wasm-bindgen", -] - -[[package]] -name = "getrandom" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" -dependencies = [ - "cfg-if", - "js-sys", - "libc", - "r-efi 5.3.0", - "wasi 0.14.7+wasi-0.2.4", - "wasm-bindgen", -] - -[[package]] -name = "getrandom" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555" -dependencies = [ - "cfg-if", - "js-sys", - "libc", - "r-efi 6.0.0", - "rand_core 0.10.0", - "wasip2", - "wasip3", - "wasm-bindgen", -] - -[[package]] -name = "gif" -version = "0.13.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ae047235e33e2829703574b54fdec96bfbad892062d97fed2f76022287de61b" -dependencies = [ - "color_quant", - "weezl", -] - -[[package]] -name = "gimli" -version = "0.28.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" -dependencies = [ - "fallible-iterator", - "indexmap 2.13.0", - "stable_deref_trait", -] - -[[package]] -name = "gimli" -version = "0.32.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e629b9b98ef3dd8afe6ca2bd0f89306cec16d43d907889945bc5d6687f2f13c7" -dependencies = [ - "fallible-iterator", - "indexmap 2.13.0", - "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", -] +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" [[package]] -name = "gix-sec" -version = "0.11.0" +name = "futures-task" +version = "0.3.31" 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", -] +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" [[package]] -name = "gix-shallow" -version = "0.4.0" +name = "futures-util" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b9a6f6e34d6ede08f522d89e5c7990b4f60524b8ae6ebf8e850963828119ad4" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" dependencies = [ - "bstr", - "gix-hash", - "gix-lock", - "thiserror 2.0.17", + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", ] [[package]] -name = "gix-submodule" -version = "0.19.1" +name = "fuzzy-matcher" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f51472f05a450cc61bc91ed2f62fb06e31e2bbb31c420bc4be8793f26c8b0c1" +checksum = "54614a3312934d066701a80f20f15fa3b56d67ac7722b39eea5b4c9dd1d66c94" dependencies = [ - "bstr", - "gix-config", - "gix-path", - "gix-pathspec", - "gix-refspec", - "gix-url", - "thiserror 2.0.17", + "thread_local", ] [[package]] -name = "gix-tempfile" -version = "17.1.0" +name = "generic-array" +version = "0.14.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c750e8c008453a2dba67a2b0d928b7716e05da31173a3f5e351d5457ad4470aa" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ - "gix-fs", - "libc", - "once_cell", - "parking_lot", - "tempfile", + "typenum", + "version_check", ] [[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" +name = "getrandom" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edfe22ba26d4b65c17879f12b9882eafe65d3c8611c933b272fce2c10f546f59" +checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" 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", + "cfg-if", + "js-sys", + "libc", + "wasi 0.11.1+wasi-snapshot-preview1", + "wasm-bindgen", ] [[package]] -name = "gix-traverse" -version = "0.46.2" +name = "getrandom" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8648172f85aca3d6e919c06504b7ac26baef54e04c55eb0100fa588c102cc33" +checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" dependencies = [ - "bitflags 2.9.4", - "gix-commitgraph", - "gix-date", - "gix-hash", - "gix-hashtable", - "gix-object", - "gix-revwalk", - "smallvec", - "thiserror 2.0.17", + "cfg-if", + "js-sys", + "libc", + "r-efi 5.3.0", + "wasi 0.14.7+wasi-0.2.4", + "wasm-bindgen", ] [[package]] -name = "gix-url" -version = "0.31.0" +name = "getrandom" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42a1ad0b04a5718b5cb233e6888e52a9b627846296161d81dcc5eb9203ec84b8" +checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555" dependencies = [ - "bstr", - "gix-features", - "gix-path", - "percent-encoding", - "thiserror 2.0.17", - "url", + "cfg-if", + "js-sys", + "libc", + "r-efi 6.0.0", + "rand_core 0.10.0", + "wasip2", + "wasip3", + "wasm-bindgen", ] [[package]] -name = "gix-utils" -version = "0.3.1" +name = "gif" +version = "0.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "befcdbdfb1238d2854591f760a48711bed85e72d80a10e8f2f93f656746ef7c5" +checksum = "4ae047235e33e2829703574b54fdec96bfbad892062d97fed2f76022287de61b" dependencies = [ - "fastrand", - "unicode-normalization", + "color_quant", + "weezl", ] [[package]] -name = "gix-validate" -version = "0.10.1" +name = "gimli" +version = "0.28.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b1e63a5b516e970a594f870ed4571a8fdcb8a344e7bd407a20db8bd61dbfde4" +checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" dependencies = [ - "bstr", - "thiserror 2.0.17", + "fallible-iterator", + "indexmap 2.13.0", + "stable_deref_trait", ] [[package]] -name = "gix-worktree" -version = "0.41.0" +name = "gimli" +version = "0.32.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54f1916f8d928268300c977d773dd70a8746b646873b77add0a34876a8c847e9" +checksum = "e629b9b98ef3dd8afe6ca2bd0f89306cec16d43d907889945bc5d6687f2f13c7" dependencies = [ - "bstr", - "gix-attributes", - "gix-features", - "gix-fs", - "gix-glob", - "gix-hash", - "gix-ignore", - "gix-index", - "gix-object", - "gix-path", - "gix-validate", + "fallible-iterator", + "indexmap 2.13.0", + "stable_deref_trait", ] [[package]] @@ -3727,52 +2923,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" @@ -3945,7 +3095,6 @@ dependencies = [ "tokio", "tokio-rustls", "tower-service", - "webpki-roots", ] [[package]] @@ -4315,18 +3464,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" @@ -4543,15 +3680,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" @@ -4719,15 +3847,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" @@ -4924,17 +4043,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" @@ -5262,24 +4370,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 +4696,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" @@ -6455,16 +5541,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" @@ -7030,11 +6106,9 @@ dependencies = [ "base64 0.22.1", "bytes", "encoding_rs", - "futures-channel", "futures-core", "futures-util", "h2 0.4.12", - "hickory-resolver", "http 1.3.1", "http-body 1.0.1", "http-body-util", @@ -7046,7 +6120,6 @@ dependencies = [ "log", "mime", "native-tls", - "once_cell", "percent-encoding", "pin-project-lite", "quinn", @@ -7067,9 +6140,7 @@ dependencies = [ "url", "wasm-bindgen", "wasm-bindgen-futures", - "wasm-streams", "web-sys", - "webpki-roots", ] [[package]] @@ -7110,12 +6181,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 +6347,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cd3c25631629d034ce7cd9940adc9d45762d46de2b0f57193c4443b92c6d4d40" dependencies = [ "aws-lc-rs", - "log", "once_cell", "ring", "rustls-pki-types", @@ -7732,16 +6796,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" @@ -9656,12 +8710,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" @@ -11210,12 +10258,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" @@ -11351,7 +10393,7 @@ dependencies = [ "dashmap 6.1.0", "deno_semver", "dialoguer", - "dirs 4.0.0", + "dirs", "flate2", "futures", "glob", @@ -11380,7 +10422,6 @@ dependencies = [ "tokio", "tokio-fs-ext", "tokio-retry", - "tokio-util", "toml", "tracing", "tracing-appender", @@ -11397,9 +10438,8 @@ dependencies = [ "bytes", "dashmap 6.1.0", "deno_semver", - "dirs 5.0.1", + "dirs", "futures", - "gix", "js-sys", "libc", "libdeflater", @@ -11408,8 +10448,6 @@ dependencies = [ "petgraph 0.6.5", "rayon", "reqwest 0.12.24", - "rustls", - "rustls-native-certs", "serde", "serde_json", "sha2", @@ -11471,7 +10509,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", ] @@ -11894,19 +10931,6 @@ dependencies = [ "wasmparser 0.244.0", ] -[[package]] -name = "wasm-streams" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15053d8d85c7eccdbefef60f06769760a563c7f0a9d6902a13d35c7800b0ad65" -dependencies = [ - "futures-util", - "js-sys", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", -] - [[package]] name = "wasm_thread" version = "0.3.3" @@ -12594,27 +11618,12 @@ dependencies = [ "rustls-pki-types", ] -[[package]] -name = "webpki-roots" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32b130c0d2d49f8b6889abc456e795e82525204f27c42cf767cf0d7734e089b8" -dependencies = [ - "rustls-pki-types", -] - [[package]] name = "weezl" 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 +12116,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" @@ -13374,12 +12373,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/pm/Cargo.toml b/crates/pm/Cargo.toml index c83fb3d019..3e65417f51 100644 --- a/crates/pm/Cargo.toml +++ b/crates/pm/Cargo.toml @@ -28,7 +28,7 @@ colored = "2.1" dashmap = "6.1.0" deno_semver = "0.7" dialoguer = { version = "0.11", features = ["fuzzy-select"] } -dirs = "4.0" +dirs = { workspace = true } flate2 = "1.0" futures = { workspace = true } glob = "0.3.1" @@ -45,12 +45,9 @@ rayon = { workspace = true } regex = "1.10" reqwest = { version = "0.12", default-features = false, features = [ "gzip", - "hickory-dns", - "http2", "json", - "rustls-tls", + "rustls-tls-native-roots", "socks", - "stream", ] } serde = { workspace = true } serde_json = { version = "1.0", features = ["preserve_order"] } @@ -63,7 +60,6 @@ tempfile = "3.15.0" term_size = "0.3" tokio-fs-ext = { workspace = true } tokio-retry = "0.3" -tokio-util = { version = "0.7" } toml = { workspace = true } tracing = { workspace = true } tracing-appender = "0.2" @@ -72,6 +68,16 @@ utoo-ruborist = { path = "../ruborist", version = "0.1.0", features = ["htt [dev-dependencies] mockito = "1.7.0" +tokio = { workspace = true, features = ["macros"] } [target.'cfg(not(target_arch = "wasm32"))'.dependencies] -tokio = { workspace = true, features = ["full"] } +tokio = { workspace = true, features = [ + "fs", + "io-util", + "macros", + "net", + "process", + "rt-multi-thread", + "sync", + "time", +] } diff --git a/crates/ruborist/Cargo.toml b/crates/ruborist/Cargo.toml index cee6b29dd1..9562d93258 100644 --- a/crates/ruborist/Cargo.toml +++ b/crates/ruborist/Cargo.toml @@ -28,17 +28,13 @@ 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 roots are selected per native target below. HTTP clients force HTTP/1.1, +# so keep reqwest's HTTP/2 stack out of the binary. +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 } -# Git clone (gated on `native-git`) -gix = { workspace = true, features = [ - "blocking-http-transport-reqwest-rust-tls", - "blocking-network-client", -], optional = true } +# Git clone support (gated on `native-git`) uses the system `git` executable. # 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. @@ -52,37 +48,24 @@ workspace = true [dev-dependencies] tokio = { workspace = true, features = ["macros", "rt"] } -# Native (non-macOS) targets: reqwest's default rustls + ring. +# Native targets: reqwest's default rustls provider (ring) + OS root store. # `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" +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 } +reqwest = { version = "0.12", default-features = false, features = ["gzip", "rustls-tls-native-roots"] } # 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_/ @@ -90,4 +73,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..198ed4b004 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 native 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..9523c1a28b 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` executable. //! //! 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. +//! Remote transport is delegated to `git` instead of linking gitoxide's +//! reqwest/rustls transport into the package-manager binary. The blocking +//! clone and filesystem copy are run 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::path::{Component, Path, PathBuf}; +use std::process::Command; use std::sync::Arc; use anyhow::{Context, Result, anyhow}; @@ -62,32 +64,59 @@ 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`]. -/// -/// 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); +fn redact_secret(text: &str, token: Option<&str>) -> String { + match token { + Some(token) if !token.is_empty() => text.replace(token, ""), + _ => text.to_string(), + } +} + +fn run_git( + args: &[&str], + cwd: Option<&Path>, + op: &str, + public_url: &str, + token: Option<&str>, +) -> Result { + let mut command = Command::new("git"); + command.env("GIT_TERMINAL_PROMPT", "0"); + if let Some(cwd) = cwd { + command.current_dir(cwd); + } + command.args(args); + + let output = command + .output() + .with_context(|| format!("failed to execute git while running {op}"))?; + + if !output.status.success() { + let stderr = String::from_utf8_lossy(&output.stderr); + let stderr = redact_secret(&stderr, token); + let mut msg = format!("{op} failed for '{public_url}'"); + if let Some(code) = output.status.code() { + msg.push_str(&format!(" (exit code {code})")); + } + if !stderr.trim().is_empty() { + msg.push_str(": "); + msg.push_str(stderr.trim()); } + if token.is_none() + && (public_url.contains("github.com") || public_url.contains("gitlab.com")) + { + msg.push_str( + "\n\nTip: set GITHUB_TOKEN or GH_TOKEN for private repos / higher rate limits", + ); + } + return Err(anyhow!(msg)); } - 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()) +} + +fn read_pkg_manifest(checkout_dir: &Path) -> Result { + let pkg_bytes = std::fs::read(checkout_dir.join("package.json")) + .context("package.json not found in git repo root (does the repo have a package.json?)")?; + serde_json::from_slice(&pkg_bytes).context("Failed to parse package.json from git checkout") } /// Build a [`GitCloneResult`] from a cached package directory on disk. @@ -114,86 +143,107 @@ fn read_cached_git_result( )) } -/// Recursively extract a git tree to a directory on disk. +fn normalize_lexically(path: &Path) -> PathBuf { + let mut normalized = PathBuf::new(); + for component in path.components() { + match component { + Component::CurDir => {} + Component::ParentDir => { + normalized.pop(); + } + Component::Prefix(_) | Component::RootDir | Component::Normal(_) => { + normalized.push(component.as_os_str()); + } + } + } + normalized +} + +fn symlink_target_stays_in_root(root_dest: &Path, link_path: &Path, target: &Path) -> bool { + let link_parent = link_path.parent().unwrap_or(root_dest); + let resolved = if target.is_absolute() { + target.to_path_buf() + } else { + link_parent.join(target) + }; + normalize_lexically(&resolved).starts_with(normalize_lexically(root_dest)) +} + +/// Recursively copy a checked-out git worktree to a directory on disk. /// /// `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() { +fn copy_worktree_to_dir(src: &Path, dest: &Path, root_dest: &Path) -> Result<()> { + std::fs::create_dir_all(dest)?; + + for entry in + std::fs::read_dir(src).with_context(|| format!("failed to read {}", src.display()))? + { 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)?; - } - 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() - ); - } - } - } - 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; - } + let file_name = entry.file_name(); + if file_name == ".git" { + 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; - } + let src_path = entry.path(); + let dest_path = dest.join(&file_name); + let metadata = std::fs::symlink_metadata(&src_path)?; + let file_type = metadata.file_type(); + + if file_type.is_dir() { + copy_worktree_to_dir(&src_path, &dest_path, root_dest)?; + } else if file_type.is_symlink() { + let target = std::fs::read_link(&src_path) + .with_context(|| format!("failed to read symlink {}", src_path.display()))?; + if !symlink_target_stays_in_root(root_dest, &dest_path, &target) { + tracing::debug!( + "Skipping symlink {} -> {} (escapes extraction dir)", + dest_path.display(), + target.display() + ); + continue; } - gix::object::tree::EntryKind::Commit => { - // Submodule reference – skip silently + + #[cfg(unix)] + std::os::unix::fs::symlink(&target, &dest_path) + .with_context(|| format!("failed to create symlink {}", dest_path.display()))?; + #[cfg(not(unix))] + { + let _ = target; tracing::debug!( - "Skipping submodule entry at {:?}", - std::str::from_utf8(entry.filename()).unwrap_or("") + "Skipping symlink {} (symlink cache extraction is unsupported on this platform)", + dest_path.display() ); } + } else if file_type.is_file() { + std::fs::copy(&src_path, &dest_path).with_context(|| { + format!( + "failed to copy {} to {}", + src_path.display(), + dest_path.display() + ) + })?; + + #[cfg(unix)] + { + let perms = std::fs::Permissions::from_mode(metadata.permissions().mode() & 0o777); + std::fs::set_permissions(&dest_path, perms).with_context(|| { + format!("failed to set permissions on {}", dest_path.display()) + })?; + } } } + Ok(()) } -/// Blocking core that does the actual clone + extraction via gix. +fn is_full_sha(commit_ish: Option<&str>) -> bool { + commit_ish.is_some_and(|c| { + (c.len() == 40 || c.len() == 64) && c.chars().all(|ch| ch.is_ascii_hexdigit()) + }) +} + +/// Blocking core that does the actual clone + extraction via system git. fn clone_repo_blocking( cache_dir: &Path, clone_url: &str, @@ -205,14 +255,10 @@ 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. - let is_full_sha = - commit_ish.is_some_and(|c| c.len() == 40 && c.chars().all(|ch| ch.is_ascii_hexdigit())); + let is_full_sha = is_full_sha(commit_ish); - // 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. The package name is known at 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); @@ -229,84 +275,58 @@ fn clone_repo_blocking( } Err(e) => { tracing::debug!("Git cache read failed, will re-fetch: {e}"); - // Fall through to network fetch } } } } - // Capture auth token once; reused for URL injection and the error hint below. + // Capture auth token once; reused for URL injection and error redaction. let token = github_auth_token(); let effective_url = match &token { Some(t) => try_inject_token_into_https_url(clone_url, t), None => clone_url.to_string(), }; - // Clone to a temporary directory (bare) let temp_dir = tempfile::tempdir().context("Failed to create temp directory for git clone")?; + let checkout_dir = temp_dir.path().join("repo"); + let checkout_str = checkout_dir + .to_str() + .ok_or_else(|| anyhow!("non-UTF-8 temporary checkout path"))?; - 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, - )); + let mut clone_args = vec!["clone", "--quiet"]; + if is_full_sha { + clone_args.push("--no-checkout"); + } else { + clone_args.extend(["--depth", "1"]); + if let Some(ref_name) = commit_ish { + clone_args.extend(["--branch", ref_name]); + } } + clone_args.extend(["--", effective_url.as_str(), checkout_str]); - // 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 + run_git(&clone_args, None, "git clone", clone_url, token.as_deref())?; + + if let Some(sha) = commit_ish + && is_full_sha { - prepare = prepare.with_ref_name(Some(ref_name))?; + run_git( + &["checkout", "--quiet", sha], + Some(&checkout_dir), + "git checkout", + clone_url, + token.as_deref(), + )?; } - 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))? - } - _ => checkout - .head_id() - .map_err(|e| anyhow!("could not resolve HEAD after fetch: {e}"))? - .detach(), - }; + let commit_hex = run_git( + &["rev-parse", "HEAD"], + Some(&checkout_dir), + "git rev-parse", + clone_url, + token.as_deref(), + )?; - 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}"))?; - - let tree_id = commit.tree_id()?; - - // 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())?; - - // 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 mut manifest = read_pkg_manifest(&checkout_dir)?; let pinned_url = format!("{}#{}", resolved_url, commit_hex); finalize_non_registry_manifest(&mut manifest, pinned_url.clone())?; @@ -316,7 +336,7 @@ fn clone_repo_blocking( } commit_cache_dir_atomic(&package_dir, |stage| { - extract_tree_to_dir(&checkout, tree_id.into(), stage, stage) + copy_worktree_to_dir(&checkout_dir, stage, stage) })?; Ok(GitCloneResult::new(package_dir, pinned_url, manifest)) @@ -375,7 +395,7 @@ pub async fn ensure_repo_cached( /// Resolve a git/github dep spec to a [`ResolvedPackage`]. /// /// Accepts an already-parsed [`PackageSpec`] so the call site's type-level -/// guarantee survives — re-parsing from a raw string would discard it. +/// guarantee survives; re-parsing from a raw string would discard it. pub(crate) async fn resolve_git_dep( cache_dir: Option<&Path>, spec: &PackageSpec, @@ -414,8 +434,20 @@ pub(crate) async fn resolve_git_dep( #[cfg(test)] mod tests { + use std::process::Command; + use super::*; + fn run_git_test(repo: &Path, args: &[&str]) { + let status = Command::new("git") + .current_dir(repo) + .env("GIT_TERMINAL_PROMPT", "0") + .args(args) + .status() + .expect("git should be executable for git resolver tests"); + assert!(status.success(), "git {args:?} failed with {status}"); + } + #[test] fn test_inject_token_into_url() { assert_eq!( @@ -459,4 +491,71 @@ mod tests { "git+ and bare URLs should produce the same dedup key" ); } + + #[test] + fn symlink_escape_check_rejects_parent_escape() { + let root = Path::new("/tmp/cache/pkg"); + let link = root.join("nested/link"); + assert!(!symlink_target_stays_in_root( + root, + &link, + Path::new("../../outside") + )); + assert!(symlink_target_stays_in_root( + root, + &link, + Path::new("../target") + )); + } + + #[test] + fn full_sha_detection_accepts_sha1_and_sha256() { + assert!(is_full_sha(Some( + "0123456789abcdef0123456789abcdef01234567" + ))); + assert!(is_full_sha(Some( + "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" + ))); + assert!(!is_full_sha(Some("main"))); + assert!(!is_full_sha(Some( + "0123456789abcdef0123456789abcdef0123456g" + ))); + } + + #[tokio::test] + async fn ensure_repo_cached_clones_local_repo() { + let repo = tempfile::tempdir().unwrap(); + run_git_test(repo.path(), &["init", "--quiet"]); + run_git_test(repo.path(), &["config", "user.email", "utoo@example.test"]); + run_git_test(repo.path(), &["config", "user.name", "Utoo Test"]); + + std::fs::write( + repo.path().join("package.json"), + r#"{"name":"demo-git-package","version":"1.2.3"}"#, + ) + .unwrap(); + std::fs::write(repo.path().join("index.js"), "module.exports = 1;\n").unwrap(); + run_git_test(repo.path(), &["add", "."]); + run_git_test(repo.path(), &["commit", "--quiet", "-m", "init"]); + + let cache_dir = tempfile::tempdir().unwrap(); + let clone_cache = GitCloneCache::new(); + let repo_url = repo.path().to_str().unwrap(); + let result = ensure_repo_cached( + cache_dir.path(), + repo_url, + None, + "demo-git-package", + &clone_cache, + ) + .await + .unwrap(); + + assert_eq!(result.name, "demo-git-package"); + assert_eq!(result.version, "1.2.3"); + assert!(result.path.join("_resolved").exists()); + assert!(result.path.join("package.json").exists()); + assert!(result.path.join("index.js").exists()); + assert!(result.resolved_url.starts_with(repo_url)); + } } diff --git a/crates/ruborist/src/service/http.rs b/crates/ruborist/src/service/http.rs index 24a6b311f1..33b4e24824 100644 --- a/crates/ruborist/src/service/http.rs +++ b/crates/ruborist/src/service/http.rs @@ -136,52 +136,15 @@ 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. /// /// This is the shared base for all HTTP clients. Callers can further customize /// the builder (e.g. add `user_agent`, `http1_only`, timeouts) before building. /// -/// On native targets: uses rustls TLS, caching DNS resolver, and reads proxy -/// from `ALL_PROXY` > `HTTPS_PROXY` / `HTTP_PROXY` (and their lowercase variants). +/// On native targets: uses reqwest's rustls TLS, caching DNS resolver, and +/// reads proxy from `ALL_PROXY` > `HTTPS_PROXY` / `HTTP_PROXY` (and their +/// lowercase variants). /// /// On WASM targets: returns a minimal builder (browser handles TLS, DNS, proxy). /// @@ -202,11 +165,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(