diff --git a/crates/ledger-mcp/src/server_tests.rs b/crates/ledger-mcp/src/server_tests.rs index af2ab17..108dd4b 100644 --- a/crates/ledger-mcp/src/server_tests.rs +++ b/crates/ledger-mcp/src/server_tests.rs @@ -1159,7 +1159,9 @@ fn logged(tag: &str) -> (BooksServer, TestDir) { } /// Books whose access log can't be written: its file is a FIFO no one -/// reads, so every record fails (as on a full disk). +/// reads, so every record fails (as on a full disk). Unix only: there is +/// no FIFO in a file's place on Windows. +#[cfg(unix)] fn unwritable(tag: &str) -> (BooksServer, TestDir) { let books = unit_dir(tag); let store = store(&books); @@ -1430,7 +1432,9 @@ fn a_call_the_books_close_or_stop_under_is_refused_as_not_run() { /// access.rs INVARIANT 3: a record that can't be written stops the /// books, and the answer says so; a call or read refused as not run /// (closing) keeps its not-run code, so the bridge still sends it -/// again; anything else is an error in words. +/// again; anything else is an error in words. Unix only: its fixture is a +/// FIFO (the refusal path itself is the same on every system). +#[cfg(unix)] #[test] fn an_access_record_that_cant_be_written_stops_the_books_and_says_so() { rt().block_on(async { diff --git a/crates/ledger-store/src/backup.rs b/crates/ledger-store/src/backup.rs index 3c3d02f..6117675 100644 --- a/crates/ledger-store/src/backup.rs +++ b/crates/ledger-store/src/backup.rs @@ -223,7 +223,13 @@ impl Git { fn run(&self, what: &str, args: &[&std::ffi::OsStr]) -> Result { use std::sync::atomic::{AtomicU64, Ordering}; let mut c = self.command(); - c.args(args); + // A path made canonical on Windows carries `\\?\`, which git refuses. + c.args(args.iter().map(|a| match a.to_str() { + Some(text) if text.contains(r"\\?\") => { + std::ffi::OsString::from(crate::root::without_extended_prefix(text)) + } + _ => (*a).to_os_string(), + })); #[cfg(unix)] { use std::os::unix::process::CommandExt; diff --git a/crates/ledger-store/src/root.rs b/crates/ledger-store/src/root.rs index 029b342..7d9f151 100644 --- a/crates/ledger-store/src/root.rs +++ b/crates/ledger-store/src/root.rs @@ -314,6 +314,23 @@ pub fn private_book_folder(book_dir: &Path, name: &str) -> Result String { + text.replace(r"\\?\UNC\", r"\\").replace(r"\\?\", "") +} + +/// [`without_extended_prefix`] for a path (the same path on Unix). +pub fn plain_path(path: &Path) -> PathBuf { + match path.to_str() { + Some(text) if text.contains(r"\\?\") => PathBuf::from(without_extended_prefix(text)), + _ => path.to_path_buf(), + } +} + /// The longest socket path used as it is: a Unix socket's address holds /// 104 bytes on macOS and 108 on Linux, the terminating zero included. const SOCKET_PATH_MAX: usize = 100; @@ -2264,6 +2281,21 @@ fn copy_flat_private(from: &Path, to: &Path) -> Result { mod unit { use super::*; + #[test] + fn the_extended_length_prefix_is_taken_off_paths() { + assert_eq!(without_extended_prefix(r"\\?\C:\a\b"), r"C:\a\b"); + assert_eq!( + without_extended_prefix(r"--git-dir=\\?\C:\a\archive.git"), + r"--git-dir=C:\a\archive.git" + ); + assert_eq!( + without_extended_prefix(r"\\?\UNC\server\share\x"), + r"\\server\share\x" + ); + assert_eq!(without_extended_prefix("/usr/bin/git"), "/usr/bin/git"); + assert_eq!(plain_path(Path::new("/a/b")), Path::new("/a/b")); + } + #[test] fn a_buffer_grows_while_the_lookup_says_too_small() { // Too small twice, then found: 16, 32, 64 KiB tried, in order.