261023.5 · 7155f6408d370e699795dc75e954635abfaf08a6
ignore: treat dotfiles ending in a dot as hidden (#3564)
`file_name()` returned `None` for any path ending in `.` on Unix, which was meant to cover the `.` and `..` components but also matched real names like `.foo.`. The hidden check then found no file name and treated those files as visible. Only a final component that is exactly `.` or `..` has no file name now.
PR #3564
reading declarations
Changes to crates/ignore/src/pathutil.rs. All files
@@ -151,15 +151,15 @@ pub(crate) fn file_name<'a, P: AsRef<Path> + ?Sized>( let path = path.as_os_str().as_bytes(); if path.is_empty() { return None;- } else if path.len() == 1 && path[0] == b'.' {- return None;- } else if path.last() == Some(&b'.') {- return None;- } else if path.len() >= 2 && &path[path.len() - 2..] == &b".."[..] {- return None; } let last_slash = memrchr(b'/', path).map(|i| i + 1).unwrap_or(0);- Some(OsStr::from_bytes(&path[last_slash..]))+ let name = &path[last_slash..];+ // Only a final component that is exactly `.` or `..` has no file+ // name. A real name that merely ends in a `.`, like `.foo.`, does.+ if name == b"." || name == b".." {+ return None;+ }+ Some(OsStr::from_bytes(name)) } #[cfg(not(unix))]@@ -169,3 +169,50 @@ pub(crate) fn file_name<'a, P: AsRef<Path> + ?Sized>( imp(path.as_ref()) }++#[cfg(test)]+mod tests {+ use std::ffi::OsStr;+ use std::path::Path;++ use super::{file_name, is_hidden_path};++ #[test]+ fn file_name_basic() {+ assert_eq!(file_name("a"), Some(OsStr::new("a")));+ assert_eq!(file_name("a/b"), Some(OsStr::new("b")));+ assert_eq!(file_name("/a/b.txt"), Some(OsStr::new("b.txt")));+ }++ // On Windows, `file_name` defers to `std`, which normalizes a trailing+ // `.` component away, so these only apply to the Unix implementation.+ #[cfg(unix)]+ #[test]+ fn file_name_dot_components() {+ assert_eq!(file_name(""), None);+ assert_eq!(file_name("."), None);+ assert_eq!(file_name(".."), None);+ assert_eq!(file_name("a/."), None);+ assert_eq!(file_name("a/.."), None);+ assert_eq!(file_name("/a/b/.."), None);+ }++ #[test]+ fn file_name_trailing_dot() {+ assert_eq!(file_name("a."), Some(OsStr::new("a.")));+ assert_eq!(file_name("a/.b."), Some(OsStr::new(".b.")));+ assert_eq!(file_name("a/..."), Some(OsStr::new("...")));+ assert_eq!(file_name("a/..b.."), Some(OsStr::new("..b..")));+ }++ #[test]+ fn hidden_trailing_dot() {+ assert!(is_hidden_path(Path::new(".foo")));+ assert!(is_hidden_path(Path::new("a/.foo.")));+ assert!(is_hidden_path(Path::new("a/.foo..")));+ assert!(is_hidden_path(Path::new("a/...")));+ assert!(!is_hidden_path(Path::new("a/foo.")));+ assert!(!is_hidden_path(Path::new("a/.")));+ assert!(!is_hidden_path(Path::new("a/..")));+ }+}