The file as vendored does not conform to our source formatting
conventions. Let's reformat it so it does.
- skip-blame automated code reformatting
( )
The file as vendored does not conform to our source formatting
conventions. Let's reformat it so it does.
No Linters Available |
No Unit Test Coverage |
Path | Packages | |||
---|---|---|---|---|
M | rust/hg-core/src/utils/path.rs (49 lines) |
Commit | Parents | Author | Summary | Date |
---|---|---|---|---|
d20f321da953 | 4bf79cc7f468 | Gregory Szorc | Dec 7 2019, 4:13 PM |
/// | /// | ||||
/// Unlike [`fs::canonicalize`], do not follow symlinks. | /// Unlike [`fs::canonicalize`], do not follow symlinks. | ||||
/// | /// | ||||
/// This function does not access the filesystem. Therefore it can behave | /// This function does not access the filesystem. Therefore it can behave | ||||
/// differently from the kernel or other library functions in corner cases. | /// differently from the kernel or other library functions in corner cases. | ||||
/// For example: | /// For example: | ||||
/// | /// | ||||
/// - On some systems with symlink support, `foo/bar/..` and `foo` can be | /// - On some systems with symlink support, `foo/bar/..` and `foo` can be | ||||
/// different as seen by the kernel, if `foo/bar` is a symlink. This | /// different as seen by the kernel, if `foo/bar` is a symlink. This function | ||||
/// function always returns `foo` in this case. | /// always returns `foo` in this case. | ||||
/// - On Windows, the official normalization rules are much more complicated. | /// - On Windows, the official normalization rules are much more complicated. | ||||
/// See https://github.com/rust-lang/rust/pull/47363#issuecomment-357069527. | /// See https://github.com/rust-lang/rust/pull/47363#issuecomment-357069527. | ||||
/// For example, this function cannot translate "drive relative" path like | /// For example, this function cannot translate "drive relative" path like | ||||
/// "X:foo" to an absolute path. | /// "X:foo" to an absolute path. | ||||
/// | /// | ||||
/// Return an error if `std::env::current_dir()` fails or if this function | /// Return an error if `std::env::current_dir()` fails or if this function | ||||
/// fails to produce an absolute path. | /// fails to produce an absolute path. | ||||
pub fn absolute(path: impl AsRef<Path>) -> io::Result<PathBuf> { | pub fn absolute(path: impl AsRef<Path>) -> io::Result<PathBuf> { | ||||
io::ErrorKind::Other, | io::ErrorKind::Other, | ||||
format!("cannot get absoltue path from {:?}", path), | format!("cannot get absoltue path from {:?}", path), | ||||
)); | )); | ||||
} | } | ||||
let mut result = PathBuf::new(); | let mut result = PathBuf::new(); | ||||
for component in path.components() { | for component in path.components() { | ||||
match component { | match component { | ||||
Component::Normal(_) | Component::RootDir | Component::Prefix(_) => { | Component::Normal(_) | ||||
| Component::RootDir | |||||
| Component::Prefix(_) => { | |||||
result.push(component); | result.push(component); | ||||
} | } | ||||
Component::ParentDir => { | Component::ParentDir => { | ||||
result.pop(); | result.pop(); | ||||
} | } | ||||
Component::CurDir => (), | Component::CurDir => (), | ||||
} | } | ||||
} | } | ||||
Ok(result) | Ok(result) | ||||
} | } | ||||
/// Remove the file pointed by `path`. | /// Remove the file pointed by `path`. | ||||
#[cfg(unix)] | #[cfg(unix)] | ||||
pub fn remove_file<P: AsRef<Path>>(path: P) -> Result<()> { | pub fn remove_file<P: AsRef<Path>>(path: P) -> Result<()> { | ||||
fs_remove_file(path)?; | fs_remove_file(path)?; | ||||
Ok(()) | Ok(()) | ||||
} | } | ||||
/// Remove the file pointed by `path`. | /// Remove the file pointed by `path`. | ||||
/// | /// | ||||
/// On Windows, removing a file can fail for various reasons, including if the file is memory | /// On Windows, removing a file can fail for various reasons, including if the | ||||
/// mapped. This can happen when the repository is accessed concurrently while a background task is | /// file is memory mapped. This can happen when the repository is accessed | ||||
/// trying to remove a packfile. To solve this, we can rename the file before trying to remove it. | /// concurrently while a background task is trying to remove a packfile. To | ||||
/// solve this, we can rename the file before trying to remove it. | |||||
/// If the remove operation fails, a future repack will clean it up. | /// If the remove operation fails, a future repack will clean it up. | ||||
#[cfg(not(unix))] | #[cfg(not(unix))] | ||||
pub fn remove_file<P: AsRef<Path>>(path: P) -> Result<()> { | pub fn remove_file<P: AsRef<Path>>(path: P) -> Result<()> { | ||||
let path = path.as_ref(); | let path = path.as_ref(); | ||||
let extension = path | let extension = path | ||||
.extension() | .extension() | ||||
.and_then(|ext| ext.to_str()) | .and_then(|ext| ext.to_str()) | ||||
.map_or(".to-delete".to_owned(), |ext| ".".to_owned() + ext + "-tmp"); | .map_or(".to-delete".to_owned(), |ext| ".".to_owned() + ext + "-tmp"); | ||||
let dest_path = Builder::new() | let dest_path = Builder::new() | ||||
.prefix("") | .prefix("") | ||||
.suffix(&extension) | .suffix(&extension) | ||||
.rand_bytes(8) | .rand_bytes(8) | ||||
.tempfile_in(path.parent().unwrap())? | .tempfile_in(path.parent().unwrap())? | ||||
.into_temp_path(); | .into_temp_path(); | ||||
rename(path, &dest_path)?; | rename(path, &dest_path)?; | ||||
// Ignore errors when removing the file, it will be cleaned up at a later time. | // Ignore errors when removing the file, it will be cleaned up at a later | ||||
// time. | |||||
let _ = fs_remove_file(dest_path); | let _ = fs_remove_file(dest_path); | ||||
Ok(()) | Ok(()) | ||||
} | } | ||||
/// Create the directory and ignore failures when a directory of the same name already exists. | /// Create the directory and ignore failures when a directory of the same name | ||||
/// already exists. | |||||
pub fn create_dir(path: impl AsRef<Path>) -> io::Result<()> { | pub fn create_dir(path: impl AsRef<Path>) -> io::Result<()> { | ||||
match fs::create_dir(path.as_ref()) { | match fs::create_dir(path.as_ref()) { | ||||
Ok(()) => Ok(()), | Ok(()) => Ok(()), | ||||
Err(e) => { | Err(e) => { | ||||
if e.kind() == ErrorKind::AlreadyExists && path.as_ref().is_dir() { | if e.kind() == ErrorKind::AlreadyExists && path.as_ref().is_dir() { | ||||
Ok(()) | Ok(()) | ||||
} else { | } else { | ||||
Err(e) | Err(e) | ||||
} | } | ||||
} | } | ||||
} | } | ||||
} | } | ||||
/// Expand the user's home directory and any environment variables references in | /// Expand the user's home directory and any environment variables references | ||||
/// the given path. | /// in the given path. | ||||
/// | /// | ||||
/// This function is designed to emulate the behavior of Mercurial's `util.expandpath` | /// This function is designed to emulate the behavior of Mercurial's | ||||
/// function, which in turn uses Python's `os.path.expand{user,vars}` functions. This | /// `util.expandpath` function, which in turn uses Python's | ||||
/// results in behavior that is notably different from the default expansion behavior | /// `os.path.expand{user,vars}` functions. This results in behavior that is | ||||
/// of the `shellexpand` crate. In particular: | /// notably different from the default expansion behavior of the `shellexpand` | ||||
/// crate. In particular: | |||||
/// | /// | ||||
/// - If a reference to an environment variable is missing or invalid, the reference | /// - If a reference to an environment variable is missing or invalid, the | ||||
/// is left unchanged in the resulting path rather than emitting an error. | /// reference is left unchanged in the resulting path rather than emitting an | ||||
/// error. | |||||
/// | /// | ||||
/// - Home directory expansion explicitly happens after environment variable | /// - Home directory expansion explicitly happens after environment variable | ||||
/// expansion, meaning that if an environment variable is expanded into a | /// expansion, meaning that if an environment variable is expanded into a | ||||
/// string starting with a tilde (`~`), the tilde will be expanded into the | /// string starting with a tilde (`~`), the tilde will be expanded into the | ||||
/// user's home directory. | /// user's home directory. | ||||
/// | |||||
pub fn expand_path(path: impl AsRef<str>) -> PathBuf { | pub fn expand_path(path: impl AsRef<str>) -> PathBuf { | ||||
expand_path_impl(path.as_ref(), |k| env::var(k).ok(), dirs::home_dir) | expand_path_impl(path.as_ref(), |k| env::var(k).ok(), dirs::home_dir) | ||||
} | } | ||||
/// Same as `expand_path` but explicitly takes closures for environment variable | /// Same as `expand_path` but explicitly takes closures for environment | ||||
/// and home directory lookup for the sake of testability. | /// variable and home directory lookup for the sake of testability. | ||||
fn expand_path_impl<E, H>(path: &str, getenv: E, homedir: H) -> PathBuf | fn expand_path_impl<E, H>(path: &str, getenv: E, homedir: H) -> PathBuf | ||||
where | where | ||||
E: FnMut(&str) -> Option<String>, | E: FnMut(&str) -> Option<String>, | ||||
H: FnOnce() -> Option<PathBuf>, | H: FnOnce() -> Option<PathBuf>, | ||||
{ | { | ||||
// The shellexpand crate does not expand Windows environment variables | // The shellexpand crate does not expand Windows environment variables | ||||
// like `%PROGRAMDATA%`. We'd like to expand them too. So let's do some | // like `%PROGRAMDATA%`. We'd like to expand them too. So let's do some | ||||
// pre-processing. | // pre-processing. | ||||
} | } | ||||
#[cfg(unix)] | #[cfg(unix)] | ||||
mod unix { | mod unix { | ||||
use super::*; | use super::*; | ||||
#[test] | #[test] | ||||
fn test_absolute_fullpath() { | fn test_absolute_fullpath() { | ||||
assert_eq!(absolute("/a/./b\\c/../d/.").unwrap(), Path::new("/a/d")); | assert_eq!( | ||||
absolute("/a/./b\\c/../d/.").unwrap(), | |||||
Path::new("/a/d") | |||||
); | |||||
assert_eq!(absolute("/a/../../../../b").unwrap(), Path::new("/b")); | assert_eq!(absolute("/a/../../../../b").unwrap(), Path::new("/b")); | ||||
assert_eq!(absolute("/../../..").unwrap(), Path::new("/")); | assert_eq!(absolute("/../../..").unwrap(), Path::new("/")); | ||||
assert_eq!(absolute("/../../../").unwrap(), Path::new("/")); | assert_eq!(absolute("/../../../").unwrap(), Path::new("/")); | ||||
assert_eq!( | assert_eq!( | ||||
absolute("//foo///bar//baz").unwrap(), | absolute("//foo///bar//baz").unwrap(), | ||||
Path::new("/foo/bar/baz") | Path::new("/foo/bar/baz") | ||||
); | ); | ||||
assert_eq!(absolute("//").unwrap(), Path::new("/")); | assert_eq!(absolute("//").unwrap(), Path::new("/")); |