diff --git a/Cargo.lock b/Cargo.lock index f0d8fb29..ffcda3fd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -980,6 +980,7 @@ dependencies = [ "notify-debouncer-mini", "once_cell", "opener", + "pathdiff", "predicates", "pretty_assertions", "pulldown-cmark", @@ -1159,6 +1160,12 @@ dependencies = [ "windows-targets", ] +[[package]] +name = "pathdiff" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8835116a5c179084a830efb3adc117ab007512b535bc1a21c991d3b32a6b44dd" + [[package]] name = "percent-encoding" version = "2.3.0" diff --git a/Cargo.toml b/Cargo.toml index c0790300..385aa470 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,6 +28,7 @@ log = "0.4.17" memchr = "2.5.0" opener = "0.6.1" pulldown-cmark = { version = "0.9.3", default-features = false } +pathdiff = "0.2.1" regex = "1.8.1" serde = { version = "1.0.163", features = ["derive"] } serde_json = "1.0.96" diff --git a/src/cmd/watch.rs b/src/cmd/watch.rs index b0344bb0..80b9ff1b 100644 --- a/src/cmd/watch.rs +++ b/src/cmd/watch.rs @@ -4,6 +4,7 @@ use ignore::gitignore::Gitignore; use mdbook::errors::Result; use mdbook::utils; use mdbook::MDBook; +use pathdiff::diff_paths; use std::path::{Path, PathBuf}; use std::sync::mpsc::channel; use std::thread::sleep; @@ -86,12 +87,21 @@ fn find_gitignore(book_root: &Path) -> Option { .find(|p| p.exists()) } +// Note: The usage of `canonicalize` may encounter occasional failures on the Windows platform, presenting a potential risk. +// For more details, refer to [Pull Request #2229](https://github.com/rust-lang/mdBook/pull/2229#discussion_r1408665981). fn filter_ignored_files(ignore: Gitignore, paths: &[PathBuf]) -> Vec { + let ignore_root = ignore + .path() + .canonicalize() + .expect("ignore root canonicalize error"); + paths .iter() .filter(|path| { + let relative_path = + diff_paths(&path, &ignore_root).expect("One of the paths should be an absolute"); !ignore - .matched_path_or_any_parents(path, path.is_dir()) + .matched_path_or_any_parents(&relative_path, relative_path.is_dir()) .is_ignore() }) .map(|path| path.to_path_buf()) @@ -176,3 +186,44 @@ where } } } + +#[cfg(test)] +mod tests { + use super::*; + use ignore::gitignore::GitignoreBuilder; + use std::env; + + #[test] + fn test_filter_ignored_files() { + let current_dir = env::current_dir().unwrap(); + + let ignore = GitignoreBuilder::new(¤t_dir) + .add_line(None, "*.html") + .unwrap() + .build() + .unwrap(); + let should_remain = current_dir.join("record.text"); + let should_filter = current_dir.join("index.html"); + + let remain = filter_ignored_files(ignore, &[should_remain.clone(), should_filter]); + assert_eq!(remain, vec![should_remain]) + } + + #[test] + fn filter_ignored_files_should_handle_parent_dir() { + let current_dir = env::current_dir().unwrap(); + + let ignore = GitignoreBuilder::new(¤t_dir) + .add_line(None, "*.html") + .unwrap() + .build() + .unwrap(); + + let parent_dir = current_dir.join(".."); + let should_remain = parent_dir.join("record.text"); + let should_filter = parent_dir.join("index.html"); + + let remain = filter_ignored_files(ignore, &[should_remain.clone(), should_filter]); + assert_eq!(remain, vec![should_remain]) + } +}