@ 261027.5

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

crates/ignore/src/pathutil.rs

@@ -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/..")));+    }+}