diff --git a/app/src-tauri/Cargo.lock b/app/src-tauri/Cargo.lock index de2e13a..3cca5a6 100644 --- a/app/src-tauri/Cargo.lock +++ b/app/src-tauri/Cargo.lock @@ -67,6 +67,12 @@ dependencies = [ "alloc-no-stdlib", ] +[[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" @@ -82,6 +88,15 @@ version = "1.0.102" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" +[[package]] +name = "arc-swap" +version = "1.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c049c0be4daef0b145cb3555416b3b8ef5b7888a38aea1a3a155801fe7b0810b" +dependencies = [ + "rustversion", +] + [[package]] name = "argon2" version = "0.5.3" @@ -256,9 +271,32 @@ checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" [[package]] name = "autocfg" -version = "1.5.0" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" +checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" + +[[package]] +name = "aws-lc-rs" +version = "1.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b281d307588d634de920874890732659e2e7672f72b5e10e81badc1a8a83621e" +dependencies = [ + "aws-lc-sys", + "zeroize", +] + +[[package]] +name = "aws-lc-sys" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9bff6c3b54fad79a2e60b8102caf565819711497c1f5f092f49508e2f5c31b27" +dependencies = [ + "cc", + "cmake", + "dunce", + "fs_extra", + "pkg-config", +] [[package]] name = "axum" @@ -327,6 +365,12 @@ version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" +[[package]] +name = "base64" +version = "0.23.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac07cdecf99051d9a5238b80f35af32cdeba5b336e55d957b318b50137e18da5" + [[package]] name = "base64ct" version = "1.8.3" @@ -468,6 +512,17 @@ dependencies = [ "alloc-stdlib", ] +[[package]] +name = "bstr" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6bb31b46c14244e20ee9984b11bf5c992b91fb6939fea616e3512c8baecdbe5f" +dependencies = [ + "memchr", + "regex-automata", + "serde_core", +] + [[package]] name = "bumpalo" version = "3.20.2" @@ -575,6 +630,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aebf35691d1bfb0ac386a69bac2fde4dd276fb618cf8bf4f5318fe285e821bb2" dependencies = [ "find-msvc-tools", + "jobserver", + "libc", "shlex", ] @@ -641,6 +698,24 @@ dependencies = [ "inout", ] +[[package]] +name = "clru" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "197fd99cb113a8d5d9b6376f3aa817f32c1078f2343b714fff7d2ca44fdf67d5" +dependencies = [ + "hashbrown 0.16.1", +] + +[[package]] +name = "cmake" +version = "0.1.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0f78a02292a74a88ac736019ab962ece0bc380e3f977bf72e376c5d78ff0678" +dependencies = [ + "cc", +] + [[package]] name = "combine" version = "4.6.7" @@ -726,6 +801,12 @@ dependencies = [ "libc", ] +[[package]] +name = "core_detect" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f8f80099a98041a3d1622845c271458a2d73e688351bf3cb999266764b81d48" + [[package]] name = "cpufeatures" version = "0.2.17" @@ -905,6 +986,20 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "dashmap" +version = "6.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6361d5c062261c78a176addb82d4c821ae42bed6089de0e12603cd25de2059c" +dependencies = [ + "cfg-if", + "crossbeam-utils", + "hashbrown 0.14.5", + "lock_api", + "once_cell", + "parking_lot_core", +] + [[package]] name = "data-encoding" version = "2.10.0" @@ -922,6 +1017,46 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "defmt" +version = "0.3.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0963443817029b2024136fc4dd07a5107eb8f977eaf18fcd1fdeb11306b64ad" +dependencies = [ + "defmt 1.1.1", +] + +[[package]] +name = "defmt" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2953bfe4f93bbd20cc71198842756f77d161884c99ebbabc41d80231ded88d1" +dependencies = [ + "bitflags 1.3.2", + "defmt-macros", +] + +[[package]] +name = "defmt-macros" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bad9c72e7ca2137e0dc3813245a0d282fd6daad32fd800af018306a9169b5fe8" +dependencies = [ + "defmt-parser", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "defmt-parser" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10d60334b3b2e7c9d91ef8150abfb6fa4c1c39ebbcf4a81c2e346aad939fee3e" +dependencies = [ + "thiserror 2.0.18", +] + [[package]] name = "deranged" version = "0.5.8" @@ -1167,6 +1302,20 @@ version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ef6b89e5b37196644d8796de5268852ff179b44e96276cf4290264843743bb7" +[[package]] +name = "encoding_rs" +version = "0.8.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e985e0451871ad22fb8d2b6b076e2028a502a0d3950998c2c5c0a4f9b5d9679" +dependencies = [ + "cfg-if", + "core_detect", + "multiversion_no_op", + "rustversion", + "scopeguard", + "simdutf8", +] + [[package]] name = "endi" version = "1.1.1" @@ -1243,10 +1392,22 @@ dependencies = [ ] [[package]] -name = "fastrand" -version = "2.3.0" +name = "faster-hex" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" +checksum = "04839bdf9d8c10f66806fad16b852fc72aab80873aebc3cb69d85b4fa41543ed" +dependencies = [ + "autocfg", + "defmt 0.3.100", + "heapless", + "serde", +] + +[[package]] +name = "fastrand" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da7c62ceae207dd37ea5b845da6a0696c799f85e97da1ab5b7910be3c1c80223" [[package]] name = "fdeflate" @@ -1279,13 +1440,12 @@ dependencies = [ [[package]] name = "filetime" -version = "0.2.27" +version = "0.2.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f98844151eee8917efc50bd9e8318cb963ae8b297431495d3f758616ea5c57db" +checksum = "5c287a33c7f0a620c38e641e7f60827713987b3c0f26e8ddc9462cc69cf75759" dependencies = [ "cfg-if", "libc", - "libredox", ] [[package]] @@ -1358,6 +1518,12 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "fs_extra" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" + [[package]] name = "futf" version = "0.1.5" @@ -1375,6 +1541,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" dependencies = [ "futures-core", + "futures-sink", ] [[package]] @@ -1675,6 +1842,810 @@ dependencies = [ "winapi", ] +[[package]] +name = "gix" +version = "0.88.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32d6dd6028308ad2f2662207506e396b97f9ec899f74bd537c0775248c774f21" +dependencies = [ + "gix-actor", + "gix-attributes", + "gix-command", + "gix-commitgraph", + "gix-config", + "gix-credentials", + "gix-date", + "gix-diff", + "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-note", + "gix-object", + "gix-odb", + "gix-pack", + "gix-path", + "gix-pathspec", + "gix-prompt", + "gix-protocol", + "gix-quote", + "gix-ref", + "gix-refspec", + "gix-revision", + "gix-revwalk", + "gix-sec", + "gix-shallow", + "gix-submodule", + "gix-tempfile", + "gix-trace", + "gix-transport", + "gix-traverse", + "gix-url", + "gix-utils", + "gix-validate", + "gix-worktree", + "gix-worktree-stream", + "gix-zlib", + "nonempty", + "smallvec", +] + +[[package]] +name = "gix-actor" +version = "0.43.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e046e9a929e8e1f40f9a34f736408c4c079f9cf004429f3e718d270e847be96" +dependencies = [ + "bstr", + "gix-date", + "gix-error", +] + +[[package]] +name = "gix-attributes" +version = "0.36.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3d57bcf18768dc259868e335fae99a9063174178b78b00348603f0791cd780a" +dependencies = [ + "bstr", + "gix-error", + "gix-features", + "gix-glob", + "gix-path", + "gix-quote", + "gix-trace", + "smallvec", + "unicode-bom", +] + +[[package]] +name = "gix-bitmap" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e9d28a8333a322df5c4fdc7b86a9928965e50794523c0edff57d1fbf2a520cf" +dependencies = [ + "gix-error", +] + +[[package]] +name = "gix-chunk" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25303d07ce2b8f98aab48ce4251317d785a3208b7ed65698928e4a2f31889936" +dependencies = [ + "gix-error", +] + +[[package]] +name = "gix-command" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb3c2e61f97adc08764060e9becb0c819d8f2ba27896434710e039ae57b1d2a7" +dependencies = [ + "bstr", + "gix-error", + "gix-path", + "gix-quote", + "gix-trace", +] + +[[package]] +name = "gix-commitgraph" +version = "0.40.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0462cbffd5828447f376559d397d9b29f1979dac3d980b5212b17fa174b174eb" +dependencies = [ + "bstr", + "gix-chunk", + "gix-error", + "gix-hash", + "memmap2", + "nonempty", +] + +[[package]] +name = "gix-config" +version = "0.61.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb2cb454dce2f1895cf9def1df8b0a6bbc579d86d8313bdf60f1ab429d52df12" +dependencies = [ + "bstr", + "gix-config-value", + "gix-error", + "gix-features", + "gix-glob", + "gix-path", + "gix-ref", + "gix-sec", + "gix-utils", + "smallvec", + "unicode-bom", +] + +[[package]] +name = "gix-config-value" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1894e816c774650b5157d965853b50d7e8f7137ea74055a6bac2f2ec051cf997" +dependencies = [ + "bitflags 2.11.0", + "bstr", + "gix-error", + "gix-path", + "libc", +] + +[[package]] +name = "gix-credentials" +version = "0.41.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3a9965e5bd5a1d75e835e4765e3900c0614878c9c9199272d6088640fecacb8" +dependencies = [ + "bstr", + "gix-command", + "gix-config-value", + "gix-date", + "gix-error", + "gix-path", + "gix-prompt", + "gix-quote", + "gix-sec", + "gix-trace", + "gix-url", +] + +[[package]] +name = "gix-date" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb8beceb16a77fb222923592864b94beb4ad4c2b175a7055c2cac6815ee0370a" +dependencies = [ + "bstr", + "gix-error", + "itoa", + "jiff", +] + +[[package]] +name = "gix-diff" +version = "0.68.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "939aee7bbc9f233de858839e865c82886e0baceb8f1fe792ea1703fa895f09e1" +dependencies = [ + "bstr", + "gix-error", + "gix-hash", + "gix-object", +] + +[[package]] +name = "gix-discover" +version = "0.56.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4476ba055ec5fbb05f59dd98380b596428da060beac5f9dccd7374302ad1fbfb" +dependencies = [ + "bstr", + "dunce", + "gix-error", + "gix-fs", + "gix-path", + "gix-ref", + "gix-sec", +] + +[[package]] +name = "gix-error" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c4a5c4cd326ddfe7f046ad9eed05461c59af6ea98b681648bac73954317addd" +dependencies = [ + "bstr", +] + +[[package]] +name = "gix-features" +version = "0.50.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb3a430dbc02d6c5e30a9f92083459593495ea22a476e837d248b22d800eed80" +dependencies = [ + "bytes", + "crc32fast", + "crossbeam-channel", + "gix-path", + "gix-trace", + "gix-utils", + "libc", + "once_cell", + "parking_lot", + "prodash", + "walkdir", +] + +[[package]] +name = "gix-filter" +version = "0.35.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f31a39e2c9b04843709d1f72b2c39c98dc0ee591e2f67d21302dbfbd06d2956" +dependencies = [ + "bstr", + "encoding_rs", + "gix-attributes", + "gix-command", + "gix-error", + "gix-hash", + "gix-object", + "gix-packetline", + "gix-path", + "gix-quote", + "gix-trace", + "gix-utils", + "smallvec", +] + +[[package]] +name = "gix-fs" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0105ab1cef4542d092c27766d2daf1548fcbad7e460dc2208642c120bb19b3ad" +dependencies = [ + "bstr", + "gix-error", + "gix-features", + "gix-path", + "gix-utils", +] + +[[package]] +name = "gix-glob" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bba50803dea16646cafb9a2d67e95b8cdf5b21c7e8c45bcd18f38bafe02fb81f" +dependencies = [ + "bitflags 2.11.0", + "bstr", + "gix-features", + "gix-path", + "gix-utils", +] + +[[package]] +name = "gix-hash" +version = "0.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a22b6febaaee8546eb0c9dd21ecdd93aca269a6cb3897e1250f0f60933c0174" +dependencies = [ + "faster-hex", + "gix-error", + "gix-features", + "sha1dc", +] + +[[package]] +name = "gix-hashtable" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c55bff2f32f6f5a95bdd43e5e5e01b0656250cffbc48eda19db0afa3d73adf2f" +dependencies = [ + "gix-hash", + "hashbrown 0.17.1", + "parking_lot", +] + +[[package]] +name = "gix-ignore" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5bfd910e271ecd1a20e4df4009a0d9d844a0c51ee5444aaa3fadd1b1b077930b" +dependencies = [ + "bstr", + "gix-glob", + "gix-path", + "gix-trace", + "unicode-bom", +] + +[[package]] +name = "gix-index" +version = "0.56.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfd5b484b5b4e218a5ac114f0982949f9bd7582ff67986665a0cf85895332433" +dependencies = [ + "bitflags 2.11.0", + "bstr", + "filetime", + "fnv", + "gix-bitmap", + "gix-error", + "gix-features", + "gix-fs", + "gix-hash", + "gix-lock", + "gix-object", + "gix-traverse", + "gix-utils", + "gix-validate", + "hashbrown 0.17.1", + "itoa", + "libc", + "memmap2", + "rustix", + "smallvec", +] + +[[package]] +name = "gix-lock" +version = "25.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd5ddfd0cc476fef744ac544783b96a9ff5d49f8de541e6e9d6d6002aa90720" +dependencies = [ + "gix-error", + "gix-tempfile", + "gix-utils", +] + +[[package]] +name = "gix-macros" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f11f5429ca73cc3758c9f53af56a9fba100a8701c5b4bc30e29b8b0b15288991" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "gix-negotiate" +version = "0.36.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72c2c0f64b64f435862c73a7f14767547781d7ca145aa9b273d12c6e55b55dd9" +dependencies = [ + "bitflags 2.11.0", + "gix-commitgraph", + "gix-date", + "gix-error", + "gix-hash", + "gix-object", + "gix-revwalk", +] + +[[package]] +name = "gix-note" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d6879cc9e5a9a0429e40b05970af6c8dad8734098a3142fec23e32eeb567580" +dependencies = [ + "gix-error", + "gix-hash", + "gix-object", +] + +[[package]] +name = "gix-object" +version = "0.65.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a72909eeeed845721a143ef3dbeb4d6cd5df79c839fe7eefc138cb4f982eba3e" +dependencies = [ + "bstr", + "gix-actor", + "gix-command", + "gix-date", + "gix-error", + "gix-features", + "gix-hash", + "gix-hashtable", + "gix-path", + "gix-tempfile", + "gix-utils", + "gix-validate", + "itoa", + "smallvec", +] + +[[package]] +name = "gix-odb" +version = "0.85.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5794b85ff563a290ea18867fa1f0a5b0b84709f2f34dddbb3614556fbe88c629" +dependencies = [ + "arc-swap", + "gix-error", + "gix-features", + "gix-fs", + "gix-hash", + "gix-hashtable", + "gix-object", + "gix-pack", + "gix-path", + "gix-quote", + "gix-zlib", + "memmap2", + "parking_lot", + "tempfile", +] + +[[package]] +name = "gix-pack" +version = "0.75.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0451a48faef8e0e7a8f6d903c8329c7d18a793b3030907ca841f889dc695d1df" +dependencies = [ + "clru", + "gix-chunk", + "gix-error", + "gix-features", + "gix-hash", + "gix-hashtable", + "gix-object", + "gix-path", + "gix-tempfile", + "gix-zlib", + "memmap2", + "parking_lot", + "smallvec", +] + +[[package]] +name = "gix-packetline" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ba540112a3fcc6388eb2aa4dc4837040478c285a35178cd9a44b07345cd6636" +dependencies = [ + "bstr", + "faster-hex", + "gix-error", + "gix-trace", +] + +[[package]] +name = "gix-path" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd051ac60c5dbce2228daabf25b6a76408ac732d416c678cfa0a4914d13f60b8" +dependencies = [ + "bstr", + "gix-error", + "gix-trace", + "gix-validate", +] + +[[package]] +name = "gix-pathspec" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ee883ab64bad641bf31ff338044edd7aa010605f19e866d98c9164e635c38b1" +dependencies = [ + "bitflags 2.11.0", + "bstr", + "gix-attributes", + "gix-config-value", + "gix-error", + "gix-glob", + "gix-path", +] + +[[package]] +name = "gix-prompt" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "057ae013425704c652a8abebc8660f45e9b0175b5b40ae72516732241ad6ba94" +dependencies = [ + "gix-command", + "gix-config-value", + "gix-error", + "parking_lot", + "rustix", +] + +[[package]] +name = "gix-protocol" +version = "0.66.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "331ac489c461790b6b692afb322085c42041c6ff7621c30ac3dbb00b12e05e89" +dependencies = [ + "bstr", + "gix-credentials", + "gix-date", + "gix-error", + "gix-features", + "gix-hash", + "gix-lock", + "gix-macros", + "gix-negotiate", + "gix-object", + "gix-ref", + "gix-refspec", + "gix-revwalk", + "gix-shallow", + "gix-trace", + "gix-transport", + "gix-utils", + "nonempty", +] + +[[package]] +name = "gix-quote" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6b040cfa7af50f131bfa34119cceee804c3cc40c766daaba8e0c8149340aea7" +dependencies = [ + "bstr", + "gix-error", + "gix-utils", +] + +[[package]] +name = "gix-ref" +version = "0.68.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c90ceeec67de03b737c30b1b69116ca56fec803edde022f3d25181f0f597083d" +dependencies = [ + "gix-actor", + "gix-error", + "gix-features", + "gix-fs", + "gix-hash", + "gix-lock", + "gix-object", + "gix-path", + "gix-tempfile", + "gix-utils", + "gix-validate", + "memmap2", +] + +[[package]] +name = "gix-refspec" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4caa89a034a055c0578150092d3edb2f1c3f74cca86b883263a28caa26c5812" +dependencies = [ + "bstr", + "gix-error", + "gix-hash", + "gix-validate", + "smallvec", +] + +[[package]] +name = "gix-revision" +version = "0.50.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8438d38e197fa3ea7bb297302db1701879a64a343f23a3d150a602a609a8751" +dependencies = [ + "bstr", + "gix-commitgraph", + "gix-date", + "gix-error", + "gix-hash", + "gix-object", + "gix-revwalk", + "nonempty", +] + +[[package]] +name = "gix-revwalk" +version = "0.36.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fa6d913e7cf8c6407d3954f9794b4b148c2113c672c34b63c59ea654a1e060e" +dependencies = [ + "gix-commitgraph", + "gix-date", + "gix-error", + "gix-hash", + "gix-hashtable", + "gix-object", + "smallvec", +] + +[[package]] +name = "gix-sec" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d8eb983a830ba586b61f7005deaf44609e1a4186cbf13a0e8668154ca910461" +dependencies = [ + "bitflags 2.11.0", + "gix-path", + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "gix-shallow" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "119bae1301715df40e8fd136deeff4fe8a3be4cfc001ed5e72bad7b47ebbc090" +dependencies = [ + "bstr", + "gix-error", + "gix-hash", + "gix-lock", + "nonempty", +] + +[[package]] +name = "gix-submodule" +version = "0.35.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b498192e5f89453fbd728263d52088aeec634a390e732f1da6d0a491c552091" +dependencies = [ + "bstr", + "gix-config", + "gix-error", + "gix-path", + "gix-pathspec", + "gix-refspec", + "gix-url", +] + +[[package]] +name = "gix-tempfile" +version = "25.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3dd7dd8df3345933caddd6ce67821c9eec17192564f1011cf0bd0b8cb0d07ffb" +dependencies = [ + "dashmap", + "gix-fs", + "libc", + "parking_lot", + "tempfile", + "windows-sys 0.61.2", +] + +[[package]] +name = "gix-trace" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df45b2cf6052d9e52681130c17d67505b023e8933ad974f94a2be0b0d24f636a" + +[[package]] +name = "gix-transport" +version = "0.60.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d2beb7b0387bbf4d7c13763c6e168f9957319aa811f09349dc59601be3b2951" +dependencies = [ + "base64 0.22.1", + "bstr", + "gix-command", + "gix-credentials", + "gix-error", + "gix-features", + "gix-packetline", + "gix-path", + "gix-quote", + "gix-sec", + "gix-url", + "parking_lot", + "reqwest 0.13.5", +] + +[[package]] +name = "gix-traverse" +version = "0.62.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd4864934e4efd92082ecc37107bce0c389b58d82888db634817f9c711efb7ed" +dependencies = [ + "bitflags 2.11.0", + "gix-commitgraph", + "gix-date", + "gix-error", + "gix-hash", + "gix-hashtable", + "gix-object", + "gix-revwalk", + "smallvec", +] + +[[package]] +name = "gix-url" +version = "0.39.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f30fe2a228d7de5f9d582463d9b724debcb870162d9fbe908cd5d76ccdecf0d" +dependencies = [ + "bstr", + "gix-error", + "gix-path", + "gix-utils", + "percent-encoding", +] + +[[package]] +name = "gix-utils" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70525766f53426ca4cd2ea4d58b0b31488dfedb416b3254e2efdd9dc184cc880" +dependencies = [ + "bstr", + "fastrand", + "getrandom 0.4.1", + "unicode-normalization", +] + +[[package]] +name = "gix-validate" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30574f81fc41b2ca3582ece2bdf8696bdfe9c170aa7e8bcafb81199c86b1ceea" +dependencies = [ + "bstr", + "gix-error", +] + +[[package]] +name = "gix-worktree" +version = "0.57.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04fa19b9b3961aafb3b9e60005b9bcef0100f8b211f46ab5269a6ac896073780" +dependencies = [ + "bstr", + "gix-attributes", + "gix-error", + "gix-fs", + "gix-glob", + "gix-hash", + "gix-ignore", + "gix-index", + "gix-object", + "gix-path", + "gix-validate", +] + +[[package]] +name = "gix-worktree-stream" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2021e1609beae0edbeefa76ac641be0571b725afacb77ac5b12f33b436e0d3d9" +dependencies = [ + "gix-attributes", + "gix-error", + "gix-features", + "gix-filter", + "gix-fs", + "gix-hash", + "gix-object", + "gix-path", + "gix-traverse", + "parking_lot", +] + +[[package]] +name = "gix-zlib" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44a1a9c82ec5b712082566cbef8f5782472a0d3973132911e5a83380a5298fe2" +dependencies = [ + "gix-error", + "zlib-rs", +] + [[package]] name = "glib" version = "0.18.5" @@ -1791,12 +2762,46 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "h2" +version = "0.4.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef8e5e5a340588f4452631496976cf8636d4a7ecf600239fdc27615d2530bc16" +dependencies = [ + "atomic-waker", + "bytes", + "fnv", + "futures-core", + "futures-sink", + "http", + "indexmap 2.13.0", + "slab", + "tokio", + "tokio-util", + "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" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" + [[package]] name = "hashbrown" version = "0.15.5" @@ -1811,6 +2816,32 @@ name = "hashbrown" version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash 0.2.0", +] + +[[package]] +name = "hashbrown" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" +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" @@ -1913,6 +2944,7 @@ dependencies = [ "bytes", "futures-channel", "futures-core", + "h2", "http", "http-body", "httparse", @@ -2280,6 +3312,60 @@ dependencies = [ "system-deps", ] +[[package]] +name = "jiff" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ab1baf72f08796de0260609515130699b890ac25f30e610ad894bc5856cafdb" +dependencies = [ + "defmt 1.1.1", + "jiff-core", + "jiff-static", + "jiff-tzdb-platform", + "log", + "portable-atomic", + "portable-atomic-util", + "serde_core", + "windows-link 0.2.1", +] + +[[package]] +name = "jiff-core" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e52fe76043ccecc9005d2305ebaadf7d7fc0cc89ca6baa10a94d6bc68c7128c" +dependencies = [ + "defmt 1.1.1", + "log", +] + +[[package]] +name = "jiff-static" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "378268a1116ad67ae6228701118ac9f491d78fda38a40a1f1a9e1348de6f7212" +dependencies = [ + "jiff-core", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "jiff-tzdb" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "142bd39932ad231f10513df9ab62661fead8719872150b7ad02a2df79f4e141e" + +[[package]] +name = "jiff-tzdb-platform" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "875a5a69ac2bab1a891711cf5eccbec1ce0341ea805560dcd90b7a2e925132e8" +dependencies = [ + "jiff-tzdb", +] + [[package]] name = "jni" version = "0.21.1" @@ -2289,19 +3375,78 @@ dependencies = [ "cesu8", "cfg-if", "combine", - "jni-sys", + "jni-sys 0.3.0", "log", "thiserror 1.0.69", "walkdir", "windows-sys 0.45.0", ] +[[package]] +name = "jni" +version = "0.22.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5efd9a482cf3a427f00d6b35f14332adc7902ce91efb778580e180ff90fa3498" +dependencies = [ + "cfg-if", + "combine", + "jni-macros", + "jni-sys 0.4.1", + "log", + "simd_cesu8", + "thiserror 2.0.18", + "walkdir", + "windows-link 0.2.1", +] + +[[package]] +name = "jni-macros" +version = "0.22.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a00109accc170f0bdb141fed3e393c565b6f5e072365c3bd58f5b062591560a3" +dependencies = [ + "proc-macro2", + "quote", + "rustc_version", + "simd_cesu8", + "syn 2.0.117", +] + [[package]] name = "jni-sys" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" +[[package]] +name = "jni-sys" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6377a88cb3910bee9b0fa88d4f42e1d2da8e79915598f65fb0c7ee14c878af2" +dependencies = [ + "jni-sys-macros", +] + +[[package]] +name = "jni-sys-macros" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38c0b942f458fe50cdac086d2f946512305e5631e720728f2a61aabcd47a6264" +dependencies = [ + "quote", + "syn 2.0.117", +] + +[[package]] +name = "jobserver" +version = "0.1.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c00acbd29eabad4a2392fa0e921c874934dbbf4194312ad20f04a0ed67a3cb3" +dependencies = [ + "getrandom 0.4.1", + "libc", +] + [[package]] name = "js-sys" version = "0.3.90" @@ -2404,9 +3549,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.182" +version = "0.2.189" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6800badb6cb2082ffd7b6a67e6125bb39f18782f793520caee8cb8846be06112" +checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2" [[package]] name = "libdbus-sys" @@ -2435,7 +3580,6 @@ checksum = "3d0b95e02c851351f877147b7deea7b1afb1df71b63aa5f8270716e0c5720616" dependencies = [ "bitflags 2.11.0", "libc", - "redox_syscall 0.7.2", ] [[package]] @@ -2552,6 +3696,15 @@ version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" +[[package]] +name = "memmap2" +version = "0.9.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1219ed1b7f229ee7104d281dd01d6802fe28bb6e95d292942c4daacdeb798c0" +dependencies = [ + "libc", +] + [[package]] name = "memoffset" version = "0.9.1" @@ -2629,6 +3782,12 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "multiversion_no_op" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "743fb55ba31b18fb1ecef6bdc9aa2743314978ac084044301a7eee33fb99a20d" + [[package]] name = "ndk" version = "0.9.0" @@ -2636,7 +3795,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3f42e7bbe13d351b6bead8286a43aac9534b82bd3cc43e47037f012ebfd62d4" dependencies = [ "bitflags 2.11.0", - "jni-sys", + "jni-sys 0.3.0", "log", "ndk-sys", "num_enum", @@ -2650,7 +3809,7 @@ version = "0.6.0+11769913" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ee6cda3051665f1fb8d9e08fc35c96d5a244fb1be711a03b71118828afc9a873" dependencies = [ - "jni-sys", + "jni-sys 0.3.0", ] [[package]] @@ -2694,6 +3853,12 @@ version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb" +[[package]] +name = "nonempty" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9737e026353e5cd0736f98eddae28665118eb6f6600902a7f50db585621fecb6" + [[package]] name = "num-conv" version = "0.2.0" @@ -2929,9 +4094,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.21.3" +version = "1.21.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" [[package]] name = "opaque-debug" @@ -2951,6 +4116,12 @@ dependencies = [ "pathdiff", ] +[[package]] +name = "openssl-probe" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" + [[package]] name = "option-ext" version = "0.2.0" @@ -3016,7 +4187,7 @@ checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" dependencies = [ "cfg-if", "libc", - "redox_syscall 0.5.18", + "redox_syscall", "smallvec", "windows-link 0.2.1", ] @@ -3325,6 +4496,21 @@ dependencies = [ "universal-hash", ] +[[package]] +name = "portable-atomic" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05c8b63e8d9609db387f0324918f81d68fe27748f084ef092fb35954d0539a85" + +[[package]] +name = "portable-atomic-util" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10ab3eb7f3becc3a1cbc4f2c6f20267996cfc1a6467a873763411b136a122715" +dependencies = [ + "portable-atomic", +] + [[package]] name = "potential_utf" version = "0.1.4" @@ -3455,6 +4641,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 = "pxfm" version = "0.1.27" @@ -3499,6 +4694,7 @@ version = "0.11.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f1906b49b0c3bc04b5fe5d86a77925ae6524a19b816ae38ce1e426255f1d8a31" dependencies = [ + "aws-lc-rs", "bytes", "getrandom 0.3.4", "lru-slab", @@ -3668,15 +4864,6 @@ dependencies = [ "bitflags 2.11.0", ] -[[package]] -name = "redox_syscall" -version = "0.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d94dd2f7cd932d4dc02cc8b2b50dfd38bd079a4e5d79198b99743d7fcf9a4b4" -dependencies = [ - "bitflags 2.11.0", -] - [[package]] name = "redox_users" version = "0.5.2" @@ -3779,27 +4966,37 @@ dependencies = [ [[package]] name = "reqwest" -version = "0.13.2" +version = "0.13.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab3f43e3283ab1488b624b44b0e988d0acea0b3214e694730a055cb6b2efa801" +checksum = "16a1cfa75cc186dd73d5818e510e042e40927bccc9c236b061cea97e1eb08029" dependencies = [ - "base64 0.22.1", + "base64 0.23.1", "bytes", + "encoding_rs", + "futures-channel", "futures-core", "futures-util", + "h2", "http", "http-body", "http-body-util", "hyper", + "hyper-rustls", "hyper-util", "js-sys", "log", + "mime", "percent-encoding", "pin-project-lite", + "quinn", + "rustls", + "rustls-pki-types", + "rustls-platform-verifier", "serde", "serde_json", "sync_wrapper", "tokio", + "tokio-rustls", "tokio-util", "tower", "tower-http", @@ -3883,6 +5080,7 @@ version = "0.23.37" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "758025cb5fccfd3bc2fd74708fd4682be41d99e5dff73c377c0646c6012c73a4" dependencies = [ + "aws-lc-rs", "once_cell", "ring", "rustls-pki-types", @@ -3891,6 +5089,18 @@ dependencies = [ "zeroize", ] +[[package]] +name = "rustls-native-certs" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dab5152771c58876a2146916e53e35057e1a4dfa2b9df0f0305b07f611fdea4d" +dependencies = [ + "openssl-probe", + "rustls-pki-types", + "schannel", + "security-framework 3.7.0", +] + [[package]] name = "rustls-pki-types" version = "1.14.0" @@ -3901,12 +5111,40 @@ dependencies = [ "zeroize", ] +[[package]] +name = "rustls-platform-verifier" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1167586491e2b18b8bfbb293e8180ec17c201c4f076d7cb3070ca964e7598f98" +dependencies = [ + "core-foundation 0.10.1", + "core-foundation-sys", + "jni 0.22.4", + "log", + "once_cell", + "rustls", + "rustls-native-certs", + "rustls-platform-verifier-android", + "rustls-webpki", + "security-framework 3.7.0", + "security-framework-sys", + "webpki-root-certs", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustls-platform-verifier-android" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eec689c0bc40ff2458a5977b6619cb718087084a18e02a131c599b62d05e1a5f" + [[package]] name = "rustls-webpki" version = "0.103.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d7df23109aa6c1567d1c575b9952556388da57401e4ace1d15f79eedad0d8f53" dependencies = [ + "aws-lc-rs", "ring", "rustls-pki-types", "untrusted", @@ -3933,6 +5171,15 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "schannel" +version = "0.1.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91c1b7e4904c873ef0710c1f407dde2e6287de2bebc1bbbf7d430bb7cbffd939" +dependencies = [ + "windows-sys 0.61.2", +] + [[package]] name = "schemars" version = "0.8.22" @@ -4274,6 +5521,12 @@ dependencies = [ "digest", ] +[[package]] +name = "sha1dc" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "983e970f506614c430f713c2da211ae1e3b9b71ca695a3c290bce7ccf0a87da8" + [[package]] name = "sha2" version = "0.10.9" @@ -4307,6 +5560,22 @@ version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e320a6c5ad31d271ad523dcf3ad13e2767ad8b1cb8f047f75a8aeaf8da139da2" +[[package]] +name = "simd_cesu8" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11031e251abf8611c80f460e19dbdeb54a66db918e49c65a7065b46ac7aec520" +dependencies = [ + "rustc_version", + "simdutf8", +] + +[[package]] +name = "simdutf8" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" + [[package]] name = "siphasher" version = "0.3.11" @@ -4356,7 +5625,7 @@ dependencies = [ "objc2-foundation", "objc2-quartz-core", "raw-window-handle", - "redox_syscall 0.5.18", + "redox_syscall", "tracing", "wasm-bindgen", "web-sys", @@ -4540,7 +5809,7 @@ dependencies = [ "gdkwayland-sys", "gdkx11-sys", "gtk", - "jni", + "jni 0.21.1", "libc", "log", "ndk", @@ -4608,7 +5877,7 @@ dependencies = [ "heck 0.5.0", "http", "image", - "jni", + "jni 0.21.1", "libc", "log", "mime", @@ -4621,7 +5890,7 @@ dependencies = [ "percent-encoding", "plist", "raw-window-handle", - "reqwest 0.13.2", + "reqwest 0.13.5", "serde", "serde_json", "serde_repr", @@ -4795,7 +6064,7 @@ dependencies = [ "dpi", "gtk", "http", - "jni", + "jni 0.21.1", "objc2", "objc2-ui-kit", "objc2-web-kit", @@ -4818,7 +6087,7 @@ checksum = "2cadb13dad0c681e1e0a2c49ae488f0e2906ded3d57e7a0017f4aaf46e387117" dependencies = [ "gtk", "http", - "jni", + "jni 0.21.1", "log", "objc2", "objc2-app-kit", @@ -5289,6 +6558,7 @@ dependencies = [ "dirs", "fern", "futures-util", + "gix", "iana-time-zone", "include_dir", "keyring", @@ -5304,6 +6574,7 @@ dependencies = [ "tauri-build", "tauri-plugin-dialog", "tauri-plugin-opener", + "tempfile", "tokio", "tower-http", "url", @@ -5404,12 +6675,27 @@ version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dbc4bc3a9f746d862c45cb89d705aa10f187bb96c76001afab07a0d35ce60142" +[[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.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" +[[package]] +name = "unicode-normalization" +version = "0.1.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fd4f6878c9cb28d874b009da9e8d183b5abc80117c40bbd187a1fde336be6e8" +dependencies = [ + "tinyvec", +] + [[package]] name = "unicode-segmentation" version = "1.12.0" @@ -5750,6 +7036,15 @@ dependencies = [ "system-deps", ] +[[package]] +name = "webpki-root-certs" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b96554aa2acc8ccdb7e1c9a58a7a68dd5d13bccc69cd124cb09406db612a1c9b" +dependencies = [ + "rustls-pki-types", +] + [[package]] name = "webpki-roots" version = "1.0.6" @@ -6369,7 +7664,7 @@ dependencies = [ "gtk", "http", "javascriptcore-rs", - "jni", + "jni 0.21.1", "libc", "ndk", "objc2", @@ -6590,6 +7885,12 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "zlib-rs" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b268e58e7c693d7c271f93ffc4ba3b380412554231c85bf61ca7af91042a4112" + [[package]] name = "zmij" version = "1.0.21" diff --git a/app/src-tauri/Cargo.toml b/app/src-tauri/Cargo.toml index 040e219..6a70f4e 100644 --- a/app/src-tauri/Cargo.toml +++ b/app/src-tauri/Cargo.toml @@ -43,11 +43,15 @@ zeroize = "1" # container. Already in the tree transitively (reqwest), and the point of # using it rather than hand-rolling is parity with the frontend's `new URL()`. url = "2" +# Marketplace repos are fetched on the host into a bare cache (spec §3). +# Blocking client + rustls: no git binary or OpenSSL needed on the host. +gix = { version = "0.88", default-features = false, features = ["blocking-network-client", "blocking-http-transport-reqwest-rust-tls", "credentials", "sha1"] } [dev-dependencies] # `test-util` (not part of tokio's `full`) lets the auto-start retry tests run # their backoff schedule under a paused clock instead of in real seconds. tokio = { version = "1", features = ["full", "test-util"] } +tempfile = "3" [build-dependencies] tauri-build = { version = "2", features = [] } diff --git a/app/src-tauri/src/marketplace/git.rs b/app/src-tauri/src/marketplace/git.rs new file mode 100644 index 0000000..bf39be8 --- /dev/null +++ b/app/src-tauri/src/marketplace/git.rs @@ -0,0 +1,648 @@ +//! The marketplace cache: one bare `gix` repository per marketplace. +//! +//! Everything here is blocking — call it from `tokio::task::spawn_blocking`. +//! Credentials are handed to gix through its credential callback for the +//! duration of one fetch and are never written to disk or into the repo +//! config. + +use std::path::{Path, PathBuf}; +use std::sync::atomic::AtomicBool; + +/// The ref the fetched branch tip is stored under. +pub const HEAD_REF: &str = "refs/triple-c/head"; +/// Prefix of the refs that keep pinned commits alive. +pub const PIN_PREFIX: &str = "refs/triple-c/pins/"; + +#[derive(Clone)] +pub struct Credential { + pub username: String, + pub password: String, +} + +impl std::fmt::Debug for Credential { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("Credential") + .field("username", &self.username) + .field("password", &"") + .finish() + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum FetchError { + /// 401 / 403, or gix's "credentials … were not accepted" / "no + /// credentials were returned" (anonymous fetch of a private repo). + Auth { + status: u16, + }, + /// 404 / "repository not found". + NotFound, + Network(String), + Other(String), +} + +impl std::fmt::Display for FetchError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + FetchError::Auth { status } => write!(f, "access denied (HTTP {})", status), + FetchError::NotFound => write!(f, "repository not found"), + FetchError::Network(m) => write!(f, "network error: {}", m), + FetchError::Other(m) => write!(f, "{}", m), + } + } +} + +/// Classify a gix error by its Debug-formatted chain. gix wraps transport +/// errors several layers deep and some layers are not `std::error::Error`, +/// so the text is the one stable thing to match on. +pub fn classify_fetch_error(chain: &str) -> FetchError { + let lower = chain.to_ascii_lowercase(); + if lower.contains("http status 401") + || lower.contains("not accepted by the remote") + // GitHub and GitLab answer an anonymous fetch of a private (or + // missing) repo with a credential challenge; with no credential + // callback result gix reports this (pre-flight F2). + || lower.contains("no credentials were returned") + { + return FetchError::Auth { status: 401 }; + } + if lower.contains("http status 403") { + return FetchError::Auth { status: 403 }; + } + if lower.contains("http status 404") || lower.contains("repository not found") { + return FetchError::NotFound; + } + const NETWORK: &[&str] = &[ + "dns error", + "resolving dns", + "failed to lookup address", + "connection refused", + "connection reset", + "timed out", + "timeout", + "network is unreachable", + "no route to host", + "error sending request", + "tcp connect error", + ]; + if NETWORK.iter().any(|needle| lower.contains(needle)) { + // The outermost line is a generic "Transport handshake failed"; the + // innermost `└─` line names the actual cause. + let cause = chain + .lines() + .filter_map(|l| l.trim_start().strip_prefix("└─")) + .last() + .unwrap_or(chain); + return FetchError::Network(first_line(cause)); + } + FetchError::Other(first_line(chain)) +} + +/// First line of `chain`, without gix's `", at :"` suffix, +/// capped at 300 characters. +fn first_line(chain: &str) -> String { + let line = chain.lines().next().unwrap_or(""); + let line = line.split(", at /").next().unwrap_or(line); + line.trim().chars().take(300).collect() +} + +fn classify(e: E) -> FetchError { + classify_fetch_error(&format!("{:?}", e)) +} + +pub fn cache_path(data_root: &Path, marketplace_id: &str) -> PathBuf { + data_root + .join("marketplaces") + .join(format!("{}.git", marketplace_id)) +} + +/// Branch names that are safe inside a refspec. Stricter than git's own +/// rules on purpose: nothing that could change the refspec's meaning. +/// `pub(crate)` so the add-marketplace form validates with this same rule +/// (pre-flight F13). +pub(crate) fn valid_branch(branch: &str) -> bool { + !branch.is_empty() + && branch.len() <= 200 + && !branch.starts_with('-') + && !branch.starts_with('/') + && !branch.ends_with('/') + && !branch.ends_with(".lock") + && !branch.contains("..") + && !branch.contains("//") + && branch + .bytes() + .all(|b| b.is_ascii_alphanumeric() || matches!(b, b'-' | b'_' | b'.' | b'/')) +} + +fn open_or_init(repo_path: &Path) -> Result { + if repo_path.exists() { + gix::open(repo_path) + .map_err(|e| FetchError::Other(format!("Could not open the marketplace cache: {}", e))) + } else { + if let Some(parent) = repo_path.parent() { + std::fs::create_dir_all(parent).map_err(|e| { + FetchError::Other(format!("Could not create {}: {}", parent.display(), e)) + })?; + } + gix::init_bare(repo_path).map_err(|e| { + FetchError::Other(format!("Could not create the marketplace cache: {}", e)) + }) + } +} + +/// Init the bare repo if missing, fetch `branch` (or the remote's default +/// branch) into [`HEAD_REF`], and return the head commit hex. +pub fn fetch( + repo_path: &Path, + url: &str, + branch: Option<&str>, + cred: Option, +) -> Result { + let refspec = match branch { + Some(b) if !valid_branch(b) => { + return Err(FetchError::Other(format!( + "{:?} is not a valid branch name", + b + ))); + } + Some(b) => format!("+refs/heads/{}:{}", b, HEAD_REF), + None => format!("+HEAD:{}", HEAD_REF), + }; + let repo = open_or_init(repo_path)?; + let remote = repo + .remote_at(url) + .map_err(|e| FetchError::Other(format!("Invalid repository URL: {}", e)))? + .with_refspecs([refspec.as_str()], gix::remote::Direction::Fetch) + .map_err(|e| FetchError::Other(format!("Invalid refspec: {}", e)))?; + let connection = remote + .connect(gix::remote::Direction::Fetch) + .map_err(classify)? + .with_credentials(move |action| match (action, &cred) { + (gix::credentials::helper::Action::Get(ctx), Some(c)) => { + Ok(Some(gix::credentials::protocol::Outcome { + identity: gix::sec::identity::Account { + username: c.username.clone(), + password: c.password.clone(), + oauth_refresh_token: None, + }, + next: gix::credentials::helper::NextAction::from(ctx), + })) + } + _ => Ok(None), + }); + connection + .prepare_fetch(gix::progress::Discard, Default::default()) + .map_err(classify)? + .receive(gix::progress::Discard, &AtomicBool::new(false)) + .map_err(classify)?; + cached_head(repo_path) + .map_err(FetchError::Other)? + .ok_or_else(|| FetchError::Other("The remote did not return a branch to fetch".to_string())) +} + +/// Current [`HEAD_REF`], if fetched before. +pub fn cached_head(repo_path: &Path) -> Result, String> { + if !repo_path.exists() { + return Ok(None); + } + let repo = + gix::open(repo_path).map_err(|e| format!("Could not open the marketplace cache: {}", e))?; + let reference = repo + .try_find_reference(HEAD_REF) + .map_err(|e| format!("Could not read {}: {}", HEAD_REF, e))?; + match reference { + None => Ok(None), + Some(mut r) => { + let id = r + .peel_to_id() + .map_err(|e| format!("Could not resolve {}: {}", HEAD_REF, e))?; + Ok(Some(id.to_string())) + } + } +} + +pub fn has_commit(repo_path: &Path, commit: &str) -> bool { + let Ok(repo) = gix::open(repo_path) else { + return false; + }; + let Ok(oid) = gix::ObjectId::from_hex(commit.as_bytes()) else { + return false; + }; + // Bound before returning: the `Result>` temporary borrows + // `repo` and must drop first (pre-flight F1, E0597 as a tail expression). + let found = repo.find_commit(oid).is_ok(); + found +} + +/// Make `refs/triple-c/pins/*` exactly the given set (commits missing from +/// the cache are skipped), so pinned commits survive later fetches. +pub fn set_pins(repo_path: &Path, commits: &[String]) -> Result<(), String> { + let repo = + gix::open(repo_path).map_err(|e| format!("Could not open the marketplace cache: {}", e))?; + let wanted: std::collections::BTreeSet<&str> = commits.iter().map(String::as_str).collect(); + + let mut existing = Vec::new(); + let platform = repo + .references() + .map_err(|e| format!("Could not list refs: {}", e))?; + for reference in platform + .prefixed(PIN_PREFIX) + .map_err(|e| format!("Could not list pins: {}", e))? + { + let reference = reference.map_err(|e| format!("Could not read a pin: {:?}", e))?; + existing.push(reference.name().as_bstr().to_string()); + } + + for name in &existing { + let commit = name.trim_start_matches(PIN_PREFIX); + if !wanted.contains(commit) { + if let Some(r) = repo + .try_find_reference(name.as_str()) + .map_err(|e| format!("Could not read {}: {}", name, e))? + { + r.delete() + .map_err(|e| format!("Could not remove {}: {}", name, e))?; + } + } + } + for commit in wanted { + let name = format!("{}{}", PIN_PREFIX, commit); + if existing.contains(&name) { + continue; + } + let Ok(oid) = gix::ObjectId::from_hex(commit.as_bytes()) else { + continue; + }; + if repo.find_commit(oid).is_err() { + continue; + } + repo.reference( + name.as_str(), + oid, + gix::refs::transaction::PreviousValue::Any, + "triple-c pin", + ) + .map_err(|e| format!("Could not pin {}: {}", commit, e))?; + } + Ok(()) +} + +#[cfg(test)] +pub(crate) mod test_support { + //! Fixture repos built with the git CLI. Tests that need one call + //! [`git_available`] first and return early without it. + use std::path::Path; + use std::process::Command; + + pub fn git_available() -> bool { + Command::new("git") + .arg("--version") + .output() + .map(|o| o.status.success()) + .unwrap_or(false) + } + + pub fn git(dir: &Path, args: &[&str]) -> String { + let out = Command::new("git") + .args([ + "-c", + "user.name=t", + "-c", + "user.email=t@example.invalid", + "-c", + "init.defaultBranch=main", + ]) + .args(args) + .current_dir(dir) + .output() + .expect("git runs"); + assert!( + out.status.success(), + "git {:?}: {}", + args, + String::from_utf8_lossy(&out.stderr) + ); + String::from_utf8_lossy(&out.stdout).trim().to_string() + } + + /// Write `files` (path, contents, executable) into a new repo and commit. + pub fn init_repo(dir: &Path, files: &[(&str, &str, bool)]) -> String { + git(dir, &["init", "-q"]); + commit_files(dir, files, "initial") + } + + pub fn commit_files(dir: &Path, files: &[(&str, &str, bool)], message: &str) -> String { + for (path, contents, exec) in files { + let full = dir.join(path); + std::fs::create_dir_all(full.parent().unwrap()).unwrap(); + std::fs::write(&full, contents).unwrap(); + #[cfg(unix)] + if *exec { + use std::os::unix::fs::PermissionsExt; + std::fs::set_permissions(&full, std::fs::Permissions::from_mode(0o755)).unwrap(); + } + #[cfg(not(unix))] + let _ = exec; + } + git(dir, &["add", "-A"]); + git(dir, &["commit", "-q", "-m", message]); + git(dir, &["rev-parse", "HEAD"]) + } + + pub fn file_url(dir: &Path) -> String { + format!("file://{}", dir.display()) + } +} + +#[cfg(test)] +mod tests { + use super::test_support::*; + use super::*; + use crate::marketplace::tree::{GitTree, TreeView}; + + #[test] + fn fetch_error_mapping() { + let cases = [ + ("Credentials provided for \"https://x\" were not accepted by the remote\n└─ Received HTTP status 401", FetchError::Auth { status: 401 }), + ("handshake\n└─ Received HTTP status 403", FetchError::Auth { status: 403 }), + ("└─ Received HTTP status 404", FetchError::NotFound), + ("remote: Repository not found.", FetchError::NotFound), + // What gix actually reports for an anonymous fetch of a private + // (or missing) GitHub/GitLab repo (pre-flight F2). + ( + "No credentials were returned at all as if the credential helper isn't functioning unknowingly, at /home/u/.cargo/registry/src/index/gix-protocol-0.1/src/handshake/function.rs:70", + FetchError::Auth { status: 401 }, + ), + ]; + for (text, want) in cases { + assert_eq!(classify_fetch_error(text), want, "{}", text); + } + assert!(matches!( + classify_fetch_error("error sending request\n└─ dns error: failed to lookup address"), + FetchError::Network(_) + )); + assert!(matches!( + classify_fetch_error("operation timed out"), + FetchError::Network(_) + )); + assert!(matches!( + classify_fetch_error("something odd"), + FetchError::Other(_) + )); + } + + #[test] + fn fetch_error_text_drops_source_locations_and_names_the_network_cause() { + // Pre-flight F2: gix appends ", at :"; + // the innermost `└─` line is the useful network cause. + let chain = "Transport handshake failed, at /home/u/.cargo/registry/src/x/handshake/function.rs:40\n\ + ├─ An IO error occurred when talking to the server, at /home/u/.cargo/y.rs:12\n\ + └─ error resolving DNS, at /home/u/.cargo/z.rs:9"; + assert_eq!( + classify_fetch_error(chain), + FetchError::Network("error resolving DNS".to_string()) + ); + let refused = "Transport handshake failed, at /home/u/.cargo/a.rs:1\n└─ Connection refused (os error 111)"; + assert_eq!( + classify_fetch_error(refused), + FetchError::Network("Connection refused (os error 111)".to_string()) + ); + assert_eq!( + classify_fetch_error("Something odd, at /home/u/.cargo/b.rs:3\n└─ deeper"), + FetchError::Other("Something odd".to_string()) + ); + } + + #[test] + fn credential_debug_never_shows_the_password() { + let c = Credential { + username: "u".into(), + password: "test-token-not-real".into(), + }; + let shown = format!("{:?}", c); + assert!(!shown.contains("test-token-not-real")); + assert!(shown.contains("")); + } + + #[test] + fn refuses_unsafe_branch_names() { + let dir = tempfile::tempdir().unwrap(); + for bad in ["-x", "a..b", "a b", "a:b", "x*", "a.lock", ""] { + let err = fetch( + &dir.path().join("c.git"), + "file:///nowhere", + Some(bad), + None, + ) + .unwrap_err(); + assert!( + matches!(err, FetchError::Other(ref m) if m.contains("branch")), + "{bad:?}: {err:?}" + ); + } + } + + #[test] + fn valid_branch_accepts_ordinary_names() { + // pub(crate) so the add-marketplace form validates with the same rule + // the fetch applies (pre-flight F13). + for good in ["main", "release/1.2", "feature_x", "v2.0-rc.1"] { + assert!(valid_branch(good), "{good:?}"); + } + for bad in [ + "/main", "main/", "a//b", "x.lock", "-x", "a..b", "a b", "a\\b", + ] { + assert!(!valid_branch(bad), "{bad:?}"); + } + } + + #[test] + fn fetches_default_branch_then_updates() { + if !git_available() { + return; + } + let src = tempfile::tempdir().unwrap(); + let first = init_repo( + src.path(), + &[ + ("agents/a.md", "one", false), + ("hooks/h/run.sh", "#!/bin/sh", true), + ], + ); + let cache = tempfile::tempdir().unwrap(); + let repo = cache_path(cache.path(), "m1"); + + assert_eq!(cached_head(&repo).unwrap(), None); + let head = fetch(&repo, &file_url(src.path()), None, None).unwrap(); + assert_eq!(head, first); + assert_eq!(cached_head(&repo).unwrap(), Some(first.clone())); + assert!(has_commit(&repo, &first)); + + let tree = GitTree::open(&repo, &first).unwrap(); + assert_eq!(tree.read_file("agents/a.md").unwrap().unwrap(), b"one"); + let hook = tree.list_dir("hooks/h").unwrap().unwrap(); + assert!(hook[0].executable); + assert!(tree.entry_id("agents/a.md").unwrap().is_some()); + assert_eq!(tree.list_dir("agents/a.md").unwrap(), None); + + let second = commit_files(src.path(), &[("agents/a.md", "two", false)], "second"); + assert_eq!( + fetch(&repo, &file_url(src.path()), None, None).unwrap(), + second + ); + // The old commit is still readable after the update. + assert_eq!( + GitTree::open(&repo, &first) + .unwrap() + .read_file("agents/a.md") + .unwrap() + .unwrap(), + b"one" + ); + } + + #[test] + fn fetches_a_named_branch() { + if !git_available() { + return; + } + let src = tempfile::tempdir().unwrap(); + init_repo(src.path(), &[("a.md", "main", false)]); + git(src.path(), &["checkout", "-q", "-b", "next"]); + let next = commit_files(src.path(), &[("a.md", "next", false)], "next"); + git(src.path(), &["checkout", "-q", "main"]); + + let cache = tempfile::tempdir().unwrap(); + let repo = cache_path(cache.path(), "m1"); + assert_eq!( + fetch(&repo, &file_url(src.path()), Some("next"), None).unwrap(), + next + ); + } + + #[test] + fn missing_repo_is_an_error_not_a_panic() { + let cache = tempfile::tempdir().unwrap(); + let err = fetch( + &cache_path(cache.path(), "m"), + "file:///definitely/not/here", + None, + None, + ) + .unwrap_err(); + assert!(!matches!(err, FetchError::Auth { .. }), "{err:?}"); + } + + #[test] + fn refused_connection_is_a_network_error_without_source_paths() { + // Port 1 on loopback: refused immediately, no real network involved. + let cache = tempfile::tempdir().unwrap(); + let err = fetch( + &cache_path(cache.path(), "m"), + "https://127.0.0.1:1/x.git", + None, + None, + ) + .unwrap_err(); + match err { + FetchError::Network(m) => assert!(!m.contains(", at /"), "{m}"), + other => panic!("expected a network error, got {other:?}"), + } + } + + #[test] + fn has_commit_is_false_for_unknown_or_malformed_ids() { + if !git_available() { + return; + } + let src = tempfile::tempdir().unwrap(); + init_repo(src.path(), &[("x", "1", false)]); + let cache = tempfile::tempdir().unwrap(); + let repo = cache_path(cache.path(), "m"); + fetch(&repo, &file_url(src.path()), None, None).unwrap(); + assert!(!has_commit(&repo, &"f".repeat(40))); + assert!(!has_commit(&repo, "not-hex")); + assert!(!has_commit( + &cache.path().join("absent.git"), + &"f".repeat(40) + )); + } + + #[test] + fn pins_are_exactly_the_requested_set() { + if !git_available() { + return; + } + let src = tempfile::tempdir().unwrap(); + let a = init_repo(src.path(), &[("x", "1", false)]); + let b = commit_files(src.path(), &[("x", "2", false)], "b"); + let cache = tempfile::tempdir().unwrap(); + let repo = cache_path(cache.path(), "m"); + fetch(&repo, &file_url(src.path()), None, None).unwrap(); + + set_pins(&repo, &[a.clone(), b.clone(), "f".repeat(40)]).unwrap(); + let pins = |repo: &Path| -> Vec { + let r = gix::open(repo).unwrap(); + let mut names: Vec = r + .references() + .unwrap() + .prefixed(PIN_PREFIX) + .unwrap() + .map(|x| x.unwrap().name().as_bstr().to_string()) + .collect(); + names.sort(); + names + }; + let mut want = vec![ + format!("{}{}", PIN_PREFIX, a), + format!("{}{}", PIN_PREFIX, b), + ]; + want.sort(); + assert_eq!(pins(&repo), want); + + set_pins(&repo, &[b.clone()]).unwrap(); + assert_eq!(pins(&repo), vec![format!("{}{}", PIN_PREFIX, b)]); + } + + #[test] + fn git_tree_entry_with_a_backslash_marks_the_item_invalid() { + // Task 3 review: a real git tree (not MemTree) whose entry name + // contains `\` must make the catalog reject the item. git itself + // refuses `/` in names, so `\` is the separator that can get through. + if !git_available() { + return; + } + let src = tempfile::tempdir().unwrap(); + init_repo(src.path(), &[("skills/ok/SKILL.md", "fine", false)]); + let evil = commit_files( + src.path(), + &[ + ("skills/s/SKILL.md", "x", false), + ("skills/s/..\\evil.sh", "boom", false), + ], + "evil", + ); + let cache = tempfile::tempdir().unwrap(); + let repo = cache_path(cache.path(), "m"); + assert_eq!( + fetch(&repo, &file_url(src.path()), None, None).unwrap(), + evil + ); + + let tree = GitTree::open(&repo, &evil).unwrap(); + let names: Vec = tree + .list_dir("skills/s") + .unwrap() + .unwrap() + .into_iter() + .map(|e| e.name) + .collect(); + assert!(names.contains(&"..\\evil.sh".to_string()), "{names:?}"); + + let items = crate::marketplace::catalog::parse_catalog(&tree); + let skill = items.iter().find(|i| i.key == "s").unwrap(); + assert!(skill.invalid.is_some(), "{skill:?}"); + let ok = items.iter().find(|i| i.key == "ok").unwrap(); + assert!(ok.invalid.is_none(), "{ok:?}"); + } +} diff --git a/app/src-tauri/src/marketplace/mod.rs b/app/src-tauri/src/marketplace/mod.rs index 70ccdb8..be7b083 100644 --- a/app/src-tauri/src/marketplace/mod.rs +++ b/app/src-tauri/src/marketplace/mod.rs @@ -1,4 +1,5 @@ //! Marketplace support — see `docs/superpowers/specs/2026-09-27-marketplace-design.md`. pub mod catalog; +pub mod git; pub mod tree; diff --git a/app/src-tauri/src/marketplace/tree.rs b/app/src-tauri/src/marketplace/tree.rs index e600b25..7a033b0 100644 --- a/app/src-tauri/src/marketplace/tree.rs +++ b/app/src-tauri/src/marketplace/tree.rs @@ -40,6 +40,105 @@ pub(crate) fn hex(bytes: &[u8]) -> String { bytes.iter().map(|b| format!("{:02x}", b)).collect() } +/// A tree at one commit of a bare gix repository. +pub struct GitTree { + repo: gix::Repository, + tree_id: gix::ObjectId, +} + +impl GitTree { + pub fn open(repo_path: &std::path::Path, commit: &str) -> Result { + let repo = gix::open(repo_path) + .map_err(|e| format!("Could not open the marketplace cache: {}", e))?; + let oid = gix::ObjectId::from_hex(commit.as_bytes()) + .map_err(|e| format!("Invalid commit id {}: {}", commit, e))?; + let tree_id = repo + .find_commit(oid) + .map_err(|e| format!("Commit {} is not in the marketplace cache: {}", commit, e))? + .tree_id() + .map_err(|e| format!("Commit {} has no tree: {}", commit, e))? + .detach(); + Ok(Self { repo, tree_id }) + } + + fn root(&self) -> Result, String> { + self.repo + .find_tree(self.tree_id) + .map_err(|e| format!("Could not read tree {}: {}", self.tree_id, e)) + } + + /// `(object id, mode)` of the entry at `path`, or `None`. + fn lookup( + &self, + path: &str, + ) -> Result, String> { + if path.is_empty() { + return Ok(Some(( + self.tree_id, + gix::object::tree::EntryKind::Tree.into(), + ))); + } + let root = self.root()?; + let entry = root + .lookup_entry_by_path(path) + .map_err(|e| format!("Could not look up {}: {}", path, e))?; + Ok(entry.map(|e| (e.object_id(), e.mode()))) + } +} + +impl TreeView for GitTree { + fn list_dir(&self, path: &str) -> Result>, String> { + let Some((id, mode)) = self.lookup(path)? else { + return Ok(None); + }; + if !mode.is_tree() { + return Ok(None); + } + let tree = self + .repo + .find_tree(id) + .map_err(|e| format!("Could not read {}: {}", path, e))?; + let mut out = Vec::new(); + for entry in tree.iter() { + let entry = entry.map_err(|e| format!("Could not read {}: {:?}", path, e))?; + let mode = entry.mode(); + let kind = if mode.is_tree() { + EntryKind::Dir + } else if mode.is_link() { + EntryKind::Symlink + } else if mode.is_blob() { + EntryKind::File + } else { + EntryKind::Other + }; + out.push(DirEntry { + name: entry.filename().to_string(), + kind, + executable: mode.is_executable(), + }); + } + Ok(Some(out)) + } + + fn read_file(&self, path: &str) -> Result>, String> { + let Some((id, mode)) = self.lookup(path)? else { + return Ok(None); + }; + if !mode.is_blob() { + return Ok(None); + } + let blob = self + .repo + .find_blob(id) + .map_err(|e| format!("Could not read {}: {}", path, e))?; + Ok(Some(blob.data.clone())) + } + + fn entry_id(&self, path: &str) -> Result, String> { + Ok(self.lookup(path)?.map(|(id, _)| id.to_string())) + } +} + #[cfg(test)] #[derive(Debug, Clone)] enum MemNode {