fix: normalize \\?\-prefixed Verbatim paths when creating relative symlinks on Windows (#4067)

This commit is contained in:
三咲雅 misaki masa 2026-06-20 12:07:33 +08:00 committed by GitHub
parent ef5db6e257
commit feeea673f1
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 27 additions and 1 deletions

View file

@ -32,6 +32,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/):
### Fixed
- Normalize `\\?\`-prefixed Verbatim paths when creating relative symlinks on Windows ([#4067])
- Keep package hashes indifferent to line endings when `ya pkg` pulls packages ([#4064])
- Use WebP as `magick` preset preloader cache format to keep image transparency ([#4065])
@ -1753,3 +1754,4 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/):
[#4040]: https://github.com/sxyazi/yazi/pull/4040
[#4064]: https://github.com/sxyazi/yazi/pull/4064
[#4065]: https://github.com/sxyazi/yazi/pull/4065
[#4067]: https://github.com/sxyazi/yazi/pull/4067

View file

@ -1,3 +1,5 @@
use std::path::PrefixComponent;
use anyhow::{Result, bail};
use yazi_shared::path::{PathBufDyn, PathCow, PathDyn, PathLike};
@ -28,7 +30,7 @@ fn path_relative_to_impl<'a>(from: PathCow<'_>, to: PathCow<'a>) -> Result<PathC
let (f_head, t_head) = loop {
match (f_it.next(), t_it.next()) {
(Some(RootDir), Some(RootDir)) => {}
(Some(Prefix(a)), Some(Prefix(b))) if a == b => {}
(Some(Prefix(a)), Some(Prefix(b))) if path_prefix_eq(a, b) => {}
(Some(Prefix(_) | RootDir), _) | (_, Some(Prefix(_) | RootDir)) => {
return Ok(to);
}
@ -45,6 +47,20 @@ fn path_relative_to_impl<'a>(from: PathCow<'_>, to: PathCow<'a>) -> Result<PathC
Ok(buf.into())
}
#[cfg(windows)]
fn path_prefix_eq(a: PrefixComponent<'_>, b: PrefixComponent<'_>) -> bool {
use std::path::Prefix::*;
match (a.kind(), b.kind()) {
(Disk(a), VerbatimDisk(b)) | (VerbatimDisk(a), Disk(b)) => a == b,
(UNC(a1, a2), VerbatimUNC(b1, b2)) | (VerbatimUNC(a1, a2), UNC(b1, b2)) => a1 == b1 && a2 == b2,
_ => a == b,
}
}
#[cfg(not(windows))]
fn path_prefix_eq(a: PrefixComponent<'_>, b: PrefixComponent<'_>) -> bool { a == b }
#[cfg(test)]
mod tests {
use yazi_shared::path::PathDyn;
@ -80,6 +96,14 @@ mod tests {
(r"C:\a\b\d", r"C:\a\b\c", r"..\c"),
(r"C:\a\b\c", r"C:\a", r"..\.."),
(r"C:\a\b\b", r"C:\a\a\b", r"..\..\a\b"),
// Verbatim (`\\?\`-prefixed) paths
(r"\\?\C:\a\b", r"C:\a\b\c", "c"),
(r"C:\a\b", r"\\?\C:\a\b\c", "c"),
(r"\\?\C:\a\b", r"\\?\C:\a\b\c", "c"),
(r"\\?\C:\a\b\c", r"C:\a\b", r".."),
(r"\\?\C:\a\b\d", r"C:\a\b\c", r"..\c"),
(r"\\?\C:\a\b\c", r"\\?\C:\a", r"..\.."),
(r"\\?\C:\a\b", r"D:\a\b\c", r"D:\a\b\c"),
];
for (from, to, expected) in cases {