Rework path_normalize_for_cd() to be less allocation trigger happy

Lots of resizing, splicing, and full-on allocating going on here.
This commit is contained in:
Mahmoud Al-Qudsi 2024-05-21 12:50:28 -05:00
parent d14d8d5733
commit 0b5e41b268

View File

@ -276,49 +276,50 @@ fn test_normalize_path() {
/// path. The intent here is to allow 'cd' out of a directory which may no longer exist, without
/// allowing 'cd' into a directory that may not exist; see #5341.
pub fn path_normalize_for_cd(wd: &wstr, path: &wstr) -> WString {
use std::collections::VecDeque;
// Fast paths.
const sep: char = '/';
const SEP: char = '/';
assert!(
wd.as_char_slice().first() == Some(&'/') && wd.as_char_slice().last() == Some(&'/'),
"Invalid working directory, it must start and end with /"
);
if path.is_empty() {
return wd.to_owned();
} else if path.as_char_slice().first() == Some(&sep) {
} else if path.as_char_slice().first() == Some(&SEP) {
return path.to_owned();
} else if path.as_char_slice().first() != Some(&'.') {
return wd.to_owned() + path;
}
// Split our strings by the sep.
let mut wd_comps = wd.split(sep).collect::<Vec<_>>();
let path_comps = path.split(sep).collect::<Vec<_>>();
// Remove empty segments from wd_comps.
// In particular this removes the leading and trailing empties.
wd_comps.retain(|comp| !comp.is_empty());
let mut wd_comps: VecDeque<_> = wd
.split(SEP)
// Remove empty segments from wd_comps, especially leading/trailing empties.
.filter(|p| !p.is_empty())
.collect();
let mut path_comps = path.split(SEP).peekable();
// Erase leading . and .. components from path_comps, popping from wd_comps as we go.
let mut erase_count = 0;
for comp in &path_comps {
let mut erase_it = false;
while let Some(comp) = path_comps.peek() {
if comp.is_empty() || comp == "." {
erase_it = true;
} else if comp == ".." && !wd_comps.is_empty() {
erase_it = true;
wd_comps.pop();
}
if erase_it {
erase_count += 1;
path_comps.next();
} else if comp == ".." && wd_comps.pop_back().is_some() {
path_comps.next();
} else {
break;
}
}
// Append un-erased elements to wd_comps and join them, then prepend the leading /.
wd_comps.extend(path_comps.into_iter().skip(erase_count));
let mut result = join_strings(&wd_comps, sep);
result.insert(0, '/');
// Append un-erased elements to wd_comps and join them, prepending with a leading /
let mut paths = wd_comps;
paths.extend(path_comps);
let mut result =
WString::with_capacity(paths.iter().fold(0, |sum, s| sum + s.len()) + paths.len() + 1);
for p in &paths {
result.push(SEP);
result.push_utfstr(*p);
}
result
}