Skip to content

Commit 7d35206

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 3022cf3 commit 7d35206

File tree

3 files changed

+51
-16
lines changed

3 files changed

+51
-16
lines changed

CHANGELOG.md

+4
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1313

1414
- Nushell: support for v0.86.0.
1515

16+
### Fixed
17+
18+
- Normalize drive letters when resolving paths on Windows.
19+
1620
## [0.9.2] - 2023-08-04
1721

1822
### Added

src/shell.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -196,7 +196,7 @@ mod tests {
196196
}
197197

198198
#[apply(opts)]
199-
fn posix_shellcheck_(cmd: Option<&str>, hook: InitHook, echo: bool, resolve_symlinks: bool) {
199+
fn posix_shellcheck(cmd: Option<&str>, hook: InitHook, echo: bool, resolve_symlinks: bool) {
200200
let opts = Opts { cmd, hook, echo, resolve_symlinks };
201201
let source = Posix(&opts).render().unwrap();
202202

src/util.rs

+46-15
Original file line numberDiff line numberDiff line change
@@ -291,44 +291,75 @@ pub fn resolve_path(path: impl AsRef<Path>) -> Result<PathBuf> {
291291
}
292292
}
293293

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

298-
fn get_drive_relative(drive_letter: u8) -> Result<PathBuf> {
298+
fn get_drive_relative_path(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

308-
let path = get_drive_path(drive_letter);
308+
let path = get_drive_prefix_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_prefix_path(drive_letter);
320348
} else {
321-
base_path = get_drive_relative(drive_letter)?;
322-
stack.extend(base_path.components());
349+
base_path = get_drive_relative_path(drive_letter)?;
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_prefix_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()),
@@ -340,7 +371,7 @@ pub fn resolve_path(path: impl AsRef<Path>) -> Result<PathBuf> {
340371
let drive_letter = get_drive_letter(&current_dir).with_context(|| {
341372
format!("could not get drive letter: {}", current_dir.display())
342373
})?;
343-
base_path = get_drive_path(drive_letter);
374+
base_path = get_drive_prefix_path(drive_letter);
344375
stack.extend(base_path.components());
345376
}
346377
_ => {

0 commit comments

Comments
 (0)