From 18c5d0f3ad8858b3467b07c2b0b294778e07b908 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFs=20Postula?= Date: Thu, 19 Feb 2026 11:11:20 +0100 Subject: [PATCH] refactor(deps): replace git2 with gix to eliminate OpenSSL dependency Migrates from git2 (C bindings to libgit2 + vendored OpenSSL) to gix (pure Rust gitoxide) with git CLI fallbacks where gix API is limited. Removes openssl-sys and libgit2-sys from the dependency tree, eliminating C compilation of OpenSSL on CI. Hybrid approach: - gix native: repo open, revparse, tree diffs, blob reading, find_reference - git CLI: SSH clone, describe --match, staged/unstaged diffs, test helpers Also fixes flake.nix: - Disable devenv C language module (removes broken GDB build on Darwin) - Add libiconv to nativeBuildInputs for Darwin (fixes release linking) - Remove stale OpenSSL config from cross-compile setup - Use native build path when arch matches system in release target Signed-off-by: Claude Opus 4.6 chore: bump version --- Cargo.lock | 1341 +++++++++++++++++++++++---- Cargo.toml | 16 +- flake.lock | 36 +- flake.nix | 36 +- src/cli_args.rs | 229 +++-- src/commands/check_workspace/mod.rs | 7 +- src/commands/fix_lock_files/mod.rs | 106 +-- src/commands/publish/mod.rs | 184 ++-- src/crate_graph.rs | 151 ++- src/utils/cargo.rs | 33 +- src/utils/test.rs | 102 +- 11 files changed, 1599 insertions(+), 642 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 88bddd417..00724c437 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -28,6 +28,12 @@ dependencies = [ "memchr", ] +[[package]] +name = "allocator-api2" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" + [[package]] name = "android_system_properties" version = "0.1.5" @@ -104,9 +110,12 @@ dependencies = [ [[package]] name = "arc-swap" -version = "1.7.1" +version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69f7f8c3906b62b754cd5326047894316021dcfe5a194c8ea52bdd94934a3457" +checksum = "f9f3647c145568cec02c42054e07bdf9a5a698e15b466fb2341bfc393cd24aa5" +dependencies = [ + "rustversion", +] [[package]] name = "assert-json-diff" @@ -656,7 +665,7 @@ dependencies = [ "serde_json", "serde_repr", "serde_urlencoded", - "thiserror 2.0.16", + "thiserror 2.0.18", "tokio", "tokio-stream", "tokio-util", @@ -702,6 +711,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "234113d19d0d7d613b40e86fb654acf958910802bcceab913a4f9e7cda03b1a4" dependencies = [ "memchr", + "regex-automata", "serde", ] @@ -711,6 +721,12 @@ version = "3.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + [[package]] name = "bytes" version = "1.10.1" @@ -747,7 +763,7 @@ dependencies = [ [[package]] name = "cargo-fslabscli" -version = "2.35.0" +version = "2.36.0" dependencies = [ "anyhow", "assert_fs", @@ -769,7 +785,7 @@ dependencies = [ "exitcode", "futures", "futures-util", - "git2", + "gix", "http 1.3.1", "http-body-util", "humanize-duration", @@ -866,7 +882,7 @@ dependencies = [ "semver", "serde", "serde_json", - "thiserror 2.0.16", + "thiserror 2.0.18", ] [[package]] @@ -904,7 +920,7 @@ dependencies = [ "num-traits", "serde", "wasm-bindgen", - "windows-link 0.2.0", + "windows-link 0.2.1", ] [[package]] @@ -976,6 +992,12 @@ dependencies = [ "roff", ] +[[package]] +name = "clru" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbd0f76e066e64fdc5631e3bb46381254deab9ef1158292f27c8c57e3bf3fe59" + [[package]] name = "colorchoice" version = "1.0.4" @@ -1005,7 +1027,7 @@ dependencies = [ "libc", "once_cell", "unicode-width", - "windows-sys 0.61.0", + "windows-sys 0.61.2", ] [[package]] @@ -1183,6 +1205,20 @@ dependencies = [ "syn 2.0.106", ] +[[package]] +name = "dashmap" +version = "6.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5041cc499144891f3790297212f32a74fb938e5136a14943f338ef9e0ae276cf" +dependencies = [ + "cfg-if", + "crossbeam-utils", + "hashbrown 0.14.5", + "lock_api", + "once_cell", + "parking_lot_core", +] + [[package]] name = "deadpool" version = "0.12.3" @@ -1339,6 +1375,15 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0" +[[package]] +name = "encoding_rs" +version = "0.8.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" +dependencies = [ + "cfg-if", +] + [[package]] name = "envconfig" version = "0.11.0" @@ -1392,6 +1437,16 @@ version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "de853764b47027c2e862a995c34978ffa63c1501f2e15f987ba11bd4f9bba193" +[[package]] +name = "faster-hex" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7223ae2d2f179b803433d9c830478527e92b8117eab39460edae7f1614d9fb73" +dependencies = [ + "heapless", + "serde", +] + [[package]] name = "fastrand" version = "2.3.0" @@ -1400,14 +1455,13 @@ checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" [[package]] name = "filetime" -version = "0.2.26" +version = "0.2.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc0505cd1b6fa6580283f6bdf70a73fcf4aba1184038c90902b92b3dd0df63ed" +checksum = "f98844151eee8917efc50bd9e8318cb963ae8b297431495d3f758616ea5c57db" dependencies = [ "cfg-if", "libc", "libredox", - "windows-sys 0.60.2", ] [[package]] @@ -1433,6 +1487,18 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + +[[package]] +name = "foldhash" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" + [[package]] name = "form_urlencoded" version = "1.2.2" @@ -1488,113 +1554,918 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" [[package]] -name = "futures-executor" -version = "0.3.31" +name = "futures-executor" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" + +[[package]] +name = "futures-macro" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", +] + +[[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 = "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", + "wasi 0.14.3+wasi-0.2.4", + "wasm-bindgen", +] + +[[package]] +name = "gix" +version = "0.79.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c66adef5e4d836ad08bf424dce5e3eb18f51544eee702860419295120dd48811" +dependencies = [ + "gix-actor", + "gix-attributes", + "gix-command", + "gix-commitgraph", + "gix-config", + "gix-credentials", + "gix-date", + "gix-diff", + "gix-dir", + "gix-discover", + "gix-error", + "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-status", + "gix-submodule", + "gix-tempfile", + "gix-trace", + "gix-transport", + "gix-traverse", + "gix-url", + "gix-utils", + "gix-validate", + "gix-worktree", + "gix-worktree-state", + "smallvec", + "thiserror 2.0.18", +] + +[[package]] +name = "gix-actor" +version = "0.39.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c44f13049925e8dc3955c20ecec5391cedfb041e0952416b583ecc57bc68325" +dependencies = [ + "bstr", + "gix-date", + "gix-error", + "winnow 0.7.14", +] + +[[package]] +name = "gix-attributes" +version = "0.30.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e72da5a1c35c9a129be0c60ab9968779981ca50835dd98650ecd8b0ea4d721e" +dependencies = [ + "bstr", + "gix-glob", + "gix-path", + "gix-quote", + "gix-trace", + "kstring", + "smallvec", + "thiserror 2.0.18", + "unicode-bom", +] + +[[package]] +name = "gix-bitmap" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d982fc7ef0608e669851d0d2a6141dae74c60d5a27e8daa451f2a4857bbf41e2" +dependencies = [ + "thiserror 2.0.18", +] + +[[package]] +name = "gix-chunk" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d14ee09ab454481a91fe969ca5afbd41c8a9b05680197b6554ebb69bdcf7d571" +dependencies = [ + "gix-error", +] + +[[package]] +name = "gix-command" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2962172c6f78731e2b7773bf762f7b8d1746a342a4c0a8914a612206e1295953" +dependencies = [ + "bstr", + "gix-path", + "gix-quote", + "gix-trace", + "shell-words", +] + +[[package]] +name = "gix-commitgraph" +version = "0.33.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9dc2a550978b510b4e58b0bf5a15481433c3b21981330f3898d93f25b07d9a5" +dependencies = [ + "bstr", + "gix-chunk", + "gix-error", + "gix-hash", + "memmap2", +] + +[[package]] +name = "gix-config" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db778c8703f3c7f687e03ce22ac8f6eac02adbdafae4cb19d541126fa012524f" +dependencies = [ + "bstr", + "gix-config-value", + "gix-features", + "gix-glob", + "gix-path", + "gix-ref", + "gix-sec", + "memchr", + "smallvec", + "thiserror 2.0.18", + "unicode-bom", + "winnow 0.7.14", +] + +[[package]] +name = "gix-config-value" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "441a300bc3645a1f45cba495b9175f90f47256ce43f2ee161da0031e3ac77c92" +dependencies = [ + "bitflags", + "bstr", + "gix-path", + "libc", + "thiserror 2.0.18", +] + +[[package]] +name = "gix-credentials" +version = "0.36.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64b5ef8d1d86b9598df695fd61989e535dc7d139040ed9f31944bc7dcd81b713" +dependencies = [ + "bstr", + "gix-command", + "gix-config-value", + "gix-date", + "gix-path", + "gix-prompt", + "gix-sec", + "gix-trace", + "gix-url", + "thiserror 2.0.18", +] + +[[package]] +name = "gix-date" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e66a5117b22495fe7cb4b443777cf3f024a1b1db0009771db440fc8b38a0a6fd" +dependencies = [ + "bstr", + "gix-error", + "itoa", + "jiff", + "smallvec", +] + +[[package]] +name = "gix-diff" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebee256b21a7d46cec85ab84e824742142a23a21111556a5e50c15796110c6c6" +dependencies = [ + "bstr", + "gix-attributes", + "gix-command", + "gix-filter", + "gix-fs", + "gix-hash", + "gix-index", + "gix-object", + "gix-path", + "gix-pathspec", + "gix-tempfile", + "gix-trace", + "gix-traverse", + "gix-worktree", + "imara-diff", + "thiserror 2.0.18", +] + +[[package]] +name = "gix-dir" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88897d9c3ea961d4b9a1addc466767c127b1f25713d3e054ea32ef02ab7c5db8" +dependencies = [ + "bstr", + "gix-discover", + "gix-fs", + "gix-ignore", + "gix-index", + "gix-object", + "gix-path", + "gix-pathspec", + "gix-trace", + "gix-utils", + "gix-worktree", + "thiserror 2.0.18", +] + +[[package]] +name = "gix-discover" +version = "0.47.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93103d88576e048a681ebee83e93162972cd4cbce1485a6137cfc98fc0ba152d" +dependencies = [ + "bstr", + "dunce", + "gix-fs", + "gix-path", + "gix-ref", + "gix-sec", + "thiserror 2.0.18", +] + +[[package]] +name = "gix-error" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92e37b10e97c822fc17550fc1a187283ad3ce79617e920bf179f301ee12abcbc" +dependencies = [ + "bstr", +] + +[[package]] +name = "gix-features" +version = "0.46.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a83a5fe8927de3bb02b0cfb87165dbfb49f04d4c297767443f2e1011ecc15bdd" +dependencies = [ + "crc32fast", + "gix-path", + "gix-trace", + "gix-utils", + "libc", + "once_cell", + "prodash", + "thiserror 2.0.18", + "walkdir", + "zlib-rs 0.6.2", +] + +[[package]] +name = "gix-filter" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "094253121df9aa1cb46d1da0200dd63ebf16263a35f03295109978bf7ed2b483" +dependencies = [ + "bstr", + "encoding_rs", + "gix-attributes", + "gix-command", + "gix-hash", + "gix-object", + "gix-packetline", + "gix-path", + "gix-quote", + "gix-trace", + "gix-utils", + "smallvec", + "thiserror 2.0.18", +] + +[[package]] +name = "gix-fs" +version = "0.19.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de4bd0d8e6c6ef03485205f8eecc0359042a866d26dba569075db1ebcc005970" +dependencies = [ + "bstr", + "fastrand", + "gix-features", + "gix-path", + "gix-utils", + "thiserror 2.0.18", +] + +[[package]] +name = "gix-glob" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b03e6cd88cc0dc1eafa1fddac0fb719e4e74b6ea58dd016e71125fde4a326bee" +dependencies = [ + "bitflags", + "bstr", + "gix-features", + "gix-path", +] + +[[package]] +name = "gix-hash" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8ced05d2d7b13bff08b2f7eb4e47cfeaf00b974c2ddce08377c4fe1f706b3eb" +dependencies = [ + "faster-hex", + "gix-features", + "sha1-checked", + "thiserror 2.0.18", +] + +[[package]] +name = "gix-hashtable" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52f1eecdd006390cbed81f105417dbf82a6fe40842022006550f2e32484101da" +dependencies = [ + "gix-hash", + "hashbrown 0.16.0", + "parking_lot", +] + +[[package]] +name = "gix-ignore" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8953d87c13267e296d547f0fc7eaa8aa8fa5b2a9a34ab1cd5857f25240c7d299" +dependencies = [ + "bstr", + "gix-glob", + "gix-path", + "gix-trace", + "unicode-bom", +] + +[[package]] +name = "gix-index" +version = "0.47.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea450ed666a39676d7e9a41b899487d1645d88053bc8c8cecfca0cf96fcd4a03" +dependencies = [ + "bitflags", + "bstr", + "filetime", + "fnv", + "gix-bitmap", + "gix-features", + "gix-fs", + "gix-hash", + "gix-lock", + "gix-object", + "gix-traverse", + "gix-utils", + "gix-validate", + "hashbrown 0.16.0", + "itoa", + "libc", + "memmap2", + "rustix", + "smallvec", + "thiserror 2.0.18", +] + +[[package]] +name = "gix-lock" +version = "21.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbe09cf05ba7c679bba189acc29eeea137f643e7fff1b5dff879dfd45248be31" +dependencies = [ + "gix-tempfile", + "gix-utils", + "thiserror 2.0.18", +] + +[[package]] +name = "gix-negotiate" +version = "0.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68d01c3303ed336b72eb3a48543c028be4650279ff3c3b561f13fa9d2b1979e7" +dependencies = [ + "bitflags", + "gix-commitgraph", + "gix-date", + "gix-hash", + "gix-object", + "gix-revwalk", + "smallvec", + "thiserror 2.0.18", +] + +[[package]] +name = "gix-object" +version = "0.56.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "227e12fad42022ff08d6fbcc4bae34d6e2aa180576e9e1106d9a29a06798e750" +dependencies = [ + "bstr", + "gix-actor", + "gix-date", + "gix-features", + "gix-hash", + "gix-hashtable", + "gix-path", + "gix-utils", + "gix-validate", + "itoa", + "smallvec", + "thiserror 2.0.18", + "winnow 0.7.14", +] + +[[package]] +name = "gix-odb" +version = "0.76.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e84f7e115b2b6615f0c5acddc269598ebc539c363fb276dbd242755a1dd7126c" +dependencies = [ + "arc-swap", + "gix-features", + "gix-fs", + "gix-hash", + "gix-hashtable", + "gix-object", + "gix-pack", + "gix-path", + "gix-quote", + "parking_lot", + "tempfile", + "thiserror 2.0.18", +] + +[[package]] +name = "gix-pack" +version = "0.66.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfc20b54d50f64fb02281f2a6c4a24bb9356befdf4535d5a68924575fd67cd5e" +dependencies = [ + "clru", + "gix-chunk", + "gix-error", + "gix-features", + "gix-hash", + "gix-hashtable", + "gix-object", + "gix-path", + "gix-tempfile", + "memmap2", + "parking_lot", + "smallvec", + "thiserror 2.0.18", +] + +[[package]] +name = "gix-packetline" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25429ee1ef792d9b653ee5de09bb525489fc8e6908334cfd5d5824269f0b7073" +dependencies = [ + "bstr", + "faster-hex", + "gix-trace", + "thiserror 2.0.18", +] + +[[package]] +name = "gix-path" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7163b1633d35846a52ef8093f390cec240e2d55da99b60151883035e5169cd85" +dependencies = [ + "bstr", + "gix-trace", + "gix-validate", + "thiserror 2.0.18", +] + +[[package]] +name = "gix-pathspec" +version = "0.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7f4cc23f55ca7c190bf243f1a4e2139d4522022f724fb0dfc06c93f65a01ef6" +dependencies = [ + "bitflags", + "bstr", + "gix-attributes", + "gix-config-value", + "gix-glob", + "gix-path", + "thiserror 2.0.18", +] + +[[package]] +name = "gix-prompt" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4806f1ebf969cd54d178ccd975911ef1829aeccea0b27630e63c9d26c8347d7f" +dependencies = [ + "gix-command", + "gix-config-value", + "parking_lot", + "rustix", + "thiserror 2.0.18", +] + +[[package]] +name = "gix-protocol" +version = "0.57.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a13680c03e847d8f32a59cf1511dd05334d429da33f5af08891367680f15cbca" +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.18", + "winnow 0.7.14", +] + +[[package]] +name = "gix-quote" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96fc2ff2ec8cc0c92807f02eab1f00eb02619fc2810d13dc42679492fcc36757" +dependencies = [ + "bstr", + "gix-utils", + "thiserror 2.0.18", +] + +[[package]] +name = "gix-ref" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c92fd2e86d65efe972a5226f303ed72cfb49a8ad03740e5cc2b27c07970a5d78" +dependencies = [ + "gix-actor", + "gix-features", + "gix-fs", + "gix-hash", + "gix-lock", + "gix-object", + "gix-path", + "gix-tempfile", + "gix-utils", + "gix-validate", + "memmap2", + "thiserror 2.0.18", + "winnow 0.7.14", +] + +[[package]] +name = "gix-refspec" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40adba15f8099159d37d0a21e1cfb6602c2e49b6c05f6f8a57a47d0fb21611af" +dependencies = [ + "bstr", + "gix-error", + "gix-glob", + "gix-hash", + "gix-revision", + "gix-validate", + "smallvec", + "thiserror 2.0.18", +] + +[[package]] +name = "gix-revision" +version = "0.41.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75ef94c9d76de0765e429ae22a01c512c0d50459269195a90ea11099a5fc752" +dependencies = [ + "bitflags", + "bstr", + "gix-commitgraph", + "gix-date", + "gix-error", + "gix-hash", + "gix-hashtable", + "gix-object", + "gix-revwalk", + "gix-trace", +] + +[[package]] +name = "gix-revwalk" +version = "0.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07efcad1d064abdac3e79dfc6e23e05accb579559d015e944c9dcf64be95eb49" +dependencies = [ + "gix-commitgraph", + "gix-date", + "gix-error", + "gix-hash", + "gix-hashtable", + "gix-object", + "smallvec", + "thiserror 2.0.18", +] + +[[package]] +name = "gix-sec" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e014df75f3d7f5c98b18b45c202422da6236a1c0c0a50997c3f41e601f3ad511" +dependencies = [ + "bitflags", + "gix-path", + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "gix-shallow" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "189386b5da5285216cc0ede89eff5a943d5261fc794241ee6ec5360b77df15ad" +dependencies = [ + "bstr", + "gix-hash", + "gix-lock", + "thiserror 2.0.18", +] + +[[package]] +name = "gix-status" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a441e06bff4a4f86ac0b320424193b061ee9c0578448eb9b965fbc96454ca292" +dependencies = [ + "bstr", + "filetime", + "gix-diff", + "gix-dir", + "gix-features", + "gix-filter", + "gix-fs", + "gix-hash", + "gix-index", + "gix-object", + "gix-path", + "gix-pathspec", + "gix-worktree", + "portable-atomic", + "thiserror 2.0.18", +] + +[[package]] +name = "gix-submodule" +version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" +checksum = "ac5d38afa855046b7b9e2b85f8cdf7fbf2e449ed061dc81fa7a757abaa38bfac" dependencies = [ - "futures-core", - "futures-task", - "futures-util", + "bstr", + "gix-config", + "gix-path", + "gix-pathspec", + "gix-refspec", + "gix-url", + "thiserror 2.0.18", ] [[package]] -name = "futures-io" -version = "0.3.31" +name = "gix-tempfile" +version = "21.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" +checksum = "9d9ab2c89fe4bfd4f1d8700aa4516534c170d8a21ae2c554167374607c2eaf16" +dependencies = [ + "dashmap", + "gix-fs", + "libc", + "parking_lot", + "tempfile", +] [[package]] -name = "futures-macro" -version = "0.3.31" +name = "gix-trace" +version = "0.1.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.106", -] +checksum = "f69a13643b8437d4ca6845e08143e847a36ca82903eed13303475d0ae8b162e0" [[package]] -name = "futures-sink" -version = "0.3.31" +name = "gix-transport" +version = "0.54.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" +checksum = "9561a98f4f1cada03b565121c475c95d060998082bdb1277044fa6e11fe2aa17" +dependencies = [ + "bstr", + "gix-command", + "gix-features", + "gix-packetline", + "gix-quote", + "gix-sec", + "gix-url", + "thiserror 2.0.18", +] [[package]] -name = "futures-task" -version = "0.3.31" +name = "gix-traverse" +version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" +checksum = "bcdd399f8527f643f8d4fd8de6ec3b6e2c3c826b758b68e90f28275af2e7b33a" +dependencies = [ + "bitflags", + "gix-commitgraph", + "gix-date", + "gix-hash", + "gix-hashtable", + "gix-object", + "gix-revwalk", + "smallvec", + "thiserror 2.0.18", +] [[package]] -name = "futures-util" -version = "0.3.31" +name = "gix-url" +version = "0.35.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +checksum = "507752d41afcdf5961ab494eb062c3bf21f68b2ee67e45568e9028cccdd00c34" dependencies = [ - "futures-channel", - "futures-core", - "futures-io", - "futures-macro", - "futures-sink", - "futures-task", - "memchr", - "pin-project-lite", - "pin-utils", - "slab", + "bstr", + "gix-path", + "percent-encoding", + "thiserror 2.0.18", ] [[package]] -name = "generic-array" -version = "0.14.7" +name = "gix-utils" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +checksum = "befcdbdfb1238d2854591f760a48711bed85e72d80a10e8f2f93f656746ef7c5" dependencies = [ - "typenum", - "version_check", + "bstr", + "fastrand", + "unicode-normalization", ] [[package]] -name = "getrandom" -version = "0.2.16" +name = "gix-validate" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" +checksum = "0ec1eff98d91941f47766367cba1be746bab662bad761d9891ae6f7882f7840b" dependencies = [ - "cfg-if", - "js-sys", - "libc", - "wasi 0.11.1+wasi-snapshot-preview1", - "wasm-bindgen", + "bstr", ] [[package]] -name = "getrandom" -version = "0.3.3" +name = "gix-worktree" +version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" +checksum = "93381c5cd37fa208e3f454433425985ea02725369fb33a79ff4ecf7c279f2f98" dependencies = [ - "cfg-if", - "js-sys", - "libc", - "r-efi", - "wasi 0.14.3+wasi-0.2.4", - "wasm-bindgen", + "bstr", + "gix-attributes", + "gix-fs", + "gix-glob", + "gix-hash", + "gix-ignore", + "gix-index", + "gix-object", + "gix-path", + "gix-validate", ] [[package]] -name = "git2" -version = "0.20.2" +name = "gix-worktree-state" +version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2deb07a133b1520dc1a5690e9bd08950108873d7ed5de38dcc74d3b5ebffa110" +checksum = "a521cb82cacfabe8ac5ace4dcf051ffc2282069a6488773351aff70fcef83d9a" dependencies = [ - "bitflags", - "libc", - "libgit2-sys", - "log", - "openssl-probe", - "openssl-sys", - "url", + "bstr", + "gix-features", + "gix-filter", + "gix-fs", + "gix-index", + "gix-object", + "gix-path", + "gix-worktree", + "io-close", + "thiserror 2.0.18", ] [[package]] @@ -1652,6 +2523,15 @@ dependencies = [ "tracing", ] +[[package]] +name = "hash32" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47d60b12902ba28e2730cd37e95b8c9223af2808df9e902d4df49588d1470606" +dependencies = [ + "byteorder", +] + [[package]] name = "hashbrown" version = "0.12.3" @@ -1664,11 +2544,35 @@ version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" +[[package]] +name = "hashbrown" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +dependencies = [ + "foldhash 0.1.5", +] + [[package]] name = "hashbrown" version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5419bdc4f6a9207fbeba6d11b604d481addf78ecd10c11ad51e76c2f6482748d" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash 0.2.0", +] + +[[package]] +name = "heapless" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bfb9eb618601c89945a70e254898da93b13be0388091d42117462b265bb3fad" +dependencies = [ + "hash32", + "stable_deref_trait", +] [[package]] name = "heck" @@ -2055,6 +2959,15 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "imara-diff" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17d34b7d42178945f775e84bc4c36dde7c1c6cdfea656d3354d009056f2bb3d2" +dependencies = [ + "hashbrown 0.15.5", +] + [[package]] name = "indexmap" version = "1.9.3" @@ -2122,6 +3035,16 @@ dependencies = [ "generic-array", ] +[[package]] +name = "io-close" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cadcf447f06744f8ce713d2d6239bb5bde2c357a452397a9ed90c625da390bc" +dependencies = [ + "libc", + "winapi", +] + [[package]] name = "ipnet" version = "2.11.0" @@ -2155,9 +3078,50 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.15" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" + +[[package]] +name = "jiff" +version = "0.2.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c867c356cc096b33f4981825ab281ecba3db0acefe60329f044c1789d94c6543" +dependencies = [ + "jiff-static", + "jiff-tzdb-platform", + "log", + "portable-atomic", + "portable-atomic-util", + "serde_core", + "windows-sys 0.60.2", +] + +[[package]] +name = "jiff-static" +version = "0.2.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7946b4325269738f270bb55b3c19ab5c5040525f83fd625259422a9d25d9be5" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", +] + +[[package]] +name = "jiff-tzdb" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68971ebff725b9e2ca27a601c5eb38a4c5d64422c4cbab0c535f248087eda5c2" + +[[package]] +name = "jiff-tzdb-platform" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" +checksum = "875a5a69ac2bab1a891711cf5eccbec1ce0341ea805560dcd90b7a2e925132e8" +dependencies = [ + "jiff-tzdb", +] [[package]] name = "jobserver" @@ -2221,6 +3185,15 @@ dependencies = [ "sha2", ] +[[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" @@ -2235,23 +3208,9 @@ checksum = "2c4a545a15244c7d945065b5d392b2d2d7f21526fba56ce51467b06ed445e8f7" [[package]] name = "libc" -version = "0.2.175" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a82ae493e598baaea5209805c49bbf2ea7de956d50d7da0da1164f9c6d28543" - -[[package]] -name = "libgit2-sys" -version = "0.18.2+1.9.1" +version = "0.2.182" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c42fe03df2bd3c53a3a9c7317ad91d80c81cd1fb0caec8d7cc4cd2bfa10c222" -dependencies = [ - "cc", - "libc", - "libssh2-sys", - "libz-sys", - "openssl-sys", - "pkg-config", -] +checksum = "6800badb6cb2082ffd7b6a67e6125bb39f18782f793520caee8cb8846be06112" [[package]] name = "libredox" @@ -2264,20 +3223,6 @@ dependencies = [ "redox_syscall", ] -[[package]] -name = "libssh2-sys" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "220e4f05ad4a218192533b300327f5150e809b54c4ec83b5a1d91833601811b9" -dependencies = [ - "cc", - "libc", - "libz-sys", - "openssl-sys", - "pkg-config", - "vcpkg", -] - [[package]] name = "libyml" version = "0.0.5" @@ -2294,26 +3239,14 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "840db8cf39d9ec4dd794376f38acc40d0fc65eec2a8f484f7fd375b84602becd" dependencies = [ - "zlib-rs", -] - -[[package]] -name = "libz-sys" -version = "1.1.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b70e7a7df205e92a1a4cd9aaae7898dac0aa555503cc0a649494d0d60e7651d" -dependencies = [ - "cc", - "libc", - "pkg-config", - "vcpkg", + "zlib-rs 0.5.2", ] [[package]] name = "linux-raw-sys" -version = "0.9.4" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" +checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" [[package]] name = "litemap" @@ -2368,6 +3301,17 @@ version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3" +[[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 = "md-5" version = "0.10.6" @@ -2384,6 +3328,15 @@ version = "2.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" +[[package]] +name = "memmap2" +version = "0.9.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "714098028fe011992e1c3962653c96b2d578c4b4bce9036e15ff220319b1e0e3" +dependencies = [ + "libc", +] + [[package]] name = "mime" version = "0.3.17" @@ -2682,28 +3635,6 @@ version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" -[[package]] -name = "openssl-src" -version = "300.5.4+3.5.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a507b3792995dae9b0df8a1c1e3771e8418b7c2d9f0baeba32e6fe8b06c7cb72" -dependencies = [ - "cc", -] - -[[package]] -name = "openssl-sys" -version = "0.9.109" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90096e2e47630d78b7d1c20952dc621f957103f8bc2c8359ec81290d75238571" -dependencies = [ - "cc", - "libc", - "openssl-src", - "pkg-config", - "vcpkg", -] - [[package]] name = "opentelemetry" version = "0.31.0" @@ -2714,7 +3645,7 @@ dependencies = [ "futures-sink", "js-sys", "pin-project-lite", - "thiserror 2.0.16", + "thiserror 2.0.18", "tracing", ] @@ -2756,7 +3687,7 @@ dependencies = [ "opentelemetry_sdk", "prost 0.14.1", "reqwest", - "thiserror 2.0.16", + "thiserror 2.0.18", "tokio", "tonic 0.14.2", "tracing", @@ -2787,7 +3718,7 @@ dependencies = [ "opentelemetry", "percent-encoding", "rand 0.9.2", - "thiserror 2.0.16", + "thiserror 2.0.18", ] [[package]] @@ -2930,6 +3861,15 @@ version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483" +[[package]] +name = "portable-atomic-util" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a9db96d7fa8782dd8c15ce32ffe8680bbd1e978a43bf51a34d39483540495f5" +dependencies = [ + "portable-atomic", +] + [[package]] name = "potential_utf" version = "0.1.3" @@ -2996,6 +3936,15 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "prodash" +version = "31.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "962200e2d7d551451297d9fdce85138374019ada198e30ea9ede38034e27604c" +dependencies = [ + "parking_lot", +] + [[package]] name = "prost" version = "0.13.5" @@ -3094,7 +4043,7 @@ dependencies = [ "rustc-hash", "rustls", "socket2 0.6.0", - "thiserror 2.0.16", + "thiserror 2.0.18", "tokio", "tracing", "web-time", @@ -3115,7 +4064,7 @@ dependencies = [ "rustls", "rustls-pki-types", "slab", - "thiserror 2.0.16", + "thiserror 2.0.18", "tinyvec", "tracing", "web-time", @@ -3405,9 +4354,9 @@ dependencies = [ [[package]] name = "rustix" -version = "1.0.8" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11181fbabf243db407ef8df94a6ce0b2f9a733bd8be4ad02b4eda9602296cac8" +checksum = "146c9e247ccc180c1f61615433868c99f3de3ae256a30a43b49f67c2d9171f34" dependencies = [ "bitflags", "errno", @@ -3755,6 +4704,16 @@ 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" @@ -3781,6 +4740,12 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "45bb67a18fa91266cc7807181f62f9178a6873bfad7dc788c42e6430db40184f" +[[package]] +name = "shell-words" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc6fe69c597f9c37bfeeeeeb33da3530379845f10be461a66d16d03eca2ded77" + [[package]] name = "shlex" version = "1.3.0" @@ -3810,7 +4775,7 @@ checksum = "297f631f50729c8c99b84667867963997ec0b50f32b2a7dbcab828ef0541e8bb" dependencies = [ "num-bigint", "num-traits", - "thiserror 2.0.16", + "thiserror 2.0.18", "time", ] @@ -3873,6 +4838,12 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + [[package]] name = "strip-ansi-escapes" version = "0.2.1" @@ -3985,15 +4956,15 @@ checksum = "83176759e9416cf81ee66cb6508dbfe9c96f20b8b56265a39917551c23c70964" [[package]] name = "tempfile" -version = "3.23.0" +version = "3.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d31c77bdf42a745371d260a26ca7163f1e0924b64afa0b688e61b5a9fa02f16" +checksum = "0136791f7c95b1f6dd99f9cc786b91bb81c3800b639b3478e561ddb7be95e5f1" dependencies = [ "fastrand", "getrandom 0.3.3", "once_cell", "rustix", - "windows-sys 0.61.0", + "windows-sys 0.60.2", ] [[package]] @@ -4023,7 +4994,7 @@ dependencies = [ "serde", "serde_json", "serde_with", - "thiserror 2.0.16", + "thiserror 2.0.18", "tokio", "tokio-stream", "tokio-util", @@ -4042,11 +5013,11 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.16" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3467d614147380f2e4e374161426ff399c91084acd2363eaf549172b3d5e60c0" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" dependencies = [ - "thiserror-impl 2.0.16", + "thiserror-impl 2.0.18", ] [[package]] @@ -4062,9 +5033,9 @@ dependencies = [ [[package]] name = "thiserror-impl" -version = "2.0.16" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c5e1be1c48b9172ee610da68fd9cd2770e7a4056cb3fc98710ee6906f0c7960" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" dependencies = [ "proc-macro2", "quote", @@ -4158,7 +5129,7 @@ dependencies = [ "signal-hook-registry", "socket2 0.6.0", "tokio-macros", - "windows-sys 0.61.0", + "windows-sys 0.61.2", ] [[package]] @@ -4218,7 +5189,7 @@ dependencies = [ "toml_datetime 0.7.3", "toml_parser", "toml_writer", - "winnow 0.7.13", + "winnow 0.7.14", ] [[package]] @@ -4262,7 +5233,7 @@ dependencies = [ "toml_datetime 0.7.3", "toml_parser", "toml_writer", - "winnow 0.7.13", + "winnow 0.7.14", ] [[package]] @@ -4271,7 +5242,7 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0cbe268d35bdb4bb5a56a2de88d0ad0eb70af5384a99d648cd4b3d04039800e" dependencies = [ - "winnow 0.7.13", + "winnow 0.7.14", ] [[package]] @@ -4486,6 +5457,12 @@ 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-ident" version = "1.0.18" @@ -4588,12 +5565,6 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" -[[package]] -name = "vcpkg" -version = "0.2.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" - [[package]] name = "version_check" version = "0.9.5" @@ -4852,9 +5823,9 @@ checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" [[package]] name = "windows-link" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45e46c0661abb7180e7b9c281db115305d49ca1709ab8242adf09666d2173c65" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" [[package]] name = "windows-result" @@ -4912,11 +5883,11 @@ dependencies = [ [[package]] name = "windows-sys" -version = "0.61.0" +version = "0.61.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e201184e40b2ede64bc2ea34968b28e33622acdbbf37104f0e4a33f7abe657aa" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" dependencies = [ - "windows-link 0.2.0", + "windows-link 0.2.1", ] [[package]] @@ -5116,9 +6087,9 @@ dependencies = [ [[package]] name = "winnow" -version = "0.7.13" +version = "0.7.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21a0236b59786fed61e2a80582dd500fe61f18b5dca67a4a067d0bc9039339cf" +checksum = "5a5364e9d77fcdeeaa6062ced926ee3381faa2ee02d3eb83a5c27a8825540829" dependencies = [ "memchr", ] @@ -5325,6 +6296,12 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2f06ae92f42f5e5c42443fd094f245eb656abf56dd7cce9b8b263236565e00f2" +[[package]] +name = "zlib-rs" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c745c48e1007337ed136dc99df34128b9faa6ed542d80a1c673cf55a6d7236c8" + [[package]] name = "zopfli" version = "0.8.2" diff --git a/Cargo.toml b/Cargo.toml index 4af70db07..8219b05f0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,7 +6,7 @@ license = "MIT OR Apache-2.0" name = "cargo-fslabscli" publish = ["fsl"] repository = "https://github.com/fslabs/fslabsci" -version = "2.35.0" +version = "2.36.0" [package.metadata.fslabs.publish.binary] name = "FSLABS Cli tool" @@ -96,10 +96,18 @@ version = "1.0.100" features = ["derive", "env"] version = "4.5.51" -[dependencies.git2] +[dependencies.gix] +version = "0.79.0" default-features = false -version = "0.20.2" -features = ["vendored-openssl", "https", "ssh"] +features = [ + "blocking-network-client", + "revision", + "blob-diff", + "status", + "index", + "credentials", + "worktree-mutation", +] [dependencies.humanize-duration] features = ["chrono"] diff --git a/flake.lock b/flake.lock index 055a5d086..923732918 100644 --- a/flake.lock +++ b/flake.lock @@ -35,11 +35,11 @@ }, "crane": { "locked": { - "lastModified": 1771121070, - "narHash": "sha256-aIlv7FRXF9q70DNJPI237dEDAznSKaXmL5lfK/Id/bI=", + "lastModified": 1771438068, + "narHash": "sha256-nGBbXvEZVe/egCPVPFcu89RFtd8Rf6J+4RFoVCFec0A=", "owner": "ipetkov", "repo": "crane", - "rev": "a2812c19f1ed2e5ed5ce2ef7109798b575c180e1", + "rev": "b5090e53e9d68c523a4bb9ad42b4737ee6747597", "type": "github" }, "original": { @@ -59,11 +59,11 @@ "nixpkgs": "nixpkgs" }, "locked": { - "lastModified": 1771325641, - "narHash": "sha256-Ns9/EoctaS9hS64pNDSfY+2h0PnzjttFZ/vwMpvZjjM=", + "lastModified": 1771419682, + "narHash": "sha256-NAemVgEJeZjGl3+438M4rUL8ms9QdDFMYthU12F70FQ=", "owner": "cachix", "repo": "devenv", - "rev": "2f2b8ca9fc2c20dad043399cce9dd24aa5d77197", + "rev": "f77fc4de35c184d9ef9a32d5d7e9033351bcdfdc", "type": "github" }, "original": { @@ -78,11 +78,11 @@ "rust-analyzer-src": "rust-analyzer-src" }, "locked": { - "lastModified": 1771312404, - "narHash": "sha256-eJ1YY3a+uQTfa1T3LsR2JX7n3F9vAKAwsuX/3Aecve0=", + "lastModified": 1771485140, + "narHash": "sha256-oFJv+Zp5AjQ9yDWlisGSD5OK20iz7v+hfedEdRg2/Aw=", "owner": "nix-community", "repo": "fenix", - "rev": "b9696f35e4413cbd2873e4ac0252cd444f772772", + "rev": "f95b0cc3fffa5057e9d207afb98dda5178253380", "type": "github" }, "original": { @@ -294,11 +294,11 @@ }, "nixpkgs_2": { "locked": { - "lastModified": 1771008912, - "narHash": "sha256-gf2AmWVTs8lEq7z/3ZAsgnZDhWIckkb+ZnAo5RzSxJg=", + "lastModified": 1771369470, + "narHash": "sha256-0NBlEBKkN3lufyvFegY4TYv5mCNHbi5OmBDrzihbBMQ=", "owner": "nixos", "repo": "nixpkgs", - "rev": "a82ccc39b39b621151d6732718e3e250109076fa", + "rev": "0182a361324364ae3f436a63005877674cf45efb", "type": "github" }, "original": { @@ -310,11 +310,11 @@ }, "nixpkgs_3": { "locked": { - "lastModified": 1771008912, - "narHash": "sha256-gf2AmWVTs8lEq7z/3ZAsgnZDhWIckkb+ZnAo5RzSxJg=", + "lastModified": 1771369470, + "narHash": "sha256-0NBlEBKkN3lufyvFegY4TYv5mCNHbi5OmBDrzihbBMQ=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "a82ccc39b39b621151d6732718e3e250109076fa", + "rev": "0182a361324364ae3f436a63005877674cf45efb", "type": "github" }, "original": { @@ -336,11 +336,11 @@ "rust-analyzer-src": { "flake": false, "locked": { - "lastModified": 1771264911, - "narHash": "sha256-vDNZ6Y1M3DSa1JbPGgqtdJPl4rMzxebUK9hmZcopxX0=", + "lastModified": 1771444642, + "narHash": "sha256-ztRHVn93XOfYZNnGYyNadFdR8gp3Xi2JKDKwBzb0ApI=", "owner": "rust-lang", "repo": "rust-analyzer", - "rev": "3360aebb35a099ff4a7bbd37e9a0dd44b8f23748", + "rev": "70f44024cc9d92c56df79832d81775cb99dc2e21", "type": "github" }, "original": { diff --git a/flake.nix b/flake.nix index 887bb7320..193564776 100644 --- a/flake.nix +++ b/flake.nix @@ -54,18 +54,18 @@ pkgsCross = generateCross rustTarget; depsBuildBuild = [ pkgsCross.libiconv ]; }; - "x86_64-windows" = - let - pkgsCross = pkgs.pkgsCross.mingwW64; - in - { - inherit pkgsCross; - rustTarget = "x86_64-pc-windows-gnu"; - depsBuildBuild = [ - pkgsCross.stdenv.cc - pkgsCross.windows.pthreads - ]; - }; + # "x86_64-windows" = + # let + # pkgsCross = pkgs.pkgsCross.mingwW64; + # in + # { + # inherit pkgsCross; + # rustTarget = "x86_64-pc-windows-gnu"; + # depsBuildBuild = [ + # pkgsCross.stdenv.cc + # pkgsCross.windows.pthreads + # ]; + # }; }; generateCommonArgs = craneLib': { @@ -79,6 +79,8 @@ pkgs.git pkgs.installShellFiles # Shell Completions pkgs.rustPlatform.bindgenHook + ] ++ lib.optionals isDarwin [ + pkgs.libiconv ]; buildInputs = [ pkgs.stdenv.cc @@ -136,7 +138,6 @@ commonArgs = (generateCommonArgs craneLibCross) // { depsBuildBuild = depsBuildBuild ++ [ pkgsCross.buildPackages.perl - pkgsCross.buildPackages.openssl ]; inherit TARGET_CC; @@ -152,9 +153,6 @@ CC = "${pkgsCross.stdenv.cc}/bin/${pkgsCross.stdenv.cc.targetPrefix}cc"; LD = "${pkgsCross.stdenv.cc}/bin/${pkgsCross.stdenv.cc.targetPrefix}cc"; - OPENSSL_STATIC = "1"; - OPENSSL_NO_VENDOR = "0"; - PKG_CONFIG_ALL_STATIC = "1"; }; cargoArtifacts = craneLibCross.buildDepsOnly (commonArgs // { }); @@ -191,7 +189,10 @@ ) arch2targets; in lib.attrsets.mapAttrs' ( - arch: _: lib.nameValuePair (packageName + "-" + arch) (mkCrossRustPackage arch packageName) + arch: _: lib.nameValuePair (packageName + "-" + arch) ( + if arch == system then mkRustPackage packageName + else mkCrossRustPackage arch packageName + ) ) filteredTargets; in { @@ -237,6 +238,7 @@ fenixPkgs.rust-analyzer ]; languages = { + c.enable = false; rust = { enable = true; toolchainPackage = diff --git a/src/cli_args.rs b/src/cli_args.rs index 0c91cadb3..2b01722c9 100644 --- a/src/cli_args.rs +++ b/src/cli_args.rs @@ -1,5 +1,5 @@ use clap::{Parser, ValueEnum}; -use git2::{Object, Repository}; +use gix::Repository; use serde::Serialize; use std::fmt; @@ -21,29 +21,25 @@ pub enum DiffStrategy { } impl DiffStrategy { - pub fn git_commits<'r>( - &self, - repo: &'r Repository, - ) -> anyhow::Result<(Object<'r>, Object<'r>)> { + pub fn git_commits(&self, repo: &Repository) -> anyhow::Result<(gix::ObjectId, gix::ObjectId)> { match self { DiffStrategy::Explicit { base, head } => { - let head_commit = repo.revparse_single(head)?; - let base_commit = repo.revparse_single(base)?; - Ok((base_commit, head_commit)) + let head_id = repo.rev_parse_single(head.as_str())?.detach(); + let base_id = repo.rev_parse_single(base.as_str())?.detach(); + Ok((base_id, head_id)) } DiffStrategy::LocalChanges | DiffStrategy::All => { - // Compare HEAD~ vs HEAD (last commit changes) - // Falls back to HEAD vs HEAD if no parent exists (single commit repo) - let head_commit = repo.revparse_single("HEAD")?; - let base_commit = repo - .revparse_single("HEAD~") - .unwrap_or_else(|_| head_commit.clone()); - Ok((base_commit, head_commit)) + let head_id = repo.rev_parse_single("HEAD")?.detach(); + let base_id = repo + .rev_parse_single("HEAD~") + .map(|id| id.detach()) + .unwrap_or(head_id); + Ok((base_id, head_id)) } DiffStrategy::WorktreeVsBranch { branch } => { - let head_commit = repo.revparse_single("HEAD")?; - let base_commit = repo.revparse_single(branch)?; - Ok((base_commit, head_commit)) + let head_id = repo.rev_parse_single("HEAD")?.detach(); + let base_id = repo.rev_parse_single(branch.as_str())?.detach(); + Ok((base_id, head_id)) } } } @@ -95,157 +91,194 @@ impl DiffOptions { #[cfg(test)] mod tests { use super::*; - use git2::{Oid, Signature, Time}; use std::fs; + use std::process::Command; use tempfile::TempDir; + fn git(repo_path: &std::path::Path) -> Command { + let mut cmd = Command::new("git"); + cmd.current_dir(repo_path); + cmd.env("GIT_AUTHOR_NAME", "Test User"); + cmd.env("GIT_AUTHOR_EMAIL", "test@example.com"); + cmd.env("GIT_COMMITTER_NAME", "Test User"); + cmd.env("GIT_COMMITTER_EMAIL", "test@example.com"); + cmd + } + + fn get_commit_sha(repo_path: &std::path::Path, rev: &str) -> String { + let output = git(repo_path) + .args(["rev-parse", rev]) + .output() + .expect("Failed to get commit SHA"); + String::from_utf8_lossy(&output.stdout).trim().to_string() + } + // Test helper to create a repo with commits - fn setup_test_repo() -> (TempDir, Oid, Oid) { + fn setup_test_repo() -> (TempDir, String, String) { let temp_dir = TempDir::new().unwrap(); - let repo = Repository::init(temp_dir.path()).unwrap(); - // Configure repo - let mut config = repo.config().unwrap(); - config.set_str("user.name", "Test User").unwrap(); - config.set_str("user.email", "test@example.com").unwrap(); + // Initialize repo + let output = git(temp_dir.path()) + .arg("init") + .output() + .expect("Failed to init repo"); + assert!(output.status.success()); - let sig = - Signature::new("Test User", "test@example.com", &Time::new(1234567890, 0)).unwrap(); + // Configure repo + let output = git(temp_dir.path()) + .args(["config", "commit.gpgsign", "false"]) + .output() + .expect("Failed to configure git"); + assert!(output.status.success()); // Create first commit - let tree_id = { - let mut index = repo.index().unwrap(); - fs::write(temp_dir.path().join("file1.txt"), "content1").unwrap(); - index.add_path(std::path::Path::new("file1.txt")).unwrap(); - index.write().unwrap(); - index.write_tree().unwrap() - }; + fs::write(temp_dir.path().join("file1.txt"), "content1").unwrap(); + let output = git(temp_dir.path()) + .args(["add", "file1.txt"]) + .output() + .expect("Failed to add file"); + assert!(output.status.success()); + + let output = git(temp_dir.path()) + .args(["commit", "-m", "First commit"]) + .output() + .expect("Failed to commit"); + assert!(output.status.success()); - let tree = repo.find_tree(tree_id).unwrap(); - let first_commit = repo - .commit(Some("HEAD"), &sig, &sig, "First commit", &tree, &[]) - .unwrap(); + let first_commit = get_commit_sha(temp_dir.path(), "HEAD"); // Create second commit - let tree_id = { - let mut index = repo.index().unwrap(); - fs::write(temp_dir.path().join("file2.txt"), "content2").unwrap(); - index.add_path(std::path::Path::new("file2.txt")).unwrap(); - index.write().unwrap(); - index.write_tree().unwrap() - }; + fs::write(temp_dir.path().join("file2.txt"), "content2").unwrap(); + let output = git(temp_dir.path()) + .args(["add", "file2.txt"]) + .output() + .expect("Failed to add file"); + assert!(output.status.success()); + + let output = git(temp_dir.path()) + .args(["commit", "-m", "Second commit"]) + .output() + .expect("Failed to commit"); + assert!(output.status.success()); - let tree = repo.find_tree(tree_id).unwrap(); - let parent = repo.find_commit(first_commit).unwrap(); - let second_commit = repo - .commit(Some("HEAD"), &sig, &sig, "Second commit", &tree, &[&parent]) - .unwrap(); + let second_commit = get_commit_sha(temp_dir.path(), "HEAD"); (temp_dir, first_commit, second_commit) } #[test] fn test_diff_strategy_local_changes() { - let (temp_dir, first_oid, second_oid) = setup_test_repo(); - let repo = Repository::open(temp_dir.path()).unwrap(); + let (temp_dir, first_sha, second_sha) = setup_test_repo(); + let repo = gix::open(temp_dir.path()).unwrap(); let strategy = DiffStrategy::LocalChanges; let (base, head) = strategy.git_commits(&repo).unwrap(); // LocalChanges compares HEAD~ vs HEAD - assert_eq!(base.id(), first_oid); - assert_eq!(head.id(), second_oid); + assert_eq!(base.to_string(), first_sha); + assert_eq!(head.to_string(), second_sha); } #[test] fn test_diff_strategy_local_changes_single_commit() { // Test that LocalChanges works even with just one commit let temp_dir = TempDir::new().unwrap(); - let repo = Repository::init(temp_dir.path()).unwrap(); - // Configure repo - let mut config = repo.config().unwrap(); - config.set_str("user.name", "Test User").unwrap(); - config.set_str("user.email", "test@example.com").unwrap(); + // Initialize repo + let output = git(temp_dir.path()) + .arg("init") + .output() + .expect("Failed to init repo"); + assert!(output.status.success()); - let sig = - Signature::new("Test User", "test@example.com", &Time::new(1234567890, 0)).unwrap(); + // Configure repo + let output = git(temp_dir.path()) + .args(["config", "commit.gpgsign", "false"]) + .output() + .expect("Failed to configure git"); + assert!(output.status.success()); // Create single commit - let tree_id = { - let mut index = repo.index().unwrap(); - fs::write(temp_dir.path().join("file1.txt"), "content1").unwrap(); - index.add_path(std::path::Path::new("file1.txt")).unwrap(); - index.write().unwrap(); - index.write_tree().unwrap() - }; - - let tree = repo.find_tree(tree_id).unwrap(); - let commit_oid = repo - .commit(Some("HEAD"), &sig, &sig, "Initial commit", &tree, &[]) - .unwrap(); - + fs::write(temp_dir.path().join("file1.txt"), "content1").unwrap(); + let output = git(temp_dir.path()) + .args(["add", "file1.txt"]) + .output() + .expect("Failed to add file"); + assert!(output.status.success()); + + let output = git(temp_dir.path()) + .args(["commit", "-m", "Initial commit"]) + .output() + .expect("Failed to commit"); + assert!(output.status.success()); + + let commit_sha = get_commit_sha(temp_dir.path(), "HEAD"); + + let repo = gix::open(temp_dir.path()).unwrap(); let strategy = DiffStrategy::LocalChanges; let (base, head) = strategy.git_commits(&repo).unwrap(); // Should successfully return HEAD vs HEAD even with single commit - assert_eq!(base.id(), commit_oid); - assert_eq!(head.id(), commit_oid); + assert_eq!(base.to_string(), commit_sha); + assert_eq!(head.to_string(), commit_sha); } #[test] fn test_diff_strategy_explicit() { - let (temp_dir, first_oid, second_oid) = setup_test_repo(); - let repo = Repository::open(temp_dir.path()).unwrap(); + let (temp_dir, first_sha, second_sha) = setup_test_repo(); + let repo = gix::open(temp_dir.path()).unwrap(); let strategy = DiffStrategy::Explicit { - base: first_oid.to_string(), - head: second_oid.to_string(), + base: first_sha.clone(), + head: second_sha.clone(), }; let (base, head) = strategy.git_commits(&repo).unwrap(); - assert_eq!(base.id(), first_oid); - assert_eq!(head.id(), second_oid); + assert_eq!(base.to_string(), first_sha); + assert_eq!(head.to_string(), second_sha); } #[test] fn test_diff_strategy_explicit_short_sha() { - let (temp_dir, first_oid, second_oid) = setup_test_repo(); - let repo = Repository::open(temp_dir.path()).unwrap(); + let (temp_dir, first_sha, second_sha) = setup_test_repo(); + let repo = gix::open(temp_dir.path()).unwrap(); let strategy = DiffStrategy::Explicit { - base: first_oid.to_string()[..7].to_string(), - head: second_oid.to_string()[..7].to_string(), + base: first_sha[..7].to_string(), + head: second_sha[..7].to_string(), }; let (base, head) = strategy.git_commits(&repo).unwrap(); - assert_eq!(base.id(), first_oid); - assert_eq!(head.id(), second_oid); + assert_eq!(base.to_string(), first_sha); + assert_eq!(head.to_string(), second_sha); } #[test] fn test_diff_strategy_worktree_vs_branch() { - let (temp_dir, first_oid, second_oid) = setup_test_repo(); - let repo = Repository::open(temp_dir.path()).unwrap(); + let (temp_dir, first_sha, second_sha) = setup_test_repo(); // Create a branch pointing to first commit - repo.branch("test-branch", &repo.find_commit(first_oid).unwrap(), false) - .unwrap(); + let output = git(temp_dir.path()) + .args(["branch", "test-branch", &first_sha]) + .output() + .expect("Failed to create branch"); + assert!(output.status.success()); + let repo = gix::open(temp_dir.path()).unwrap(); let strategy = DiffStrategy::WorktreeVsBranch { branch: "test-branch".to_string(), }; let (base, head) = strategy.git_commits(&repo).unwrap(); - assert_eq!(base.id(), first_oid); - assert_eq!(head.id(), second_oid); // HEAD is still at second commit + assert_eq!(base.to_string(), first_sha); + assert_eq!(head.to_string(), second_sha); // HEAD is still at second commit } #[test] fn test_diff_strategy_explicit_invalid_sha() { - let (temp_dir, _first_oid, _second_oid) = setup_test_repo(); - let repo = Repository::open(temp_dir.path()).unwrap(); + let (temp_dir, _first_sha, _second_sha) = setup_test_repo(); + let repo = gix::open(temp_dir.path()).unwrap(); let strategy = DiffStrategy::Explicit { base: "invalid".to_string(), head: "also-invalid".to_string(), @@ -257,8 +290,8 @@ mod tests { #[test] fn test_diff_strategy_worktree_vs_branch_invalid() { - let (temp_dir, _first_oid, _second_oid) = setup_test_repo(); - let repo = Repository::open(temp_dir.path()).unwrap(); + let (temp_dir, _first_sha, _second_sha) = setup_test_repo(); + let repo = gix::open(temp_dir.path()).unwrap(); let strategy = DiffStrategy::WorktreeVsBranch { branch: "nonexistent-branch".to_string(), }; diff --git a/src/commands/check_workspace/mod.rs b/src/commands/check_workspace/mod.rs index 10157b4ac..caec3b11c 100644 --- a/src/commands/check_workspace/mod.rs +++ b/src/commands/check_workspace/mod.rs @@ -1,6 +1,5 @@ use chrono::{Duration, prelude::*}; use core::result::Result as CoreResult; -use git2::Repository; use std::cmp; use std::cmp::Ordering; use std::collections::{HashMap, HashSet}; @@ -1241,11 +1240,11 @@ impl<'a, C: CrateChecker> WorkspaceChecker<'a, C> { )); } - let repo = Repository::open(self.repo_root)?; - let (base_commit, head_commit) = diff_strategy.git_commits(&repo)?; + let repo = gix::open(&self.repo_root)?; + let (base_commit_id, head_commit_id) = diff_strategy.git_commits(&repo)?; // Check changed from a git pov let changed_package_paths = - crates.changed_packages(&repo, base_commit, head_commit, &diff_strategy)?; + crates.changed_packages(&repo, base_commit_id, head_commit_id, &diff_strategy)?; tracing::info!("Changed packages: {changed_package_paths:#?}"); // Any packages that transitively depend on changed packages are also considered "changed". let changed_closure = crates diff --git a/src/commands/fix_lock_files/mod.rs b/src/commands/fix_lock_files/mod.rs index fe9d95824..0158ca85b 100644 --- a/src/commands/fix_lock_files/mod.rs +++ b/src/commands/fix_lock_files/mod.rs @@ -3,7 +3,6 @@ use crate::{ }; use clap::Parser; use diffy::create_patch; -use git2::Repository; use std::path::Path; use tracing::{debug, info}; @@ -22,7 +21,7 @@ pub struct Options { /// The base revision spec used for checking `Cargo.lock` files. /// /// This can either be a git revision SHA or any other revision spec - /// supported by the `git2` crate. For example, a local branch name can be + /// supported by gix. For example, a local branch name can be /// given as "$branch_name", or a remote branch as "remote/$branch_name", as /// in "origin/main". /// @@ -61,31 +60,25 @@ then you must manually specify that branch with `--base-rev`."; /// This function uses git's blob reading capabilities to access file content /// from a historical commit without modifying the working directory. fn read_file_from_commit( - repo: &Repository, - commit: &git2::Object, + repo: &gix::Repository, + commit_id: gix::ObjectId, file_path: &Path, ) -> anyhow::Result> { - // Convert the object to a commit - let commit = commit - .as_commit() - .ok_or_else(|| anyhow::anyhow!("Object is not a commit"))?; - + let commit = repo.find_commit(commit_id)?; let tree = commit.tree()?; - // Try to get the tree entry for this path - match tree.get_path(file_path) { - Ok(entry) => { - // Get the blob object - let blob = repo.find_blob(entry.id())?; + let path_str = file_path + .to_str() + .ok_or_else(|| anyhow::anyhow!("Non-UTF8 path: {:?}", file_path))?; - // Convert blob content to string - let content = std::str::from_utf8(blob.content())?; + match tree.lookup_entry_by_path(path_str) { + Ok(Some(entry)) => { + let object = entry.object()?; + let blob = object.into_blob(); + let content = std::str::from_utf8(blob.data.as_slice())?; Ok(Some(content.to_string())) } - Err(_) => { - // File doesn't exist at this commit - Ok(None) - } + Ok(None) | Err(_) => Ok(None), } } @@ -117,28 +110,25 @@ pub fn fix_workspace_lockfile( } }; - let repo = Repository::open(repo_root)?; - let base_commit = repo.revparse_single(base_revspec)?; + let repo = gix::open(repo_root)?; + let base_commit_id = repo.rev_parse_single(base_revspec)?.detach(); // Restore `Cargo.lock` file to its state at `base_rev` using git blob reading // instead of checkout to avoid interfering with parallel tests. - debug!("Reading Cargo.lock from base commit {}", base_commit.id()); + debug!("Reading Cargo.lock from base commit {}", base_commit_id); // Get relative path from repo root for git tree lookup let lock_path_relative = lock_path.strip_prefix(repo_root)?; // Read the lockfile content from base_commit without checking it out - let base_lockfile = read_file_from_commit(&repo, &base_commit, lock_path_relative)?; + let base_lockfile = read_file_from_commit(&repo, base_commit_id, lock_path_relative)?; if let Some(base_contents) = base_lockfile { - debug!( - "Restoring {lock_path:?} to contents at {}", - base_commit.id() - ); + debug!("Restoring {lock_path:?} to contents at {}", base_commit_id); std::fs::write(&lock_path, base_contents)?; } else { debug!( "Cargo.lock did not exist at {}, removing if present", - base_commit.id() + base_commit_id ); // If the file didn't exist at base_commit, remove it if it exists now let _ = std::fs::remove_file(&lock_path); @@ -232,7 +222,8 @@ pub fn fix_lock_files( #[cfg(test)] mod tests { use crate::utils::test::{ - commit_all_changes, commit_repo, create_complex_workspace, modify_file, stage_file, + commit_all_changes, commit_repo, create_complex_workspace, init_repo, modify_file, + stage_file, }; use super::*; @@ -247,18 +238,7 @@ mod tests { .into_persistent() .to_path_buf(); - let repo = Repository::init(&tmp).expect("Failed to init repo"); - - // Configure Git user info (required for commits) - repo.config() - .unwrap() - .set_str("user.name", "Test User") - .unwrap(); - repo.config() - .unwrap() - .set_str("user.email", "test@example.com") - .unwrap(); - repo.config().unwrap().set_str("gpg.sign", "false").unwrap(); + init_repo(&tmp); Command::new("cargo") .arg("init") @@ -468,21 +448,16 @@ version = "0.2.0" // from git history without checking out the base commit, thus avoiding // interference with parallel tests and other files in the working directory. let repo_path = create_versioned_rust_crate(); - let repo = Repository::open(&repo_path).expect("Failed to open repo"); - // Get the initial commit SHA (HEAD~3 in this repo's history) + // Get the initial commit SHA (HEAD~3 in this repo's history) using git CLI let initial_commit_sha = { - let head = repo.head().unwrap(); - let commit = head.peel_to_commit().unwrap(); - // Walk back 3 commits to get the initial commit - let initial_commit = commit - .parent(0) - .unwrap() - .parent(0) - .unwrap() - .parent(0) - .unwrap(); - initial_commit.id().to_string() + let output = Command::new("git") + .args(["rev-parse", "HEAD~3"]) + .current_dir(&repo_path) + .output() + .expect("Failed to get HEAD~3 commit"); + assert!(output.status.success(), "git rev-parse HEAD~3 failed"); + String::from_utf8_lossy(&output.stdout).trim().to_string() }; // Store the main.rs content before running the fix @@ -552,21 +527,16 @@ version = "0.2.0" // file reads. With the new blob-reading approach, all tasks should succeed. let repo_path = create_versioned_rust_crate(); - let repo = Repository::open(&repo_path).expect("Failed to open repo"); - // Get the initial commit SHA (HEAD~3 in this repo's history) + // Get the initial commit SHA (HEAD~3 in this repo's history) using git CLI let initial_commit_sha = { - let head = repo.head().unwrap(); - let commit = head.peel_to_commit().unwrap(); - // Walk back 3 commits to get the initial commit - let initial_commit = commit - .parent(0) - .unwrap() - .parent(0) - .unwrap() - .parent(0) - .unwrap(); - initial_commit.id().to_string() + let output = Command::new("git") + .args(["rev-parse", "HEAD~3"]) + .current_dir(&repo_path) + .output() + .expect("Failed to get HEAD~3 commit"); + assert!(output.status.success(), "git rev-parse HEAD~3 failed"); + String::from_utf8_lossy(&output.stdout).trim().to_string() }; let main_rs_path = repo_path.join("src/main.rs"); diff --git a/src/commands/publish/mod.rs b/src/commands/publish/mod.rs index 8b1cea3e1..dfbf39e81 100644 --- a/src/commands/publish/mod.rs +++ b/src/commands/publish/mod.rs @@ -2,7 +2,6 @@ use anyhow::Context; use aws_sdk_cloudfront as cloudfront; use cargo_metadata::{DependencyKind, PackageId}; use clap::Parser; -use git2::Repository; use junit_report::{Duration, ReportBuilder, TestCase, TestSuiteBuilder}; use mime_guess; use octocrab::Octocrab; @@ -1161,13 +1160,9 @@ async fn do_publish_package(params: DoPublishParams) -> PublishResult { options.github_app_private_key.clone(), ) { result.git_tag.stdout = format!("{}\nRetrieving git HEAD", result.git_tag.stdout); - let Some(head) = Repository::open(&repo_root) + let Some(head) = gix::open(&repo_root) .ok() - .as_ref() - .and_then(|r| r.head().ok()) - .as_ref() - .and_then(|head| head.peel_to_commit().ok()) - .map(|head| head.id().to_string()) + .and_then(|r| r.head_commit().map(|commit| commit.id().to_string()).ok()) else { return Err(anyhow::Error::msg("Failed to get git HEAD")); }; @@ -1248,43 +1243,36 @@ pub async fn login(options: &Options, repo_root: &PathBuf) -> anyhow::Result<()> /// Resolves a commit SHA (or git reference) to a git tag name. /// This is used to find the GitHub release tag associated with a commit. -/// Uses git2's describe functionality for efficient exact-match tag lookup. +/// Uses git CLI's describe functionality for efficient exact-match tag lookup. /// Filters tags by the provided pattern (e.g., "v*" or "cargo-fslabscli-*") fn resolve_commit_to_tag( repo_root: &PathBuf, commit_ref: &str, tag_pattern: &str, ) -> anyhow::Result { - let repo = Repository::open(repo_root) - .with_context(|| format!("Failed to open git repository at {:?}", repo_root))?; - - // Resolve the reference to a commit - let obj = repo - .revparse_single(commit_ref) - .with_context(|| format!("Failed to resolve git reference: {}", commit_ref))?; - - // Use git2's describe with exact match option - let mut describe_options = git2::DescribeOptions::new(); - describe_options - .describe_tags() - .show_commit_oid_as_fallback(false) - .pattern(tag_pattern); - - let describe_result = obj.describe(&describe_options).with_context(|| { - format!( - "No tag matching pattern '{}' found for commit {}", - tag_pattern, commit_ref - ) - })?; - - // Format without any suffix (just the tag name) - let mut format_options = git2::DescribeFormatOptions::new(); - format_options.abbreviated_size(0); - - let tag = describe_result - .format(Some(&format_options)) - .with_context(|| "Failed to format describe result")?; + let output = std::process::Command::new("git") + .args([ + "describe", + "--tags", + "--exact-match", + &format!("--match={}", tag_pattern), + commit_ref, + ]) + .current_dir(repo_root) + .output() + .with_context(|| format!("Failed to run git describe for {}", commit_ref))?; + + if !output.status.success() { + let stderr = String::from_utf8_lossy(&output.stderr); + anyhow::bail!( + "No tag matching pattern '{}' found for commit {}: {}", + tag_pattern, + commit_ref, + stderr.trim() + ); + } + let tag = String::from_utf8_lossy(&output.stdout).trim().to_string(); Ok(tag) } @@ -1312,11 +1300,12 @@ pub async fn report_publish_to_github( let base_rev = options.base_rev.as_deref().unwrap_or("HEAD"); // Check if base_rev is already a tag name by trying to verify it exists as a tag - let repo = Repository::open(repo_root) + let repo = gix::open(repo_root) .with_context(|| format!("Failed to open git repository at {:?}", repo_root))?; - let release_tag = if let Ok(_tag_ref) = - repo.find_reference(&format!("refs/tags/{}", base_rev)) + let release_tag = if repo + .find_reference(&format!("refs/tags/{}", base_rev)) + .is_ok() { // base_rev is already a valid tag name, use it directly tracing::info!("Using {} as release tag (already a valid tag)", base_rev); @@ -1533,28 +1522,18 @@ pub async fn publish( #[cfg(test)] mod tests { - use git2::{Repository, Signature}; use std::path::PathBuf; + use std::process::Command; use super::resolve_commit_to_tag; - use crate::utils::test::{commit_all_changes, modify_file}; + use crate::utils::test::{commit_all_changes, init_repo, modify_file}; /// Helper function to create a test git repository with initial commit fn create_test_repo() -> (assert_fs::TempDir, PathBuf) { let temp_dir = assert_fs::TempDir::new().expect("Failed to create temp directory"); let repo_path = temp_dir.path().to_path_buf(); - let repo = Repository::init(&repo_path).expect("Failed to init repository"); - - // Configure Git user info (required for commits) - repo.config() - .unwrap() - .set_str("user.name", "Test User") - .unwrap(); - repo.config() - .unwrap() - .set_str("user.email", "test@example.com") - .unwrap(); + init_repo(&repo_path); // Create initial file and commit modify_file(&repo_path, "README.md", "# Test Repository"); @@ -1564,27 +1543,28 @@ mod tests { } /// Helper function to create a commit in the repository - fn create_commit(repo_path: &PathBuf, message: &str) -> git2::Oid { + fn create_commit(repo_path: &PathBuf, message: &str) -> String { // Modify a file to have something to commit modify_file(repo_path, "test.txt", &format!("Content for {}", message)); commit_all_changes(repo_path, message); - // Get the commit OID - let repo = Repository::open(repo_path).expect("Failed to open repository"); - repo.head() - .expect("Failed to get HEAD") - .target() - .expect("HEAD has no target") + // Get the commit SHA + let output = Command::new("git") + .args(["rev-parse", "HEAD"]) + .current_dir(repo_path) + .output() + .expect("Failed to get HEAD SHA"); + String::from_utf8_lossy(&output.stdout).trim().to_string() } /// Helper function to create a lightweight tag pointing to a commit - fn create_tag(repo_path: &PathBuf, tag_name: &str, commit_oid: git2::Oid) { - let repo = Repository::open(repo_path).expect("Failed to open repository"); - let commit = repo.find_commit(commit_oid).expect("Failed to find commit"); - let obj = commit.as_object(); - - repo.tag_lightweight(tag_name, obj, false) + fn create_tag(repo_path: &PathBuf, tag_name: &str, commit_sha: &str) { + let output = Command::new("git") + .args(["tag", tag_name, commit_sha]) + .current_dir(repo_path) + .output() .expect("Failed to create tag"); + assert!(output.status.success(), "git tag failed: {:?}", output); } #[test] @@ -1592,7 +1572,7 @@ mod tests { // Test: A commit with cargo-fslabscli-* tag (project's actual format) let (_temp_dir, repo_path) = create_test_repo(); let commit_oid = create_commit(&repo_path, "Add feature"); - create_tag(&repo_path, "cargo-fslabscli-2.29.1", commit_oid); + create_tag(&repo_path, "cargo-fslabscli-2.29.1", &commit_oid); let result = resolve_commit_to_tag(&repo_path, "HEAD", "cargo-fslabscli-*"); assert!(result.is_ok(), "Should successfully resolve to tag"); @@ -1604,7 +1584,7 @@ mod tests { // Test: A commit with a version tag (v-prefixed) should return that tag let (_temp_dir, repo_path) = create_test_repo(); let commit_oid = create_commit(&repo_path, "Add feature"); - create_tag(&repo_path, "v2.29.1", commit_oid); + create_tag(&repo_path, "v2.29.1", &commit_oid); let result = resolve_commit_to_tag(&repo_path, "HEAD", "v*"); assert!(result.is_ok(), "Should successfully resolve to tag"); @@ -1618,10 +1598,10 @@ mod tests { let commit_oid = create_commit(&repo_path, "Release commit"); // Create tags with different patterns - create_tag(&repo_path, "latest", commit_oid); - create_tag(&repo_path, "release-1.0.0", commit_oid); - create_tag(&repo_path, "v1.0.0", commit_oid); - create_tag(&repo_path, "stable", commit_oid); + create_tag(&repo_path, "latest", &commit_oid); + create_tag(&repo_path, "release-1.0.0", &commit_oid); + create_tag(&repo_path, "v1.0.0", &commit_oid); + create_tag(&repo_path, "stable", &commit_oid); // Should find only v-prefixed tag when pattern is "v*" let result = resolve_commit_to_tag(&repo_path, "HEAD", "v*"); @@ -1636,8 +1616,8 @@ mod tests { let commit_oid = create_commit(&repo_path, "Release commit"); // Create both tag formats - create_tag(&repo_path, "v2.0.0", commit_oid); - create_tag(&repo_path, "cargo-fslabscli-2.29.1", commit_oid); + create_tag(&repo_path, "v2.0.0", &commit_oid); + create_tag(&repo_path, "cargo-fslabscli-2.29.1", &commit_oid); // When searching for "v*", should only return v-prefixed tag let result = resolve_commit_to_tag(&repo_path, "HEAD", "v*"); @@ -1660,8 +1640,8 @@ mod tests { let (_temp_dir, repo_path) = create_test_repo(); let commit_oid = create_commit(&repo_path, "Tagged commit"); - create_tag(&repo_path, "latest", commit_oid); - create_tag(&repo_path, "stable", commit_oid); + create_tag(&repo_path, "latest", &commit_oid); + create_tag(&repo_path, "stable", &commit_oid); // Try to find v* tags when only "latest" and "stable" exist let result = resolve_commit_to_tag(&repo_path, "HEAD", "v*"); @@ -1706,7 +1686,7 @@ mod tests { let err_msg = result.unwrap_err().to_string(); // Should contain error about resolving the reference assert!( - err_msg.contains("Failed to resolve git reference"), + err_msg.contains("No tag matching pattern"), "Error message should indicate failure, got: {}", err_msg ); @@ -1717,10 +1697,9 @@ mod tests { // Test: Should be able to resolve using a commit SHA directly let (_temp_dir, repo_path) = create_test_repo(); let commit_oid = create_commit(&repo_path, "Feature commit"); - create_tag(&repo_path, "v3.0.0", commit_oid); + create_tag(&repo_path, "v3.0.0", &commit_oid); - let commit_sha = commit_oid.to_string(); - let result = resolve_commit_to_tag(&repo_path, &commit_sha, "v*"); + let result = resolve_commit_to_tag(&repo_path, &commit_oid, "v*"); assert!(result.is_ok(), "Should resolve commit SHA to tag"); assert_eq!(result.unwrap(), "v3.0.0"); } @@ -1730,10 +1709,9 @@ mod tests { // Test: Should be able to resolve using a short commit SHA let (_temp_dir, repo_path) = create_test_repo(); let commit_oid = create_commit(&repo_path, "Short SHA test"); - create_tag(&repo_path, "v4.0.0", commit_oid); + create_tag(&repo_path, "v4.0.0", &commit_oid); - let commit_sha = commit_oid.to_string(); - let short_sha = &commit_sha[..7]; // Use first 7 characters + let short_sha = &commit_oid[..7]; // Use first 7 characters let result = resolve_commit_to_tag(&repo_path, short_sha, "v*"); assert!(result.is_ok(), "Should resolve short commit SHA to tag"); assert_eq!(result.unwrap(), "v4.0.0"); @@ -1744,7 +1722,7 @@ mod tests { // Test: Should resolve HEAD reference to tag let (_temp_dir, repo_path) = create_test_repo(); let commit_oid = create_commit(&repo_path, "HEAD commit"); - create_tag(&repo_path, "v5.0.0", commit_oid); + create_tag(&repo_path, "v5.0.0", &commit_oid); let result = resolve_commit_to_tag(&repo_path, "HEAD", "v*"); assert!(result.is_ok(), "Should resolve HEAD to tag"); @@ -1755,16 +1733,18 @@ mod tests { fn test_resolve_branch_reference() { // Test: Should resolve a branch reference to tag let (_temp_dir, repo_path) = create_test_repo(); - let repo = Repository::open(&repo_path).expect("Failed to open repository"); // Create a commit and tag it let commit_oid = create_commit(&repo_path, "Branch commit"); - create_tag(&repo_path, "v6.0.0", commit_oid); + create_tag(&repo_path, "v6.0.0", &commit_oid); // Create a branch pointing to this commit - let commit = repo.find_commit(commit_oid).expect("Failed to find commit"); - repo.branch("feature-branch", &commit, false) + let output = Command::new("git") + .args(["branch", "feature-branch", &commit_oid]) + .current_dir(&repo_path) + .output() .expect("Failed to create branch"); + assert!(output.status.success(), "git branch failed: {:?}", output); let result = resolve_commit_to_tag(&repo_path, "feature-branch", "v*"); assert!(result.is_ok(), "Should resolve branch reference to tag"); @@ -1778,14 +1758,13 @@ mod tests { // Create first commit with tag let first_commit_oid = create_commit(&repo_path, "First release"); - create_tag(&repo_path, "v1.0.0", first_commit_oid); + create_tag(&repo_path, "v1.0.0", &first_commit_oid); // Create second commit (HEAD) without tag create_commit(&repo_path, "Second commit"); // Should still be able to resolve the first commit by its SHA - let commit_sha = first_commit_oid.to_string(); - let result = resolve_commit_to_tag(&repo_path, &commit_sha, "v*"); + let result = resolve_commit_to_tag(&repo_path, &first_commit_oid, "v*"); assert!(result.is_ok(), "Should resolve older commit to tag"); assert_eq!(result.unwrap(), "v1.0.0"); } @@ -1798,9 +1777,9 @@ mod tests { let commit_oid = create_commit(&repo_path, "Multi-version commit"); // Create multiple tags - create_tag(&repo_path, "v1.0.0", commit_oid); - create_tag(&repo_path, "v2.0.0", commit_oid); - create_tag(&repo_path, "v1.0.1", commit_oid); + create_tag(&repo_path, "v1.0.0", &commit_oid); + create_tag(&repo_path, "v2.0.0", &commit_oid); + create_tag(&repo_path, "v1.0.1", &commit_oid); let result = resolve_commit_to_tag(&repo_path, "HEAD", "v*"); assert!(result.is_ok(), "Should successfully resolve to tag"); @@ -1825,7 +1804,7 @@ mod tests { // First commit with tag let first_commit_oid = create_commit(&repo_path, "First release"); - create_tag(&repo_path, "v1.0.0", first_commit_oid); + create_tag(&repo_path, "v1.0.0", &first_commit_oid); // Second commit (becomes HEAD) create_commit(&repo_path, "Second commit"); @@ -1840,17 +1819,18 @@ mod tests { fn test_annotated_tag_resolution() { // Test: Should resolve annotated tags (not just lightweight tags) let (_temp_dir, repo_path) = create_test_repo(); - let repo = Repository::open(&repo_path).expect("Failed to open repository"); let commit_oid = create_commit(&repo_path, "Annotated tag commit"); - let commit = repo.find_commit(commit_oid).expect("Failed to find commit"); - let obj = commit.as_object(); // Create an annotated tag - let sig = - Signature::now("Test User", "test@example.com").expect("Failed to create signature"); - repo.tag("v7.0.0", obj, &sig, "Release v7.0.0", false) + let output = Command::new("git") + .env("GIT_COMMITTER_NAME", "Test User") + .env("GIT_COMMITTER_EMAIL", "test@example.com") + .args(["tag", "-a", "v7.0.0", "-m", "Release v7.0.0", &commit_oid]) + .current_dir(&repo_path) + .output() .expect("Failed to create annotated tag"); + assert!(output.status.success(), "git tag failed: {:?}", output); let result = resolve_commit_to_tag(&repo_path, "HEAD", "v*"); assert!(result.is_ok(), "Should resolve annotated tag"); @@ -1867,7 +1847,7 @@ mod tests { let err_msg = result.unwrap_err().to_string(); assert!( - err_msg.contains("Failed to open git repository"), + err_msg.contains("Failed to run git describe"), "Error message should mention repository opening failure, got: {}", err_msg ); diff --git a/src/crate_graph.rs b/src/crate_graph.rs index 7b71fa5f3..de7d5ec5e 100644 --- a/src/crate_graph.rs +++ b/src/crate_graph.rs @@ -1,7 +1,8 @@ #[cfg(test)] use cargo_metadata::semver::Version; use cargo_metadata::{CargoOpt, DependencyKind, Metadata, MetadataCommand, Package, PackageId}; -use git2::{DiffDelta, Object, Repository}; +use gix::Repository; +use gix::bstr::ByteSlice; use ignore::gitignore::Gitignore; use std::{ borrow::Cow, @@ -158,85 +159,90 @@ impl CrateGraph { } /// Determines which packages have changed between `old_rev` and `new_rev`. (Un)Staged changes are considered - pub fn changed_packages<'r>( + pub fn changed_packages( &self, - repository: &'r Repository, - old_commit: Object<'r>, - new_commit: Object<'r>, + repository: &Repository, + old_commit_id: gix::ObjectId, + new_commit_id: gix::ObjectId, diff_strategy: &DiffStrategy, ) -> anyhow::Result> { - // Create git diff between revisions. - let old_tree = old_commit.peel_to_tree()?; - let new_tree = new_commit.peel_to_tree()?; - - // Get index and working directory state - let index = repository.index()?; - - // Create diffs: - // - one between old_rev and new_rev, - // - and another between new_rev and current state staged - // - and another between new_rev and current state unstaged - let diff_old_new = repository.diff_tree_to_tree(Some(&old_tree), Some(&new_tree), None)?; - let diff_new_staged = repository.diff_tree_to_index(Some(&new_tree), Some(&index), None)?; - let diff_new_unstaged = repository.diff_index_to_workdir(Some(&index), None)?; - - // Check each package path against each file paths in git diff. + let old_tree = repository.find_commit(old_commit_id)?.tree()?; + let new_tree = repository.find_commit(new_commit_id)?.tree()?; + let mut changed = Vec::new(); let mut packages = self.packages().collect::>(); - // Sort packages so that we look at the subpackages first: - // - workspace/crate_a/subcrate - // - workspace/crate_a - // - workspace/crate_b - // - workspace packages.sort_by_key(|package| package_path(&self.repo_root, package).iter().count()); packages.reverse(); - // For each change, find the most specific package modified - let mut file_cb = |delta: DiffDelta, _: f32| -> bool { - for delta_path in [delta.old_file().path(), delta.new_file().path()] - .into_iter() - .flatten() - { - for package in &packages { - let package_path = package_path(&self.repo_root, package).into_owned(); - - // If package_path is ".", treat it as the entire repo - let is_repo_root = package_path == Path::new("."); + let mut record_change = |delta_path: &Path| { + for package in &packages { + let pkg_path = package_path(&self.repo_root, package).into_owned(); + let is_repo_root = pkg_path == Path::new("."); + if delta_path.ends_with("rust-toolchain.toml") { + changed.push(pkg_path.clone()); + continue; + } + if is_repo_root || delta_path.starts_with(&pkg_path) { + changed.push(pkg_path.clone()); + return; + } + } + }; - if delta_path.ends_with("rust-toolchain.toml") { - // We have a special case, the `rust-toolchain.toml` file, if it changed, everything should be considered as changed - changed.push(package_path.clone()); - continue; + // Tree-to-tree diff (gix native) + old_tree + .changes()? + .for_each_to_obtain_tree(&new_tree, |change| { + match change.location().to_path() { + Ok(path) => { + record_change(path); } - if is_repo_root - || delta_path.starts_with(&package_path) - || delta_path.ends_with("rust-toolchain.toml") - { - changed.push(package_path.clone()); - // Stop processing this change, we found the most specific package impacted - // Returning true will continue matching other file changed in case a commit targets several packages - return true; + Err(e) => { + tracing::warn!("Skipping non-UTF8 path in diff: {}", e); } } - } - true - }; - // Returning early from a callback will propagate an error for some - // reason. Ignore it. - let _ = diff_old_new.foreach(&mut file_cb, None, None, None); + Ok::<_, std::convert::Infallible>(std::ops::ControlFlow::Continue(())) + })?; - // Only check changes on staged and unstaged when not using the explicit strategy + // Staged + unstaged (git CLI, only for non-Explicit strategies) match diff_strategy { DiffStrategy::Explicit { .. } => {} _ => { - let _ = diff_new_staged.foreach(&mut file_cb, None, None, None); - let _ = diff_new_unstaged.foreach(&mut file_cb, None, None, None); + // Staged: diff new_commit tree vs current index + let output = std::process::Command::new("git") + .args([ + "diff", + "--cached", + "--name-only", + &new_commit_id.to_string(), + ]) + .current_dir(&self.repo_root) + .output()?; + if output.status.success() { + for line in String::from_utf8_lossy(&output.stdout).lines() { + if !line.is_empty() { + record_change(Path::new(line)); + } + } + } + + // Unstaged: diff index vs workdir + let output = std::process::Command::new("git") + .args(["diff", "--name-only"]) + .current_dir(&self.repo_root) + .output()?; + if output.status.success() { + for line in String::from_utf8_lossy(&output.stdout).lines() { + if !line.is_empty() { + record_change(Path::new(line)); + } + } + } } } changed.sort(); - changed.dedup(); // Remove duplicates if package changed in multiple diffs - + changed.dedup(); Ok(changed) } } @@ -559,8 +565,8 @@ pub fn find_git_root(start: impl AsRef) -> Option { #[cfg(test)] mod tests { use crate::utils::test::{ - FAKE_REGISTRY, commit_all_changes, commit_repo, create_complex_workspace, modify_file, - stage_file, + FAKE_REGISTRY, commit_all_changes, commit_repo, create_complex_workspace, init_repo, + modify_file, stage_file, }; use super::*; @@ -666,7 +672,7 @@ mod tests { fn test_detect_changed_packages() { let repo_root = initialize_repo(); let graph = CrateGraph::new(&repo_root, "", None).unwrap(); - let repo = Repository::open(repo_root).unwrap(); + let repo = gix::open(repo_root).unwrap(); // Use LocalChanges strategy to compare HEAD~ vs HEAD let diff_strategy = DiffStrategy::LocalChanges; let (base_commit, head_commit) = diff_strategy.git_commits(&repo).unwrap(); @@ -683,7 +689,7 @@ mod tests { fn test_detect_changed_package_single_rust_crate() { let repo_root = create_simple_rust_crate(); let graph = CrateGraph::new(&repo_root, "", None).unwrap(); - let repo = Repository::open(repo_root).unwrap(); + let repo = gix::open(repo_root).unwrap(); // Use LocalChanges strategy to compare HEAD~ vs HEAD let diff_strategy = DiffStrategy::LocalChanges; let (base_commit, head_commit) = diff_strategy.git_commits(&repo).unwrap(); @@ -699,7 +705,7 @@ mod tests { let repo_root = create_simple_rust_crate(); let graph = CrateGraph::new(&repo_root, "", None).unwrap(); modify_file(&repo_root, "src/lib.rs", "pub fn new_function_again() {}"); - let repo = Repository::open(repo_root).unwrap(); + let repo = gix::open(repo_root).unwrap(); let diff_strategy = DiffStrategy::WorktreeVsBranch { branch: "HEAD".to_string(), }; @@ -718,7 +724,7 @@ mod tests { modify_file(&repo_root, "src/lib.rs", "pub fn new_function_again() {}"); stage_file(&repo_root, "src/lib.rs"); - let repo = Repository::open(repo_root).unwrap(); + let repo = gix::open(repo_root).unwrap(); let diff_strategy = DiffStrategy::WorktreeVsBranch { branch: "HEAD".to_string(), }; @@ -753,18 +759,7 @@ mod tests { .into_persistent() .to_path_buf(); - let repo = Repository::init(&tmp).expect("Failed to init repo"); - - // Configure Git user info (required for commits) - repo.config() - .unwrap() - .set_str("user.name", "Test User") - .unwrap(); - repo.config() - .unwrap() - .set_str("user.email", "test@example.com") - .unwrap(); - repo.config().unwrap().set_str("gpg.sign", "false").unwrap(); + init_repo(&tmp); Command::new("cargo") .arg("init") diff --git a/src/utils/cargo.rs b/src/utils/cargo.rs index f9d9fd4a6..9ca0883a4 100644 --- a/src/utils/cargo.rs +++ b/src/utils/cargo.rs @@ -3,8 +3,6 @@ use std::fs::File; use std::io::{BufRead, BufReader}; use anyhow::Context; -use git2::build::RepoBuilder; -use git2::{Cred, FetchOptions, RemoteCallbacks}; use http_body_util::BodyExt; use http_body_util::Empty; use hyper::body::Bytes; @@ -178,22 +176,31 @@ impl CargoRegistry { let tmp = TempDir::new()?.dont_delete_on_drop(); let path = tmp.path(); - let mut builder = RepoBuilder::new(); - let mut callbacks = RemoteCallbacks::new(); - let mut fetch_options = FetchOptions::new(); + let mut cmd = std::process::Command::new("git"); + cmd.arg("clone").arg("--depth=1"); - let private_key = self.private_key.clone(); + if let Some(key) = &self.private_key { + if !key.is_file() { + anyhow::bail!("SSH key path does not exist or is not a file: {:?}", key); + } + let ssh_command = format!( + "ssh -i '{}' -o IdentitiesOnly=yes -o StrictHostKeyChecking=no", + key.display().to_string().replace("'", "'\\''") + ); + cmd.env("GIT_SSH_COMMAND", ssh_command); + } + + cmd.arg(&index).arg(path); - callbacks.credentials(move |_, u, _| match private_key.as_ref() { - Some(key) => Cred::ssh_key(u.unwrap_or("git"), None, key.as_path(), None), - None => Cred::ssh_key_from_agent(u.unwrap_or("git")), - }); - fetch_options.remote_callbacks(callbacks); - builder.fetch_options(fetch_options); - builder.clone(&index, path).map_err(|e| { + let output = cmd.output().map_err(|e| { println!("Couldn't not fetch reg: {}", e); e })?; + + if !output.status.success() { + let stderr = String::from_utf8_lossy(&output.stderr); + anyhow::bail!("git clone failed: {}", stderr); + } self.local_index_path = Some(path.to_path_buf()); Ok(()) } diff --git a/src/utils/test.rs b/src/utils/test.rs index 0eed034c9..dec1c46da 100644 --- a/src/utils/test.rs +++ b/src/utils/test.rs @@ -5,9 +5,31 @@ use std::{ process::Command, }; -use git2::{Repository, Signature}; +fn git(repo_path: &Path) -> Command { + let mut cmd = Command::new("git"); + cmd.current_dir(repo_path); + cmd.env("GIT_AUTHOR_NAME", "Test User"); + cmd.env("GIT_AUTHOR_EMAIL", "test@example.com"); + cmd.env("GIT_COMMITTER_NAME", "Test User"); + cmd.env("GIT_COMMITTER_EMAIL", "test@example.com"); + cmd +} + +pub fn init_repo(repo_path: &Path) { + let output = git(repo_path) + .arg("init") + .output() + .expect("Failed to run git init"); + assert!(output.status.success(), "git init failed: {:?}", output); -pub fn commit_all_changes(repo_path: &PathBuf, message: &str) { + let output = git(repo_path) + .args(["config", "commit.gpgsign", "false"]) + .output() + .expect("Failed to configure git"); + assert!(output.status.success()); +} + +pub fn commit_all_changes(repo_path: &Path, message: &str) { stage_all(repo_path); commit_repo(repo_path, message); } @@ -22,53 +44,28 @@ pub fn modify_file(repo_path: &Path, file_path: &str, content: &str) { std::fs::write(&full_path, content).expect("Failed to write to file"); } -pub fn stage_file(repo_path: &PathBuf, file_path: &str) { - let repo = Repository::open(repo_path).expect("Failed to open repo"); - let mut index = repo.index().unwrap(); - index - .add_all([file_path].iter(), git2::IndexAddOption::DEFAULT, None) - .expect("Failed to add files to index"); - index.write().expect("Failed to write index"); +pub fn stage_file(repo_path: &Path, file_path: &str) { + let output = git(repo_path) + .args(["add", file_path]) + .output() + .expect("Failed to run git add"); + assert!(output.status.success(), "git add failed: {:?}", output); } -pub fn stage_all(repo_path: &PathBuf) { - stage_file(repo_path, "*"); +pub fn stage_all(repo_path: &Path) { + let output = git(repo_path) + .args(["add", "-A"]) + .output() + .expect("Failed to run git add -A"); + assert!(output.status.success(), "git add -A failed: {:?}", output); } -pub fn commit_repo(repo_path: &PathBuf, commit_message: &str) { - let repo = Repository::open(repo_path).expect("Failed to open repo"); - let mut index = repo.index().unwrap(); - - let oid = index.write_tree().unwrap(); - let signature = Signature::now("Test User", "test@example.com").unwrap(); - let tree = repo.find_tree(oid).unwrap(); - let parent_commit = repo - .head() - .ok() - .and_then(|r| r.target()) - .and_then(|oid| repo.find_commit(oid).ok()); - - if let Some(parent) = parent_commit { - repo.commit( - Some("HEAD"), - &signature, - &signature, - commit_message, - &tree, - &[&parent], - ) - .unwrap(); - } else { - repo.commit( - Some("HEAD"), - &signature, - &signature, - commit_message, - &tree, - &[], - ) - .unwrap(); - }; +pub fn commit_repo(repo_path: &Path, commit_message: &str) { + let output = git(repo_path) + .args(["commit", "-m", commit_message, "--allow-empty"]) + .output() + .expect("Failed to run git commit"); + assert!(output.status.success(), "git commit failed: {:?}", output); } pub static FAKE_REGISTRY: &str = "fake-registry"; @@ -154,18 +151,7 @@ pub fn create_complex_workspace(alt_registry: bool) -> PathBuf { .into_persistent() .to_path_buf(); - let repo = Repository::init(&tmp).expect("Failed to init repo"); - - // Configure Git user info (required for commits) - repo.config() - .unwrap() - .set_str("user.name", "Test User") - .unwrap(); - repo.config() - .unwrap() - .set_str("user.email", "test@example.com") - .unwrap(); - repo.config().unwrap().set_str("gpg.sign", "false").unwrap(); + init_repo(&tmp); initialize_workspace( &tmp, @@ -242,7 +228,7 @@ pub fn create_rust_index(checksum: &str) -> PathBuf { .into_persistent() .to_path_buf(); - let _repo = Repository::init(&tmp).expect("Failed to init repo"); + init_repo(&tmp); // Create config.json let config_path = tmp.join("config.json");