From 5229fb8a597240825b6aa99001a8079bcc0264f0 Mon Sep 17 00:00:00 2001 From: Lorenz Leutgeb Date: Fri, 22 Aug 2025 10:48:48 +0200 Subject: [PATCH] fix: Normalize filesystem paths with `dunce` Microsoft Windows has Universal Naming Convention (UNC) paths, see The Rust standard library normalizes paths to this form when calling `std::fs::canonicalize`, see However, some programs, do not parse these paths correctly, including our ally `git`: $ git clone \\?\C:\Users\lorenz\tmp Cloning into 'tmp'... ssh: Could not resolve hostname \\\\?\\c: No such host is known. fatal: Could not read from remote repository. Please make sure you have the correct access rights and the repository exists. The `dunce` crate solves the problem of having to deal with normalization and avoiding weird UNC paths for us. Note that on non-Windows platforms, it *behaves exactly like* `std::fs::canonicalize`! --- Cargo.lock | 9 +++++++++ Cargo.toml | 1 + crates/radicle-cli/Cargo.toml | 1 + crates/radicle-cli/src/commands/init.rs | 5 +---- crates/radicle-remote-helper/Cargo.toml | 1 + crates/radicle-remote-helper/src/lib.rs | 2 +- crates/radicle/Cargo.toml | 1 + crates/radicle/src/profile.rs | 2 +- crates/radicle/src/rad.rs | 6 ++---- 9 files changed, 18 insertions(+), 10 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0cbb7436..36dc6177 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -758,6 +758,12 @@ dependencies = [ "litrs", ] +[[package]] +name = "dunce" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" + [[package]] name = "dyn-clone" version = "1.0.17" @@ -2662,6 +2668,7 @@ dependencies = [ "colored", "crossbeam-channel", "cyphernet", + "dunce", "emojis", "fast-glob", "fastrand", @@ -2698,6 +2705,7 @@ version = "0.15.0" dependencies = [ "anyhow", "chrono", + "dunce", "git-ref-format", "human-panic", "lexopt", @@ -2901,6 +2909,7 @@ dependencies = [ name = "radicle-remote-helper" version = "0.12.0" dependencies = [ + "dunce", "log", "radicle", "radicle-cli", diff --git a/Cargo.toml b/Cargo.toml index 6732827d..9eef4729 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,6 +27,7 @@ chrono = { version = "0.4.26", default-features = false } colored = "2.1.0" crossbeam-channel = "0.5.6" cyphernet = "0.5.2" +dunce = "1.0.5" fastrand = { version = "2.0.0", default-features = false } git2 = { version = "0.19.0", default-features = false } human-panic = "2" diff --git a/crates/radicle-cli/Cargo.toml b/crates/radicle-cli/Cargo.toml index b2cd526e..77dee189 100644 --- a/crates/radicle-cli/Cargo.toml +++ b/crates/radicle-cli/Cargo.toml @@ -16,6 +16,7 @@ path = "src/main.rs" [dependencies] anyhow = { workspace = true } chrono = { workspace = true, features = ["clock", "std"] } +dunce = { workspace = true } git-ref-format = { version = "0.3.0", features = ["macro"] } human-panic.workspace = true lexopt = { workspace = true } diff --git a/crates/radicle-cli/src/commands/init.rs b/crates/radicle-cli/src/commands/init.rs index 99cb4146..1e966e0b 100644 --- a/crates/radicle-cli/src/commands/init.rs +++ b/crates/radicle-cli/src/commands/init.rs @@ -212,10 +212,7 @@ pub fn init( options: Options, profile: &profile::Profile, ) -> anyhow::Result<()> { - let path = repo - .workdir() - .unwrap_or_else(|| repo.path()) - .canonicalize()?; + let path = dunce::canonicalize(repo.workdir().unwrap_or_else(|| repo.path()))?; let interactive = options.interactive; let default_branch = match find_default_branch(&repo) { diff --git a/crates/radicle-remote-helper/Cargo.toml b/crates/radicle-remote-helper/Cargo.toml index 6a31fdae..8354a127 100644 --- a/crates/radicle-remote-helper/Cargo.toml +++ b/crates/radicle-remote-helper/Cargo.toml @@ -14,6 +14,7 @@ name = "git-remote-rad" path = "src/git-remote-rad.rs" [dependencies] +dunce = { workspace = true } log = { workspace = true } radicle = { workspace = true } radicle-cli = { workspace = true } diff --git a/crates/radicle-remote-helper/src/lib.rs b/crates/radicle-remote-helper/src/lib.rs index bdc541b2..7f6a87cd 100644 --- a/crates/radicle-remote-helper/src/lib.rs +++ b/crates/radicle-remote-helper/src/lib.rs @@ -162,7 +162,7 @@ pub fn run(profile: radicle::Profile) -> Result<(), Error> { // N.b. `working` is the `.git` folder and `fetch::run` // requires the working directory. - let working = working.map_err(|_| Error::NoGitDir)?.canonicalize()?; + let working = dunce::canonicalize(working.map_err(|_| Error::NoGitDir)?)?; let working = working.parent().ok_or_else(|| Error::NoWorkingCopy { path: working.clone(), })?; diff --git a/crates/radicle/Cargo.toml b/crates/radicle/Cargo.toml index ac89dac8..637cbb56 100644 --- a/crates/radicle/Cargo.toml +++ b/crates/radicle/Cargo.toml @@ -22,6 +22,7 @@ chrono = { workspace = true, features = ["clock"], optional = true } colored = { workspace = true, optional = true } crossbeam-channel = { workspace = true } cyphernet = { workspace = true, features = ["tor", "dns", "p2p-ed25519"] } +dunce = { workspace = true } fast-glob = { version = "0.3.2" } fastrand = { workspace = true } git2 = { workspace = true, features = ["vendored-libgit2"] } diff --git a/crates/radicle/src/profile.rs b/crates/radicle/src/profile.rs index 7236399b..4ae3a66e 100644 --- a/crates/radicle/src/profile.rs +++ b/crates/radicle/src/profile.rs @@ -541,7 +541,7 @@ impl Home { fs::create_dir_all(path.clone())?; } let home = Self { - path: path.canonicalize()?, + path: dunce::canonicalize(path)?, }; for dir in &[home.storage(), home.keys(), home.node(), home.cobs()] { diff --git a/crates/radicle/src/rad.rs b/crates/radicle/src/rad.rs index c90d84c1..63a931f5 100644 --- a/crates/radicle/src/rad.rs +++ b/crates/radicle/src/rad.rs @@ -127,7 +127,7 @@ where })?, [ "push", - &format!("{}", stored.path().canonicalize()?.display()), + &format!("{}", dunce::canonicalize(stored.path())?.display()), &pushspec.to_string(), ], [], @@ -295,9 +295,7 @@ pub fn checkout, S: storage::ReadStorage>( "fetch", &format!( "{}", - stored - .path() - .canonicalize() + dunce::canonicalize(stored.path()) .map_err(CheckoutError::Fetch)? .display() ),