Avoid fs::canonicalize (#539)
Previously, we used `fs::canonicalize` to ensure paths used in search were absolute. This lead to bad behavior when the justfile was symbolic link to a file in another directory. Additionally, on Windows, this produced paths in extended length syntax, which, I believe, has compatibility issues. This diff replaces uses of `fs::canonicalize` with a simpler algorithm that roots path in the invocation directory (which will be a no-op if said path is already absolute), uses `Path::components` to remove extra `/` and `.`, and resolves instances of `..` without following symlinks, by removing the `..` and the component that proceeds it.
This commit is contained in:
parent
f8693d6fe0
commit
c4e9857ebd
@ -32,7 +32,7 @@ pub(crate) use snafu::{ResultExt, Snafu};
|
||||
pub(crate) use unicode_width::UnicodeWidthChar;
|
||||
|
||||
// modules
|
||||
pub(crate) use crate::{config_error, keyword, search_error, setting};
|
||||
pub(crate) use crate::{config_error, keyword, setting};
|
||||
|
||||
// functions
|
||||
pub(crate) use crate::{default::default, empty::empty, load_dotenv::load_dotenv, output::output};
|
||||
|
@ -9,8 +9,6 @@ pub(crate) enum ConfigError {
|
||||
message
|
||||
))]
|
||||
Internal { message: String },
|
||||
#[snafu(display("Could not canonicalize justfile path `{}`: {}", path.display(), source))]
|
||||
JustfilePathCanonicalize { path: PathBuf, source: io::Error },
|
||||
#[snafu(display("Failed to get current directory: {}", source))]
|
||||
CurrentDir { source: io::Error },
|
||||
#[snafu(display(
|
||||
|
@ -1,5 +1,7 @@
|
||||
use crate::common::*;
|
||||
|
||||
use std::path::Component;
|
||||
|
||||
const FILENAME: &str = "justfile";
|
||||
|
||||
pub(crate) struct Search {
|
||||
@ -25,12 +27,7 @@ impl Search {
|
||||
}
|
||||
|
||||
SearchConfig::FromSearchDirectory { search_directory } => {
|
||||
let search_directory =
|
||||
search_directory
|
||||
.canonicalize()
|
||||
.context(search_error::Canonicalize {
|
||||
path: search_directory,
|
||||
})?;
|
||||
let search_directory = Self::clean(invocation_directory, search_directory);
|
||||
|
||||
let justfile = Self::justfile(&search_directory)?;
|
||||
|
||||
@ -43,7 +40,7 @@ impl Search {
|
||||
}
|
||||
|
||||
SearchConfig::WithJustfile { justfile } => {
|
||||
let justfile: PathBuf = justfile.to_path_buf();
|
||||
let justfile = Self::clean(invocation_directory, justfile);
|
||||
|
||||
let working_directory = Self::working_directory_from_justfile(&justfile)?;
|
||||
|
||||
@ -57,8 +54,8 @@ impl Search {
|
||||
justfile,
|
||||
working_directory,
|
||||
} => Ok(Search {
|
||||
justfile: justfile.to_path_buf(),
|
||||
working_directory: working_directory.to_path_buf(),
|
||||
justfile: Self::clean(invocation_directory, justfile),
|
||||
working_directory: Self::clean(invocation_directory, working_directory),
|
||||
}),
|
||||
}
|
||||
}
|
||||
@ -92,16 +89,30 @@ impl Search {
|
||||
}
|
||||
}
|
||||
|
||||
fn working_directory_from_justfile(justfile: &Path) -> SearchResult<PathBuf> {
|
||||
let justfile_canonical = justfile
|
||||
.canonicalize()
|
||||
.context(search_error::Canonicalize { path: justfile })?;
|
||||
fn clean(invocation_directory: &Path, path: &Path) -> PathBuf {
|
||||
let path = invocation_directory.join(path);
|
||||
|
||||
let mut clean = Vec::new();
|
||||
|
||||
for component in path.components() {
|
||||
if component == Component::ParentDir {
|
||||
if let Some(Component::Normal(_)) = clean.last() {
|
||||
clean.pop();
|
||||
}
|
||||
} else {
|
||||
clean.push(component);
|
||||
}
|
||||
}
|
||||
|
||||
clean.into_iter().collect()
|
||||
}
|
||||
|
||||
fn working_directory_from_justfile(justfile: &Path) -> SearchResult<PathBuf> {
|
||||
Ok(
|
||||
justfile_canonical
|
||||
justfile
|
||||
.parent()
|
||||
.ok_or_else(|| SearchError::JustfileHadNoParent {
|
||||
path: justfile_canonical.clone(),
|
||||
path: justfile.to_path_buf(),
|
||||
})?
|
||||
.to_owned(),
|
||||
)
|
||||
@ -111,6 +122,7 @@ impl Search {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use test_utilities::tmptree;
|
||||
|
||||
#[test]
|
||||
fn not_found() {
|
||||
@ -228,4 +240,51 @@ mod tests {
|
||||
_ => panic!("No errors were expected"),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn justfile_symlink_parent() {
|
||||
let tmp = tmptree! {
|
||||
src: "",
|
||||
sub: {},
|
||||
};
|
||||
|
||||
let src = tmp.path().join("src");
|
||||
let sub = tmp.path().join("sub");
|
||||
let justfile = sub.join("justfile");
|
||||
|
||||
#[cfg(unix)]
|
||||
std::os::unix::fs::symlink(&src, &justfile).unwrap();
|
||||
|
||||
#[cfg(windows)]
|
||||
std::os::windows::fs::symlink_file(&src, &justfile).unwrap();
|
||||
|
||||
let search_config = SearchConfig::FromInvocationDirectory;
|
||||
|
||||
let search = Search::search(&search_config, &sub).unwrap();
|
||||
|
||||
assert_eq!(search.justfile, justfile);
|
||||
assert_eq!(search.working_directory, sub);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn clean() {
|
||||
let cases = &[
|
||||
("/", "foo", "/foo"),
|
||||
("/bar", "/foo", "/foo"),
|
||||
("//foo", "bar//baz", "/foo/bar/baz"),
|
||||
("/", "..", "/"),
|
||||
("/", "/..", "/"),
|
||||
("/..", "", "/"),
|
||||
("/../../../..", "../../../", "/"),
|
||||
("/.", "./", "/"),
|
||||
("/foo/../", "bar", "/bar"),
|
||||
("/foo/bar", "..", "/foo"),
|
||||
("/foo/bar/", "..", "/foo"),
|
||||
];
|
||||
|
||||
for (prefix, suffix, want) in cases {
|
||||
let have = Search::clean(Path::new(prefix), Path::new(suffix));
|
||||
assert_eq!(have, Path::new(want));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -12,9 +12,7 @@ pub(crate) enum SearchError {
|
||||
.map(|candidate| candidate.file_name().unwrap().to_string_lossy())
|
||||
),
|
||||
))]
|
||||
MultipleCandidates {
|
||||
candidates: Vec<PathBuf>,
|
||||
},
|
||||
MultipleCandidates { candidates: Vec<PathBuf> },
|
||||
#[snafu(display(
|
||||
"I/O error reading directory `{}`: {}",
|
||||
directory.display(),
|
||||
@ -26,13 +24,8 @@ pub(crate) enum SearchError {
|
||||
},
|
||||
#[snafu(display("No justfile found"))]
|
||||
NotFound,
|
||||
Canonicalize {
|
||||
path: PathBuf,
|
||||
source: io::Error,
|
||||
},
|
||||
JustfileHadNoParent {
|
||||
path: PathBuf,
|
||||
},
|
||||
#[snafu(display("Justfile path had no parent: {}", path.display()))]
|
||||
JustfileHadNoParent { path: PathBuf },
|
||||
}
|
||||
|
||||
impl Error for SearchError {}
|
||||
|
Loading…
Reference in New Issue
Block a user