diff --git a/Cargo.lock b/Cargo.lock index e4fa48c67..bc77de704 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -301,6 +301,17 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "async-recursion" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + [[package]] name = "async-stream" version = "0.3.6" @@ -334,6 +345,30 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "asyncfuse" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73944bd789372ebf1f10a25ee59296bbb437b31dbe234dcd959e98a1f3238fac" +dependencies = [ + "aligned_box", + "async-notify", + "async-trait", + "bincode 1.3.3", + "bytes", + "dashmap", + "futures-channel", + "futures-util", + "libc", + "nix 0.29.0", + "serde", + "slab", + "tokio", + "tracing", + "trait-make", + "which", +] + [[package]] name = "atoi" version = "2.0.0" @@ -521,6 +556,26 @@ dependencies = [ "serde", ] +[[package]] +name = "bincode" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36eaf5d7b090263e8150820482d5d93cd964a81e4019913c972f4edcc6edb740" +dependencies = [ + "bincode_derive", + "serde", + "unty", +] + +[[package]] +name = "bincode_derive" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf95709a440f45e986983918d0e8a1f30a9b1df04918fc828670606804ac3c09" +dependencies = [ + "virtue", +] + [[package]] name = "bindgen" version = "0.72.1" @@ -1324,6 +1379,19 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "crossbeam" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1137cd7e7fc0fb5d3c5a8678be38ec56e819125d8d7907411fe24ccb943faca8" +dependencies = [ + "crossbeam-channel", + "crossbeam-deque", + "crossbeam-epoch", + "crossbeam-queue", + "crossbeam-utils", +] + [[package]] name = "crossbeam-channel" version = "0.5.15" @@ -1379,7 +1447,7 @@ dependencies = [ "document-features", "futures-core", "mio", - "parking_lot", + "parking_lot 0.12.5", "rustix 1.1.4", "signal-hook", "signal-hook-mio", @@ -1605,7 +1673,7 @@ dependencies = [ "hashbrown 0.14.5", "lock_api", "once_cell", - "parking_lot_core", + "parking_lot_core 0.9.12", ] [[package]] @@ -1645,6 +1713,37 @@ dependencies = [ "zeroize", ] +[[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 = "deltae" version = "0.3.2" @@ -2016,6 +2115,12 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c34f04666d835ff5d62e058c3995147c06f42fe86ff053337632bca83e42702d" +[[package]] +name = "endian-type" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "869b0adbda23651a9c5c0c3d270aac9fcb52e8622a8f2b17e57802d7791962f2" + [[package]] name = "enum-map" version = "2.7.3" @@ -2036,6 +2141,29 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "env_filter" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "900d271a03799a1ee8d1ca9b19893b48ca674a9284fefcfb85f05e74ed314217" +dependencies = [ + "log", + "regex", +] + +[[package]] +name = "env_logger" +version = "0.11.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de671bd27a75a797dc9ae289ba1e77276e75e2026408aab65185384e2d5cd3f6" +dependencies = [ + "anstream", + "anstyle", + "env_filter", + "jiff", + "log", +] + [[package]] name = "equivalent" version = "1.0.2" @@ -2228,6 +2356,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c" dependencies = [ "crc32fast", + "libz-sys", "miniz_oxide", "zlib-rs", ] @@ -2285,6 +2414,16 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "fs2" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9564fc758e15025b46aa6643b1b77d047d1a56a1aea6e01002ac0c7026876213" +dependencies = [ + "libc", + "winapi", +] + [[package]] name = "fs_extra" version = "1.3.0" @@ -2347,7 +2486,7 @@ checksum = "1d930c203dd0b6ff06e0201a4a2fe9149b43c684fd4420555b26d21b1a02956f" dependencies = [ "futures-core", "lock_api", - "parking_lot", + "parking_lot 0.12.5", ] [[package]] @@ -2402,6 +2541,15 @@ dependencies = [ "slab", ] +[[package]] +name = "fxhash" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" +dependencies = [ + "byteorder", +] + [[package]] name = "gag" version = "1.0.0" @@ -2483,6 +2631,52 @@ dependencies = [ "polyval", ] +[[package]] +name = "git-internal" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3373b0719c11eed1df7d80242354816316b44c7f842f519daf60efacec7a861a" +dependencies = [ + "ahash 0.8.12", + "async-trait", + "axum", + "bincode 2.0.1", + "bstr", + "byteorder", + "bytes", + "chrono", + "colored", + "crc32fast", + "dashmap", + "diffs", + "encoding_rs", + "flate2", + "futures", + "futures-util", + "hex", + "libc", + "lru-mem", + "memchr", + "natord", + "num_cpus", + "path-absolutize", + "rayon", + "sea-orm", + "serde", + "sha1 0.10.6", + "sha2 0.10.9", + "similar 2.7.0", + "tempfile", + "thiserror 2.0.18", + "threadpool", + "tokio", + "tokio-stream", + "tracing", + "tracing-subscriber", + "uuid", + "zstd-sys", +] + [[package]] name = "git-internal" version = "0.8.3" @@ -2519,7 +2713,7 @@ dependencies = [ "serde_json", "sha1 0.11.0", "sha2 0.11.0", - "similar", + "similar 3.1.1", "tempfile", "thiserror 2.0.18", "threadpool", @@ -3174,6 +3368,15 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "instant" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222" +dependencies = [ + "cfg-if", +] + [[package]] name = "ipnet" version = "2.12.0" @@ -3229,6 +3432,42 @@ version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" +[[package]] +name = "jiff" +version = "0.2.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "668b7183bd07af9a4885f5c35b0cc5c83c4607a913c16b7e17291832910d2dcc" +dependencies = [ + "defmt", + "jiff-core", + "jiff-static", + "log", + "portable-atomic", + "portable-atomic-util", + "serde_core", +] + +[[package]] +name = "jiff-core" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7feca88439efe53da3754500c1851dedf3cb36c524dd5cf8225cc0794de95d09" +dependencies = [ + "defmt", +] + +[[package]] +name = "jiff-static" +version = "0.2.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a69dcb3a21cfb32ce1cd056169337ca284af0766dd766e7878819b251a49204" +dependencies = [ + "jiff-core", + "proc-macro2", + "quote", + "syn 2.0.117", +] + [[package]] name = "jni" version = "0.21.1" @@ -3392,7 +3631,7 @@ dependencies = [ "memmap2", "moka", "nix 0.29.0", - "radix_trie", + "radix_trie 0.2.1", "reqwest 0.12.28", "rfuse3", "serde", @@ -3405,6 +3644,37 @@ dependencies = [ "vmm-sys-util", ] +[[package]] +name = "libfuse-fs" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf5f1ba3c5498a7b893d4398dfd6de0f21d87d16f1385258950d62d5a7806af8" +dependencies = [ + "async-trait", + "asyncfuse", + "bitflags 2.11.0", + "bytes", + "clap", + "futures", + "futures-util", + "itertools 0.14.0", + "libc", + "lru", + "memmap2", + "moka", + "nix 0.29.0", + "radix_trie 0.2.1", + "reqwest 0.12.28", + "serde", + "serde_json", + "tokio", + "tracing", + "tracing-subscriber", + "uuid", + "vm-memory", + "vmm-sys-util", +] + [[package]] name = "libloading" version = "0.8.9" @@ -3447,11 +3717,12 @@ dependencies = [ "dirs", "fastrand", "flate2", + "fs2", "futures", "futures-core", "futures-util", "gag", - "git-internal", + "git-internal 0.8.3", "hex", "http", "hyper-util", @@ -3461,7 +3732,7 @@ dependencies = [ "keyring", "lazy_static", "libc", - "libfuse-fs", + "libfuse-fs 0.1.13", "libvault", "lru-mem", "mime_guess", @@ -3487,6 +3758,7 @@ dependencies = [ "rpassword", "rust-embed", "scopeguard", + "scorpiofs", "sea-orm", "seccompiler", "serde", @@ -3495,7 +3767,7 @@ dependencies = [ "sha1 0.11.0", "sha2 0.10.9", "shlex", - "similar", + "similar 3.1.1", "syn 2.0.117", "tar", "tempfile", @@ -3505,7 +3777,7 @@ dependencies = [ "tokio-stream", "tokio-tungstenite 0.29.0", "tokio-util", - "toml", + "toml 0.8.23", "tower", "tower-http", "tracing", @@ -3587,7 +3859,7 @@ dependencies = [ "pem", "pgp", "priority-queue", - "radix_trie", + "radix_trie 0.2.1", "rand 0.9.2", "rand_chacha 0.3.1", "regex", @@ -3609,7 +3881,7 @@ dependencies = [ "tempfile", "thiserror 2.0.18", "tokio", - "toml", + "toml 0.8.23", "tonic", "tracing", "ureq 2.12.1", @@ -3619,6 +3891,17 @@ dependencies = [ "zeroize", ] +[[package]] +name = "libz-sys" +version = "1.1.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85bc9657773828b90eeb625adff10eeac83cc21bbfd8e23a03eaa8a33c9e28d9" +dependencies = [ + "cc", + "pkg-config", + "vcpkg", +] + [[package]] name = "line-clipping" version = "0.3.5" @@ -3820,7 +4103,7 @@ dependencies = [ "equivalent", "event-listener", "futures-util", - "parking_lot", + "parking_lot 0.12.5", "portable-atomic", "smallvec", "tagptr", @@ -4111,7 +4394,7 @@ dependencies = [ "hyper", "itertools 0.14.0", "md-5", - "parking_lot", + "parking_lot 0.12.5", "percent-encoding", "quick-xml", "rand 0.10.0", @@ -4395,6 +4678,17 @@ version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" +[[package]] +name = "parking_lot" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99" +dependencies = [ + "instant", + "lock_api", + "parking_lot_core 0.8.6", +] + [[package]] name = "parking_lot" version = "0.12.5" @@ -4402,7 +4696,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" dependencies = [ "lock_api", - "parking_lot_core", + "parking_lot_core 0.9.12", +] + +[[package]] +name = "parking_lot_core" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60a2cfe6f0ad2bfc16aefa463b497d5c7a5ecd44a23efa72aa342d90177356dc" +dependencies = [ + "cfg-if", + "instant", + "libc", + "redox_syscall 0.2.16", + "smallvec", + "winapi", ] [[package]] @@ -5151,7 +5459,17 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c069c179fcdc6a2fe24d8d18305cf085fdbd4f922c041943e203685d6a1c58fd" dependencies = [ - "endian-type", + "endian-type 0.1.2", + "nibble_vec", +] + +[[package]] +name = "radix_trie" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b4431027dcd37fc2a73ef740b5f233aa805897935b8bce0195e41bbf9a3289a" +dependencies = [ + "endian-type 0.2.0", "nibble_vec", ] @@ -5345,6 +5663,15 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "redox_syscall" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" +dependencies = [ + "bitflags 1.3.2", +] + [[package]] name = "redox_syscall" version = "0.5.18" @@ -5561,7 +5888,7 @@ dependencies = [ "aligned_box", "async-notify", "async-trait", - "bincode", + "bincode 1.3.3", "bytes", "dashmap", "futures-channel", @@ -6049,6 +6376,44 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "scorpiofs" +version = "0.3.1" +dependencies = [ + "async-recursion", + "async-trait", + "asyncfuse", + "axum", + "bincode 2.0.1", + "bytes", + "clap", + "clap_complete", + "crossbeam", + "dashmap", + "env_logger", + "futures", + "git-internal 0.4.1", + "hex", + "libc", + "libfuse-fs 0.2.0", + "log", + "once_cell", + "radix_trie 0.3.0", + "reqwest 0.13.2", + "ring", + "serde", + "serde_json", + "sled", + "thiserror 2.0.18", + "tokio", + "toml 0.9.12+spec-1.1.0", + "tracing", + "tracing-subscriber", + "url", + "uuid", + "whoami", +] + [[package]] name = "sdd" version = "3.0.10" @@ -6315,6 +6680,15 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_spanned" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6662b5879511e06e8999a8a235d848113e942c9124f211511b16466ee2995f26" +dependencies = [ + "serde_core", +] + [[package]] name = "serde_urlencoded" version = "0.7.1" @@ -6411,7 +6785,7 @@ dependencies = [ "futures-executor", "futures-util", "once_cell", - "parking_lot", + "parking_lot 0.12.5", "scc", "serial_test_derive", ] @@ -6582,6 +6956,12 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" +[[package]] +name = "similar" +version = "2.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbbb5d9659141646ae647b42fe094daf6c6192d1620870b449d9557f748b2daa" + [[package]] name = "similar" version = "3.1.1" @@ -6603,6 +6983,22 @@ version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" +[[package]] +name = "sled" +version = "0.34.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f96b4737c2ce5987354855aed3797279def4ebf734436c6aa4552cf8e169935" +dependencies = [ + "crc32fast", + "crossbeam-epoch", + "crossbeam-utils", + "fs2", + "fxhash", + "libc", + "log", + "parking_lot 0.11.2", +] + [[package]] name = "smallvec" version = "1.15.1" @@ -6954,7 +7350,7 @@ dependencies = [ "atomic", "crossbeam-channel", "getrandom 0.2.17", - "parking_lot", + "parking_lot 0.12.5", "rand 0.8.5", "seahash", "thiserror 1.0.69", @@ -7389,7 +7785,7 @@ dependencies = [ "bytes", "libc", "mio", - "parking_lot", + "parking_lot 0.12.5", "pin-project-lite", "signal-hook-registry", "socket2", @@ -7490,11 +7886,26 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362" dependencies = [ "serde", - "serde_spanned", + "serde_spanned 0.6.9", "toml_datetime 0.6.11", "toml_edit 0.22.27", ] +[[package]] +name = "toml" +version = "0.9.12+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf92845e79fc2e2def6a5d828f0801e29a2f8acc037becc5ab08595c7d5e9863" +dependencies = [ + "indexmap 2.13.0", + "serde_core", + "serde_spanned 1.1.1", + "toml_datetime 0.7.5+spec-1.1.0", + "toml_parser", + "toml_writer", + "winnow", +] + [[package]] name = "toml_datetime" version = "0.6.11" @@ -7504,6 +7915,15 @@ dependencies = [ "serde", ] +[[package]] +name = "toml_datetime" +version = "0.7.5+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92e1cfed4a3038bc5a127e35a2d360f145e1f4b971b551a2ba5fd7aedf7e1347" +dependencies = [ + "serde_core", +] + [[package]] name = "toml_datetime" version = "1.0.0+spec-1.1.0" @@ -7521,7 +7941,7 @@ checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" dependencies = [ "indexmap 2.13.0", "serde", - "serde_spanned", + "serde_spanned 0.6.9", "toml_datetime 0.6.11", "toml_write", "winnow", @@ -7554,6 +7974,12 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801" +[[package]] +name = "toml_writer" +version = "1.1.2+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d56353a2a665ad0f41a421187180aab746c8c325620617ad883a99a1cbe66d2" + [[package]] name = "tonic" version = "0.14.5" @@ -7984,6 +8410,12 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" +[[package]] +name = "unty" +version = "0.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d49784317cd0d1ee7ec5c716dd598ec5b4483ea832a2dced265471cc0f690ae" + [[package]] name = "ureq" version = "2.12.1" @@ -8108,6 +8540,12 @@ version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" +[[package]] +name = "virtue" +version = "0.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "051eb1abcf10076295e815102942cc58f9d5e3b4560e46e53c21e8ff6f3af7b1" + [[package]] name = "vm-memory" version = "0.16.2" @@ -8457,7 +8895,7 @@ checksum = "7aafc5e81e847f05d6770e074faf7b1cd4a5dec9a0e88eac5d55e20fdfebee9a" dependencies = [ "event-listener", "futures-core", - "parking_lot", + "parking_lot 0.12.5", "triomphe", ] @@ -8481,6 +8919,7 @@ checksum = "5d4a4db5077702ca3015d3d02d74974948aba2ad9e12ab7df718ee64ccd7e97d" dependencies = [ "libredox", "wasite", + "web-sys", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index b0de0deb6..2f442b91b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,7 +12,8 @@ categories = ["command-line-utilities", "development-tools"] readme = "README.md" [features] -default = [] +default = ["scorpiofs-direct"] +scorpiofs-direct = ["dep:scorpiofs"] worktree-fuse = [] # Unix FUSE-backed worktree commands (optional) test-network = [] # L2: tests requiring outbound network but no secrets test-live-ai = [] # L3: tests calling real LLM APIs @@ -70,6 +71,7 @@ sea-orm = { version = "1.1.20", features = [ ]} serde = { version = "1.0.228", features = ["derive"] } serde_json = "1.0.149" +fs2 = "0.4.3" sha1 = "0.11.0" sha2 = "0.10" thiserror = "2.0.18" @@ -158,6 +160,7 @@ rfuse3 = { version = "0.0.8", features = ["tokio-runtime", "unprivileged"] } [target.'cfg(target_os = "linux")'.dependencies] # Linux-only seccomp BPF compiler seccompiler = { version = "0.5.0", features = ["json"] } +scorpiofs = { version = "=0.3.1", optional = true } [target.'cfg(windows)'.dependencies] windows-sys = { version = "0.61.2", features = ["Win32_Storage_FileSystem"] } diff --git a/docs/commands/worktree.md b/docs/commands/worktree.md index abd383307..23c74219d 100644 --- a/docs/commands/worktree.md +++ b/docs/commands/worktree.md @@ -8,6 +8,8 @@ Manage multiple working trees attached to this repository. ``` libra worktree add +libra worktree scorpiofs attach --remote-path --job-id +libra worktree scorpiofs detach libra worktree list libra worktree lock [--reason ] libra worktree unlock @@ -22,6 +24,12 @@ libra worktree repair `libra worktree` manages multiple working trees that share a single repository database and object store. This allows you to have several checkouts of the same repository simultaneously, which is useful for working on multiple branches at once, running builds while editing code, or testing changes in isolation. +Mounted worktrees use the backend-neutral architecture documented in +[`worktree-storage-backends.md`](../development/integration/worktree-storage-backends.md). +ScorpioFS is a remote-revision projection backend; BrewFS is modeled as a +persistent distributed-volume backend. Git objects, refs, index state, and +authoritative backend lifecycle state remain owned by Libra. + Each linked worktree is a directory containing its own real `.libra` gitdir — a local directory (not a symlink) that holds the worktree's private `HEAD`, index, and `HEAD` reflog, plus a `commondir` pointer to the shared storage and a stable `worktree_id`. The main worktree is the original repository directory. All worktrees share the same SQLite database, object store, branch/tag/remote refs, and configuration, but each keeps its own checked-out branch and staging state. (A worktree created by an older Libra version may still use the legacy shared-`.libra` symlink layout; run `libra worktree repair` to check.) Worktree metadata is persisted in a `worktrees.json` file inside the `.libra` storage directory. Each entry tracks the filesystem path, whether it is the main worktree, its lock status, and an optional lock reason. The state file is written atomically via a temporary file rename to prevent corruption. @@ -47,6 +55,36 @@ libra --json worktree add ../my-feature libra worktree add /tmp/libra-test ``` +### Subcommand: `scorpiofs attach` + +Creates or recovers an idempotent Antares mount, waits for readiness, and +attaches persistent Libra linked-worktree metadata to the returned mountpoint. +By default on Linux, Libra starts a resident worker that links the ScorpioFS +crate directly. Libra owns the worker and desired mount state in +`.libra/scorpiofs/state.json`; ScorpioFS owns only live filesystem +materialization. Libra also continues to own the index, objects, commits, +refs, fetch, and push. + +```bash +libra worktree scorpiofs attach \ + --config-path scorpio.toml \ + --remote-path /project/aardvark-dns \ + --job-id dev-aardvark +``` + +Pass `--endpoint http://127.0.0.1:2725/antares` to use an externally managed +ScorpioFS daemon as a compatibility transport instead. + +### Subcommand: `scorpiofs detach` + +Refuses to detach a dirty worktree, removes its persistent linked-worktree +metadata, and asks Antares to delete the mount by job ID. Repeated remote +cleanup is idempotent. + +```bash +libra worktree scorpiofs detach /var/lib/antares/mounts/ +``` + ### Subcommand: `list` List all registered worktrees and their state. `--porcelain` emits a stable, diff --git a/docs/development/integration/scorpiofs-worktree-backend.md b/docs/development/integration/scorpiofs-worktree-backend.md new file mode 100644 index 000000000..07d29d79d --- /dev/null +++ b/docs/development/integration/scorpiofs-worktree-backend.md @@ -0,0 +1,557 @@ +# ScorpioFS remote worktree backend + +Status: MVP implemented and validated against a real Mega-backed FUSE mount + +This document describes the ScorpioFS-specific adapter. The backend-neutral +contracts, BrewFS extension point, storage ownership, and process model are +defined in +[`worktree-storage-backends.md`](worktree-storage-backends.md). + +## Libra-owned state and direct crate execution + +Libra is the authoritative owner of ScorpioFS desired state. It persists the +worker identity and every requested mount under +`.libra/scorpiofs/state.json`, including lifecycle transitions through +`mounting`, `ready`, `unmounting`, and `recoverable_error`. + +On Linux, `worktree scorpiofs attach` starts or reuses a hidden +`libra scorpiofs-worker` process by default. That worker links the `scorpiofs` +crate directly and keeps FUSE sessions alive after the invoking CLI process +exits. ScorpioFS runs with external state ownership, so its in-memory mount +registry is an execution cache only: it must not persist, recover, or decide +the desired mount set. + +The HTTP transport remains available only as an explicit compatibility mode: + +```text +libra worktree scorpiofs attach \ + --endpoint http://127.0.0.1:2725/antares \ + --remote-path /project/aardvark-dns \ + --job-id aardvark-dns +``` + +Without `--endpoint`, Libra starts the crate-backed worker using +`--config-path scorpio.toml`. When the final Libra-owned mount is detached, +Libra asks its worker to shut down gracefully. + +## Summary + +Libra integrates ScorpioFS as a remote-projection worktree backend. Libra +remains the only owner of version-control semantics and desired lifecycle +state. ScorpioFS owns remote file materialization, FUSE execution, changelist +layers, writable upper layers, and live mount sessions. + +The integration must not: + +- reimplement Git object, index, ref, merge, or transport logic in ScorpioFS; +- link the complete Libra application into ScorpioFS; +- mount Libra's FUSE worktree on top of a ScorpioFS FUSE mount; +- store the persistent Libra repository database inside an ephemeral Antares + upper layer; +- expose arbitrary Libra command execution through ScorpioFS's unauthenticated + HTTP API. + +## Ownership + +### Libra owns + +- repository identity; +- common object storage; +- the index; +- HEAD, branches, refs, and reflogs; +- commit and tree construction; +- status, diff, restore, checkout, merge, rebase, and stash semantics; +- remotes, credentials, fetch, pull, and push; +- hooks and signing; +- Mega single-commit push preflight. + +### ScorpioFS owns + +- the Mega-backed read-only base filesystem; +- lazy tree and blob materialization; +- optional changelist layers; +- per-mount writable upper layers; +- FUSE inode and file-handle lifecycle; +- mount creation, readiness, recovery, and deletion; +- efficient reporting of paths changed in the writable view; +- switching a mount to a different immutable base snapshot. + +### The integration layer owns + +- mapping a Libra linked worktree to a ScorpioFS mount; +- the local protocol and capability negotiation; +- lifecycle and operation locks; +- recreating the `.libra` worktree pointer after remount; +- coordinating base-snapshot changes for pull, switch, and reset; +- converting service errors into stable Libra errors. + +## Why the integration belongs in Libra + +Libra already exposes a library entry point and implements the full VCS command +surface. It also has linked-worktree scoping for local HEAD, index, FETCH_HEAD, +sequencer, rebase, and advisory state. + +ScorpioFS already exposes an Antares control plane and an isolated userspace +overlay. Making ScorpioFS a Libra worktree backend therefore adds one adapter +instead of duplicating VCS behavior. + +Libra's optional `worktree-fuse` feature is not used for this backend. That +feature creates a local overlay from a local lower directory. A ScorpioFS +backend is already a mounted remote overlay; nesting the two introduces +duplicate mount ownership, cleanup ambiguity, and unnecessary filesystem +overhead. + +## Storage layout + +Persistent Libra state lives outside the ScorpioFS mount: + +```text +/.libra/ +├── libra.db +├── objects/ +├── refs/ +└── worktrees/ + └── scorpiofs/ + └── / + ├── commondir + ├── worktree_id + ├── index + ├── HEAD + ├── FETCH_HEAD + └── backend.json +``` + +The mounted worktree contains only a reconstructable `.libra` gitdir pointer: + +```text +/.libra +``` + +The pointer resolves to the persistent worktree gitdir. It may be recreated +after every mount without changing repository history or worktree identity. + +`backend.json` contains no credentials: + +```json +{ + "schema_version": 1, + "backend": "scorpiofs", + "endpoint": "unix:///run/scorpiofs/control.sock", + "mount_id": "1a78c97f-68b7-4873-bfe2-2d67f3768b23", + "job_id": "build-123", + "remote_path": "/project/aardvark-dns", + "base_oid": "0123456789abcdef", + "cl": "1XFJ4PGK" +} +``` + +The canonical identity is a stable Libra `worktree_id`, not the transient +ScorpioFS `mount_id`. + +## User-facing command model + +The backend is managed through Libra: + +```text +libra worktree scorpiofs attach \ + --endpoint http://127.0.0.1:2725/antares \ + --remote-path /project/aardvark-dns \ + --job-id dev-aardvark + +libra worktree scorpiofs detach + +libra worktree list +libra worktree repair +libra worktree remove +``` + +After the worktree is ready, ordinary Libra commands run inside it: + +```text +libra status +libra add . +libra commit -m "..." +libra fetch origin +libra push --dry-run origin main:main +``` + +Backend-specific options belong to `worktree scorpiofs attach`; normal VCS +commands must not grow ScorpioFS-specific flags. + +An attached ScorpioFS worktree uses a private detached HEAD. Pushes from it +must therefore name both the remote and an explicit source/destination +refspec, such as `main:main`. A default push that needs Libra to infer the +current branch remains rejected. The `--dry-run` form validates Mega discovery +and the update plan without mutating the remote. + +The existing local-copy and optional local-FUSE worktree backends remain +compatible. A serialized worktree record gains a backward-compatible backend +descriptor whose default is `local`. + +## Control protocol + +### Transport + +Production integration uses a local Unix domain socket. A loopback HTTP endpoint +may be supported for development, but must require an explicit opt-in and must +not accept credentials or arbitrary commands. + +The first implementation may use the existing Antares loopback HTTP API behind +the backend client. The public Rust interface must hide the transport so it can +move to the Unix socket without changing command code. + +### Version negotiation + +Every client begins with: + +```json +{ + "protocol_version": 1, + "client": "libra", + "client_version": "0.19.40" +} +``` + +The service responds with: + +```json +{ + "protocol_version": 1, + "service": "scorpiofs", + "capabilities": [ + "mount.v1", + "ready.v1", + "changes.v1", + "base-snapshot.v1" + ] +} +``` + +Libra must fail closed when a required capability is unavailable. Optional +capabilities may select a documented slower fallback. + +### Mount request + +```json +{ + "job_id": "dev-aardvark", + "path": "/project/aardvark-dns", + "cl": null, + "base_oid": "0123456789abcdef" +} +``` + +Response: + +```json +{ + "mount_id": "1a78c97f-68b7-4873-bfe2-2d67f3768b23", + "mountpoint": "/var/lib/scorpiofs/antares/mnt/1a78c97f", + "base_oid": "0123456789abcdef", + "ready": false +} +``` + +Create-by-`job_id` remains idempotent. + +### Changed-path request + +Libra must not recursively scan the full remote monorepo for `status` or +`add .`. ScorpioFS reports candidate paths from the CL and writable upper +layers: + +```json +{ + "mount_id": "1a78c97f-68b7-4873-bfe2-2d67f3768b23", + "generation": 42, + "changes": [ + { "kind": "modified", "path": "src/lib.rs" }, + { "kind": "added", "path": "notes.txt" }, + { "kind": "deleted", "path": "src/old.rs" }, + { + "kind": "renamed", + "path": "src/new.rs", + "source_path": "src/previous.rs" + } + ] +} +``` + +This is a candidate set, not authoritative Git status. Libra still applies +ignore rules, pathspecs, index comparison, content hashing, rename policy, and +Git-compatible output. + +If `changes.v1` is unavailable, Libra may scan only the physical writable +layer. It must warn before falling back to a full mounted-tree walk. + +## Worktree creation transaction + +`libra worktree scorpiofs attach` performs: + +1. Validate the current Libra repository and requested remote path. +2. Negotiate backend capabilities. +3. Reserve a stable Libra worktree ID. +4. Create the persistent per-worktree gitdir and `backend.json`. +5. Request or recover the idempotent ScorpioFS mount. +6. Wait for mount readiness with a bounded timeout. +7. Attach the worktree gitdir pointer inside the mount. +8. Seed the worktree index from the selected Libra commit without populating + files. +9. Register the worktree in Libra's common worktree state. +10. Mark the backend record ready. + +Failures roll back in reverse order. A mount that cannot be deleted is recorded +as orphaned and reported with a repair command; it must not be silently +forgotten. + +## Status and detach consistency + +ScorpioFS is the authority for the writable-view candidate set. Libra does not +walk the full mounted monorepo during `status`, `add`, or the dirty-worktree +check that precedes `detach`. It asks `changes.v1` for candidate paths and then +applies normal Libra index, ignore, hashing, and rename rules to only those +paths. + +This rule is also required for correct cleanup. A FUSE mount can contain +implementation-local upper-layer artifacts that are not part of the +ScorpioFS-reported writable view. A raw recursive disk scan could therefore +make an already committed worktree impossible to detach. Detach uses the same +candidate-path collection as `status`; it still refuses to detach when the +service reports staged or unstaged Libra-visible changes. The persistent +`.libra` pointer is metadata, not a user file or staged change. + +## Normal command behavior + +### Status + +1. Resolve the current linked-worktree scope. +2. Load and validate `backend.json`. +3. Ask ScorpioFS for changed-path candidates. +4. Let Libra compare HEAD, index, and mounted file content. +5. Enumerate untracked files from the writable layer, not the remote base. + +### Add + +Libra applies pathspec and ignore semantics, reads selected mounted files, +writes blobs, and updates the worktree-local index. Deleted candidates stage as +deletions. ScorpioFS does not create Git objects. + +### Commit + +Libra builds trees and commits from the index, updates refs and reflogs, runs +hooks, and signs when configured. ScorpioFS is not involved. + +### Fetch + +Libra updates objects and refs. Fetch does not change the mounted base or +writable filesystem. + +### Push + +Libra performs transport, authentication, pack construction, and ref updates. +Mega-specific single-commit policy is checked in Libra before transport. +ScorpioFS does not receive credentials or push data. + +The initial validation uses an explicit refspec and `--dry-run`: + +```text +libra push --dry-run origin main:main +``` + +This proves remote discovery, Smart HTTP planning, detached-worktree refspec +handling, and Mega update preparation without changing the remote. A real +push remains an explicit user action and is subject to Libra's Mega +single-commit preflight. + +## Branch and base-snapshot changes + +There are two implementation stages. + +### Stage A: upper-layer delta + +Checkout, switch, restore, and reset write the difference between the immutable +base tree and target tree into the writable layer. Deletions use the overlay's +supported whiteout representation. + +This provides correctness first but may grow the upper layer after repeated +branch switches. + +### Stage B: transactional base switch + +For clean worktrees, Libra requests a new immutable base snapshot: + +1. Acquire the exclusive VCS and mount lifecycle leases. +2. Verify or stash local changes. +3. Prepare a replacement mount for the target commit. +4. Attach the existing persistent Libra worktree gitdir. +5. Verify the new view and index. +6. Atomically publish the replacement mount. +7. Delete the old mount. + +If publication fails, the old mount remains active. If old-mount cleanup fails, +the operation succeeds with an explicit orphan warning and repair record. + +`pull`, branch `switch`, `checkout`, `reset --hard`, and `rebase` must not update +Libra refs while leaving the user on an unrelated old base view. + +## Locks and lifecycle + +Each backend worktree has: + +- a shared read lease for status, diff, log, and read-only inspection; +- an exclusive VCS lease for add, commit, checkout, merge, rebase, and reset; +- an exclusive lifecycle lease for mount, base switch, repair, and unmount. + +Unmount refuses to race with a VCS operation. Shutdown stops admitting new +operations, waits for bounded graceful completion, persists recovery state, and +then unmounts. + +The backend state machine is: + +```text +Detached + -> Mounting + -> Ready + -> SwitchingBase + -> Ready + -> Unmounting + -> Detached + +Any state may enter RecoverableError. +``` + +## Error contract + +Backend failures map to stable Libra error categories: + +- backend unavailable; +- protocol incompatible; +- mount rejected; +- mount readiness timeout; +- stale mount identity; +- changed-path generation lost; +- base snapshot unavailable; +- worktree busy; +- cleanup incomplete; +- backend state corrupt. + +Messages include the operation, worktree path, job ID, and recovery action. They +must not include tokens, credential-bearing URLs, signing material, or file +contents. + +## Security + +- Prefer a Unix socket owned by the current user or service group. +- Validate that every returned mountpoint is inside the configured ScorpioFS + mount root. +- Validate that every worktree gitdir is inside Libra common storage. +- Never pass credentials on a process command line. +- Do not expose a generic "run Libra command" ScorpioFS endpoint. +- Treat remote paths, CL names, mount IDs, and changed paths as untrusted. +- Reject absolute changed paths and paths containing parent traversal. +- Preserve the unauthenticated Antares API warning until a protected transport + is available. + +## Compatibility + +- Existing Libra repositories and local worktrees default to backend `local`. +- Existing serialized worktree records load without migration. +- `worktree-fuse` remains optional and independent. +- Native Git fallback remains available for explicitly unsupported Libra + behavior. +- Hooks remain under Libra metadata; no synthetic `.git/hooks` directory is + created. +- Advanced commands that are not safe in linked worktrees remain guarded until + their state is worktree-scoped. + +## Implementation phases + +### Phase 1: backend substrate + +- Add versioned backend types and persistent backend records. +- Add a transport-independent ScorpioFS client. +- Add backend-aware worktree registration, list, repair, and removal. +- Use the existing Antares HTTP API for mount, readiness, and delete. + +### Phase 2: core VCS workflow + +- Run status, add, commit, fetch, and push in the attached linked worktree. +- Add Mega single-commit push preflight. +- Add lifecycle locking and recovery tests. + +### Phase 3: changed paths + +- Add `changes.v1` to ScorpioFS. +- Consume candidates in Libra status and add. +- Add generation, overflow, rename, deletion, and ignore tests. + +### Phase 4: mutable worktree operations + +- Validate restore, path checkout, switch, reset, merge, and stash on the + writable overlay. +- Add whiteout and metadata-operation coverage. + +### Phase 5: immutable base snapshots + +- Add commit-addressed bases and transactional base switching to ScorpioFS. +- Integrate pull, branch switching, reset, and rebase. +- Add crash recovery and orphan cleanup. + +### Phase 6: production validation + +- **Validated** mount/open, status, add, commit, fetch, explicit push planning, + and detach against `project/aardvark-dns` on Mega. The test ran in an + isolated Linux user and mount namespace and verified that + `.libra/scorpiofs/state.json` has an empty `mounts` map after detach. +- **Implemented test coverage** includes endpoint validation, state locking, + lifecycle transitions, changed-path validation, attach idempotency, and the + detach regression where an unreported local artifact must not block a clean + ScorpioFS worktree. +- Validate Buck2 builds on the same mount. +- Test restart recovery, concurrent worktrees, cancellation, and cleanup. +- Document native Git fallback and operational diagnostics. + +## Verified command trace + +The end-to-end test executes this sequence against the deployed Mega +subrepository: + +```text +libra clone https://git.rk8s.xuanwu.openatom.cn/project/aardvark-dns control +libra worktree scorpiofs attach --config-path scorpio.toml \ + --remote-path /project/aardvark-dns --job-id +cd +libra status --porcelain +libra fetch origin +libra add libra-scorpiofs-e2e.txt +libra commit -m "test: validate Libra ScorpioFS backend" +libra push --dry-run origin main:main +libra worktree scorpiofs detach +``` + +The test creates only a temporary local commit in the isolated worktree. It +does not publish a remote commit or alter the deployed Mega branch. + +## Deliberate current limits + +- The compatibility HTTP control transport is still supported; a protected + Unix-socket transport is the production target. +- Libra uses ScorpioFS as a POSIX data plane and does not duplicate Git logic + in the filesystem service. +- Automatic transactional base switching, restart recovery, and concurrent + mount stress are not yet validated end-to-end. +- Buck2-on-ScorpioFS validation remains separate work; passing VCS lifecycle + tests is not a Buck2 compatibility guarantee. + +## Acceptance criteria + +The first production-capable milestone is complete when: + +- a ScorpioFS mount attaches as a persistent Libra linked worktree; +- normal Libra `status`, `add`, `commit`, `fetch`, and `push` work inside it; +- unmount/remount preserves Libra metadata and worktree identity; +- status and `add .` do not walk the full remote monorepo; +- no credentials pass through ScorpioFS; +- mount and VCS operations cannot race destructively; +- failures leave a diagnosable and repairable state; +- focused unit tests and a real Mega/FUSE end-to-end test pass. diff --git a/docs/development/integration/worktree-storage-backends.md b/docs/development/integration/worktree-storage-backends.md new file mode 100644 index 000000000..7221ce2e8 --- /dev/null +++ b/docs/development/integration/worktree-storage-backends.md @@ -0,0 +1,244 @@ +# Worktree storage backend architecture + +Status: backend-neutral substrate implemented; ScorpioFS adapter implemented; +BrewFS SDK runtime boundary implemented + +Implementation labels in this document: + +- **Implemented**: present in this Libra branch and covered by automated tests. +- **Validated**: exercised against a deployed ScorpioFS and Mega service. +- **Planned**: an architectural direction, not a currently available command. + +## Purpose + +Libra supports worktrees whose POSIX files may come from different storage +systems. The Git model must remain identical across local directories, +repository-aware lazy projections such as ScorpioFS, and persistent distributed +volumes such as BrewFS. + +The architecture separates three planes: + +```text +Git plane + Libra refs, index, objects, commits, fetch, and push + +Control plane + Libra worktree coordinator, desired state, locks, worker supervision, + backend capability negotiation, recovery, and cleanup + +Data plane + Local directory, ScorpioFS FUSE mount, BrewFS FUSE mount, or a future + POSIX-visible backend +``` + +Libra owns the first two planes. A backend driver owns only its data-plane +session. + +## Core contract + +`internal::worktree_backend` defines: + +- `BackendKind`; +- `BackendCapabilities`; +- `BackendMountSource`; +- `BackendMountRequest`; +- `BackendMountSession`; +- `BackendHealth`; +- `BackendLifecycle`; +- `WorktreeBackendDriver`; +- `BackendRegistry`. + +The driver contract contains lifecycle operations rather than a duplicate +filesystem API: + +```text +mount +health +changed_paths (optional) +flush (optional) +unmount +recover +``` + +Build tools, editors, and ordinary Libra commands access the mounted POSIX +path. They do not call a backend SDK for individual reads and writes. + +## Capability model + +| Capability | Local | ScorpioFS | BrewFS | +|---|---:|---:|---:| +| POSIX worktree | yes | yes | yes | +| Revision projection | no | yes | no | +| Native changed paths | no | yes | no | +| Persistent volume | no | no | yes | +| Multi-client storage | no | no | yes | +| Flush before commit | no | no | yes | + +Command code must branch on capabilities, not concrete backend names. + +## Backend source types + +The generic mount request distinguishes: + +```text +local_directory +remote_projection +persistent_volume +``` + +ScorpioFS accepts `remote_projection`, including a monorepo path, base object +ID, and optional change layer. + +BrewFS accepts `persistent_volume`, including a volume and optional subpath. +BrewFS does not inherently project a Mega commit. Libra must populate or import +the selected Git tree before treating a new BrewFS volume as a worktree. + +## Process model + +The target process model is: + +```text +libra CLI + -> Libra worktree supervisor + -> ScorpioFS worker linked to the ScorpioFS crate + -> BrewFS worker linked to the BrewFS crate +``` + +FUSE sessions outlive an individual CLI invocation. Backend workers also +isolate filesystem crashes and dependency runtimes from the Git command +process. Unix domain sockets should replace loopback HTTP as the default local +control transport; the current ScorpioFS loopback protocol remains a +compatibility transport during migration. + +Libra owns worker selection, desired state, and recovery. ScorpioFS owns only +live mount state. The configured endpoint is a control-plane address, never a +Git remote and never a credential container. + +## Persistent layout + +Libra metadata remains on a host-local filesystem: + +```text +/.libra/ + objects/ + refs/ + worktrees/ + backends/ + desired-state.json + state.lock +``` + +Backend caches and runtime state remain separate: + +```text +~/.cache/libra/backends/scorpiofs// +~/.cache/libra/backends/brewfs// +/run/user//libra/ +``` + +`.libra` must not be stored in a ScorpioFS upper layer or a BrewFS volume. A +mounted worktree contains only a reconstructable `.libra` pointer to its +host-local per-worktree gitdir. + +### State reconciliation + +The persistent Libra record is authoritative. A live backend mount is an +execution resource that may disappear after a process or machine restart. +Recovery must read and validate host-local Libra state, query the backend by +its durable cleanup key, recreate the pointer only after containment checks, +and record failures as recoverable rather than silently dropping desired state. + +## ScorpioFS adapter + +`ScorpioFsDriver` implements `WorktreeBackendDriver` by translating generic +remote-projection requests into Antares mount requests. It exposes native +changed-path candidates and idempotent cleanup by job ID. + +The existing ScorpioFS command and state files remain compatible while command +orchestration is incrementally moved onto the generic driver. + +## BrewFS SDK boundary + +`BrewFsDriver` accepts a `BrewFsRuntime`. The runtime is responsible for: + +- constructing BrewFS metadata and object backends from named profiles; +- retaining the BrewFS SDK client and FUSE handle; +- mounting a persistent volume; +- reporting health; +- draining writes before Git commit publication; +- unmounting the session. + +Configuration stores profile names, not credentials: + +```toml +[backends.brewfs.team] +volume = "team-workspace" +mount_root = "/home/alice/libra-workspaces" +metadata_profile = "production-redis" +data_profile = "production-s3" +``` + +BrewFS 0.1.2 exports filesystem clients but keeps the complete mount assembly +used by its binary private. Libra therefore does not claim direct embedded +mount support until BrewFS exports a stable mount builder/session API. The +runtime trait is the integration seam for that API. + +The minimum upstream SDK shape Libra needs is: + +```rust +pub struct MountBuilder { /* metadata, object, cache, and FUSE options */ } + +impl MountBuilder { + pub async fn mount(self, mountpoint: &Path) -> Result; +} + +pub struct MountedFs { /* SDK client and FUSE handle */ } + +impl MountedFs { + pub fn client(&self) -> &brewfs::Client; + pub async fn health(&self) -> Result; + pub async fn flush(&self) -> Result<()>; + pub async fn unmount(self) -> Result<()>; +} +``` + +The handle must retain all background workers and expose bounded graceful +shutdown. Configuration construction must accept credential references or +preconstructed backends so Libra never serializes secrets into `.libra`. + +## Commit durability + +For a backend with `flush_before_commit`, Libra must: + +1. finish index updates; +2. request backend flush; +3. wait for durable completion or fail the commit; +4. construct and publish the Git commit; +5. update refs and reflogs. + +This ordering prevents a commit from naming worktree content that remains only +in an unflushed client buffer. + +## Migration + +1. Keep existing `worktree scorpiofs attach/detach` behavior. +2. Route ScorpioFS changed-path discovery through `ScorpioFsDriver`. +3. Move attach, health, recovery, and detach orchestration to the generic + driver. +4. Introduce a generic `worktree create --backend` command. +5. Add the BrewFS crate after its stable mount session API is available. +6. Implement a BrewFS SDK runtime and persistent-volume checkout/import flow. +7. Migrate legacy `.libra/scorpiofs/state.json` into versioned backend-neutral + desired state. + +## Current validation boundary + +The ScorpioFS implementation is validated for an attached remote projection: +`attach`, `status`, `fetch`, `add`, `commit`, explicit-refspec +`push --dry-run`, and `detach`. Validation uses the deployed Mega +`project/aardvark-dns` repository inside an isolated Linux user and mount +namespace. + +This does not claim every mutable Git operation or automatic base switch is +production-ready. Transactional base switching, restart recovery, concurrent +mount stress, and a direct embedded BrewFS SDK mount remain planned work. diff --git a/src/cli.rs b/src/cli.rs index d99282bc2..4ac419097 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -737,6 +737,8 @@ enum Commands { hide = true )] Hooks(command::hooks::HooksArgs), + #[command(about = "Run a Libra-owned ScorpioFS worker", hide = true)] + ScorpiofsWorker(command::scorpiofs_worker::ScorpioFsWorkerArgs), } #[derive(Subcommand, Debug)] @@ -2183,6 +2185,9 @@ async fn parse_async_scoped(argv: Vec) -> CliResult<()> { command::agent::investigate::execute_safe(cmd_args, &output).await? } Commands::Hooks(cmd_args) => command::hooks::execute_safe(cmd_args, &output).await?, + Commands::ScorpiofsWorker(cmd_args) => { + command::scorpiofs_worker::execute_safe(cmd_args).await? + } Commands::Bisect(bisect_cmd) => { command::bisect::execute_safe(bisect_cmd, &output).await? } diff --git a/src/command/add.rs b/src/command/add.rs index f6c73b13a..d99ae09e3 100644 --- a/src/command/add.rs +++ b/src/command/add.rs @@ -534,13 +534,27 @@ pub async fn run_add(args: &AddArgs) -> CliResult { ignore_case, }; - let (mut visible_changes, mut ignored_changes) = if args.force { - status::changes_to_be_staged_split_force_with_ignore_case(ignore_case) - .map_err(|source| AddError::Status { source })? - } else { - status::changes_to_be_staged_split_safe_with_ignore_case(ignore_case) - .map_err(|source| AddError::Status { source })? - }; + let backend_changes = crate::internal::scorpiofs_backend::current_worktree_changes() + .await + .map_err(|error| { + CliError::fatal(format!( + "failed to query ScorpioFS worktree changes: {error}" + )) + })?; + let backend_candidates = backend_changes + .as_ref() + .map(|changes| changes.candidate_paths()); + let (mut visible_changes, mut ignored_changes) = + if let Some(candidates) = backend_candidates.as_deref() { + status::changes_to_be_staged_split_for_paths_with_ignore_case(ignore_case, candidates) + .map_err(|source| AddError::Status { source })? + } else if args.force { + status::changes_to_be_staged_split_force_with_ignore_case(ignore_case) + .map_err(|source| AddError::Status { source })? + } else { + status::changes_to_be_staged_split_safe_with_ignore_case(ignore_case) + .map_err(|source| AddError::Status { source })? + }; if args.force { visible_changes.extend(ignored_changes.clone()); ignored_changes = Changes::default(); diff --git a/src/command/mod.rs b/src/command/mod.rs index 058b96eaa..a6fadd131 100644 --- a/src/command/mod.rs +++ b/src/command/mod.rs @@ -105,6 +105,7 @@ pub mod rev_parse; pub mod revert; pub mod revision; pub mod sandbox; +pub mod scorpiofs_worker; pub mod service; pub mod shortlog; pub mod show; diff --git a/src/command/push.rs b/src/command/push.rs index 31b97b64b..6c0e77eb9 100644 --- a/src/command/push.rs +++ b/src/command/push.rs @@ -785,12 +785,16 @@ fn validate_push_args(args: &PushArgs) -> Result<(), PushError> { Ok(()) } -async fn validate_local_refspecs(args: &PushArgs, current_branch: &str) -> Result<(), PushError> { +async fn validate_local_refspecs( + args: &PushArgs, + current_branch: Option<&str>, +) -> Result<(), PushError> { if args.mirror { return Ok(()); } if args.refspecs.is_empty() && !args.tags { + let current_branch = current_branch.ok_or(PushError::DetachedHead)?; resolve_local_ref(current_branch).await?; } @@ -819,13 +823,14 @@ pub async fn run_push(args: PushArgs, output: &OutputConfig) -> Result name, - Head::Detached(_) => return Err(PushError::DetachedHead), + Head::Branch(name) => Some(name), + Head::Detached(_) => None, }; let repository = match args.repository.clone() { Some(repo) => repo, None => { - let remote = ConfigKv::get_remote(¤t_branch).await.ok().flatten(); + let current_branch = current_branch.as_deref().ok_or(PushError::DetachedHead)?; + let remote = ConfigKv::get_remote(current_branch).await.ok().flatten(); match remote { Some(remote) => remote, None => return Err(PushError::NoRemoteConfigured), @@ -850,7 +855,7 @@ pub async fn run_push(args: PushArgs, output: &OutputConfig) -> Result Result