From a90dfe0dcaa2e92a742c7e4f2d8294ef538f0241 Mon Sep 17 00:00:00 2001 From: roboomp Date: Wed, 12 Aug 2026 19:26:59 +0000 Subject: [PATCH] fix(bash): preserved unicode in drive-alias translation Translated the /c and /mnt/c tail per char over the valid UTF-8 suffix instead of copying raw bytes as chars, so non-ASCII path components no longer mojibake. Removed the now-unused byte separator helper and added a non-ASCII regression. Fixes #8355 --- crates/vendor/brush-core/src/sys/fs.rs | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/crates/vendor/brush-core/src/sys/fs.rs b/crates/vendor/brush-core/src/sys/fs.rs index ce45b6e9c..1ac32444f 100644 --- a/crates/vendor/brush-core/src/sys/fs.rs +++ b/crates/vendor/brush-core/src/sys/fs.rs @@ -84,12 +84,16 @@ fn translate_unix_drive_path(path: &Path) -> Option { let bytes = raw.as_bytes(); let (drive, tail) = drive_alias_parts(bytes)?; + // `tail` is a suffix of the valid UTF-8 `raw` beginning at an ASCII `/` + // boundary, so it is itself valid UTF-8. Translate separators per `char` — + // iterating bytes would split multibyte scalars (e.g. `José` → `José`). + let tail = std::str::from_utf8(tail).ok()?; let mut native = String::with_capacity(3 + tail.len()); native.push(char::from(drive).to_ascii_uppercase()); native.push(':'); native.push('\\'); - for &byte in tail { - native.push(if is_path_separator(byte) { '\\' } else { char::from(byte) }); + for ch in tail.chars() { + native.push(if ch == '/' || ch == '\\' { '\\' } else { ch }); } Some(PathBuf::from(native)) } @@ -119,11 +123,6 @@ fn drive_alias_parts(bytes: &[u8]) -> Option<(u8, &[u8])> { None } -#[cfg(any(windows, test))] -const fn is_path_separator(byte: u8) -> bool { - byte == b'/' || byte == b'\\' -} - pub use super::platform::fs::*; /// Extension trait for path-related filesystem operations. @@ -189,6 +188,18 @@ mod tests { ); } + #[test] + fn drive_alias_tail_preserves_non_ascii_components() { + assert_eq!( + translate_unix_drive_path(Path::new("/c/Users/José/file")).as_deref(), + Some(Path::new("C:\\Users\\José\\file")), + ); + assert_eq!( + translate_unix_drive_path(Path::new("/mnt/d/项目/データ")).as_deref(), + Some(Path::new("D:\\项目\\データ")), + ); + } + #[test] fn pattern_drive_alias_roots_report_consumed_components() { assert_eq!(