Skip to content

Commit 0bb92dd

Browse files
committed
Normalize drive letters when resolving paths on Windows
When it comes to resolving paths on Windows, even though the underlying API expects drive letter prefixes to be uppercase, some sources (e.g. environment variables like `=C:`) won't normalize components, instead returning the value as-is. While this wouldn't be a problem normally as NTFS is case-insensitive on Windows, this introduces duplicates in the database when adding new entries via `zoxide add`: ```batchfile prompt > zoxide query --list D:\ d:\ D:\coding d:\coding D:\coding\.cloned d:\coding\.cloned ``` This is a cherry-pick from ajeetdsouza#567; see also rust-lang/rust-analyzer#14683. Signed-off-by: mataha <[email protected]>
1 parent dbe6f18 commit 0bb92dd

File tree

2 files changed

+42
-10
lines changed

2 files changed

+42
-10
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1818
- zsh: better cd completions.
1919
- elvish: `z -` now work as expected.
2020
- Lazily delete excluded directories from the database.
21+
- Normalize drive letters when resolving paths on Windows.
2122

2223
## [0.9.4] - 2024-02-21
2324

src/util.rs

+41-10
Original file line numberDiff line numberDiff line change
@@ -292,43 +292,74 @@ pub fn resolve_path(path: impl AsRef<Path>) -> Result<PathBuf> {
292292
}
293293

294294
fn get_drive_path(drive_letter: u8) -> PathBuf {
295-
format!(r"{}:\", drive_letter as char).into()
295+
format!(r"{}:\", patch_drive_letter(drive_letter)).into()
296296
}
297297

298298
fn get_drive_relative(drive_letter: u8) -> Result<PathBuf> {
299299
let path = current_dir()?;
300300
if Some(drive_letter) == get_drive_letter(&path) {
301-
return Ok(path);
301+
return Ok(patch_drive_prefix(path));
302302
}
303303

304-
if let Some(path) = env::var_os(format!("={}:", drive_letter as char)) {
305-
return Ok(path.into());
304+
if let Some(path) = env::var_os(format!("={}:", patch_drive_letter(drive_letter))) {
305+
return Ok(patch_drive_prefix(path.into()));
306306
}
307307

308308
let path = get_drive_path(drive_letter);
309309
Ok(path)
310310
}
311311

312+
fn patch_drive_letter(drive_letter: u8) -> char {
313+
drive_letter.to_ascii_uppercase() as char
314+
}
315+
316+
// https://github.com/rust-lang/rust-analyzer/pull/14689
317+
fn patch_drive_prefix(path: PathBuf) -> PathBuf {
318+
let mut components = path.components();
319+
320+
match components.next() {
321+
Some(Component::Prefix(prefix)) => {
322+
let prefix = match prefix.kind() {
323+
Prefix::Disk(drive_letter) => {
324+
format!(r"{}:", patch_drive_letter(drive_letter))
325+
}
326+
Prefix::VerbatimDisk(drive_letter) => {
327+
format!(r"\\?\{}:", patch_drive_letter(drive_letter))
328+
}
329+
_ => return path,
330+
};
331+
332+
let mut path = PathBuf::default();
333+
path.push(prefix);
334+
path.extend(components);
335+
path
336+
}
337+
_ => path,
338+
}
339+
}
340+
312341
match components.peek() {
313342
Some(Component::Prefix(prefix)) => match prefix.kind() {
314343
Prefix::Disk(drive_letter) => {
315-
let disk = components.next().unwrap();
344+
components.next();
316345
if components.peek() == Some(&Component::RootDir) {
317-
let root = components.next().unwrap();
318-
stack.push(disk);
319-
stack.push(root);
346+
components.next();
347+
base_path = get_drive_path(drive_letter);
320348
} else {
321349
base_path = get_drive_relative(drive_letter)?;
322-
stack.extend(base_path.components());
323350
}
351+
352+
stack.extend(base_path.components());
324353
}
325354
Prefix::VerbatimDisk(drive_letter) => {
326355
components.next();
327356
if components.peek() == Some(&Component::RootDir) {
328357
components.next();
358+
base_path = get_drive_path(drive_letter);
359+
} else {
360+
bail!("illegal path: {}", path.display());
329361
}
330362

331-
base_path = get_drive_path(drive_letter);
332363
stack.extend(base_path.components());
333364
}
334365
_ => bail!("invalid path: {}", path.display()),

0 commit comments

Comments
 (0)