diff --git a/crates/fs-operation/src/search.rs b/crates/fs-operation/src/search.rs index ce91636de..eb1129ea9 100644 --- a/crates/fs-operation/src/search.rs +++ b/crates/fs-operation/src/search.rs @@ -282,9 +282,10 @@ impl DescriptorIgnoreMatchers { let matcher = builder.build().map_err(|_| { FsError::InvalidArgument("ignore patterns could not be compiled".to_string()) })?; - // An explicitly selected search root is traversed even when an ancestor - // ignores that directory, matching path-backed Grep behavior. + // An explicitly selected search root is traversed even when a strict + // ancestor ignores that directory, matching path-backed Grep behavior. if directory != explicit_base + && explicit_base.starts_with(directory) && matcher .matched_path_or_any_parents(explicit_base, true) .is_ignore() diff --git a/crates/workdir/src/external_local_tests.rs b/crates/workdir/src/external_local_tests.rs index 664f8eca3..c0d1b5d18 100644 --- a/crates/workdir/src/external_local_tests.rs +++ b/crates/workdir/src/external_local_tests.rs @@ -423,6 +423,46 @@ async fn external_read_write_provider_rejects_symlink_mutation_escape() { ); } +#[cfg(target_os = "linux")] +#[tokio::test] +async fn external_grep_honors_ignore_files_below_search_root() { + let root = tempfile::tempdir().unwrap(); + for (directory, ignore_file) in [ + ("search/gitignore-rules", ".gitignore"), + ("search/ignore-rules", ".ignore"), + ] { + let directory = root.path().join(directory); + std::fs::create_dir_all(&directory).unwrap(); + std::fs::write(directory.join(ignore_file), "ignored.txt\n").unwrap(); + std::fs::write(directory.join("ignored.txt"), "needle ignored\n").unwrap(); + std::fs::write(directory.join("visible.txt"), "needle visible\n").unwrap(); + } + let session = external_session(&root, BoundedReadLimits::new(4096, 1024).unwrap()); + + let result = WorkdirSession::grep( + &session, + GrepRequest { + pattern: "needle".to_string(), + path: WorkdirPath::new("search").unwrap(), + glob: Some("*.txt".to_string()), + file_type: None, + case_insensitive: false, + before_context: 0, + after_context: 0, + multiline: false, + output_mode: GrepOutputMode::FilesWithMatches, + limit: 10, + offset: 0, + }, + ) + .await + .unwrap(); + + assert!(result.output.contains("search/gitignore-rules/visible.txt")); + assert!(result.output.contains("search/ignore-rules/visible.txt")); + assert!(!result.output.contains("ignored.txt")); +} + #[cfg(target_os = "linux")] #[tokio::test] async fn external_grep_honors_gitignore_above_a_nested_search_root() {