fix(search): stop outer .gitignore rules at nested repositories in the remaining walkers (#1304)

#1309 made the Find in Files, file scope, index and walk_files walkers require git inside a repository, so an outer repository's .gitignore stops at a nested repository. External TypeScript project discovery (ide-lsp) and Lombok detection (ide-sdk) still walked with require_git(false) and could miss a nested repository's sources the same way; they now use the same check. Adds tests for walk_files (nested repository with its own .gitignore and info/exclude, a plain folder inside the outer repository, the outer repository, a folder outside any repository) and for scope_files.

Co-authored-by: jaw52 <[email protected]>
Цей коміт міститься в:
Jaw authored and GitHub committed 2026-11-04 23:20:38 +08:00
1 джерело a08707dabd
коміт 057877a39e
4 files changed
+71 -2

No files matched your search

+1 -1
Переглянути файл
@@ -59,7 +59,7 @@ fn source_files(root: &Path, exclude_root: &Path, excludes: Vec<String>, limit:
}
let Ok(ignores) = ignores.build() else { return Vec::new() };
let mut walk = ignore::WalkBuilder::new(root);
walk.require_git(false).follow_links(false).filter_entry(move |entry| {
walk.require_git(ide_core::ignored::inside_git_repo(root)).follow_links(false).filter_entry(move |entry| {
if entry.depth() == 0 {
return true;
}
+1 -1
Переглянути файл
@@ -246,7 +246,7 @@ fn sources_import_lombok(root: &Path) -> bool {
// Imports precede the first type, after at most a license header.
const HEAD: u64 = 8 * 1024;
let mut walker = ignore::WalkBuilder::new(root);
walker.require_git(false).follow_links(false).filter_entry(|e| {
walker.require_git(ide_core::ignored::inside_git_repo(root)).follow_links(false).filter_entry(|e| {
e.depth() == 0
|| !e.file_type().is_some_and(|t| t.is_dir())
|| !OUTPUT_DIRS.contains(&e.file_name().to_string_lossy().as_ref())
+28
Переглянути файл
@@ -207,6 +207,34 @@ fn honours_ignore_files_hidden_and_git() {
assert_eq!(paths(&views), ["src/a.rs", "sub/kept.txt"]);
}
#[test]
fn nested_repository_ignores_outer_repository_rules() {
// #1254: the opened project is a Git repository inside a folder its parent repository ignores.
let p = project(&[
(".git/HEAD", b"ref: refs/heads/main\n"),
("projects/.gitignore", b"*\n!.gitignore\n"),
("projects/nested/.git/HEAD", b"ref: refs/heads/main\n"),
("projects/nested/.gitignore", b"*.log\n"),
("projects/nested/search-test.txt", b"somesss\n"),
("projects/nested/debug.log", b"somesss\n"),
]);
let nested = p.path().join("projects/nested");
let (views, _) = run(&nested, &query("somesss"));
assert_eq!(paths(&views), ["search-test.txt"]);
let files = ide_search::scope_files(
&nested,
&SearchScope::Project,
&ide_search::FileMask::new(&[], &[]).unwrap(),
None,
&CancellationToken::new(),
)
.unwrap();
let mut rels: Vec<_> = files.iter().map(|f| f.rel_path.as_str()).collect();
rels.sort();
assert_eq!(rels, ["search-test.txt"]);
}
#[test]
fn skips_binary_files() {
let mut late_nul = b"needle\n".to_vec();
+41
Переглянути файл
@@ -259,6 +259,47 @@ fn walk_honours_ignores() {
assert_eq!(found, expected);
}
#[test]
fn walk_scopes_gitignore_to_each_repository() {
let (_t, r) = root();
// An outer repository ignores everything under `projects/`, nested repository included (#1254).
write(&r.join("outer/.git/HEAD"), "ref: refs/heads/main\n");
write(&r.join("outer/top.txt"), "");
write(&r.join("outer/projects/.gitignore"), "*\n!.gitignore\n");
write(&r.join("outer/projects/nested/.git/HEAD"), "ref: refs/heads/main\n");
write(&r.join("outer/projects/nested/.git/info/exclude"), "local.txt\n");
write(&r.join("outer/projects/nested/.gitignore"), "*.log\n");
write(&r.join("outer/projects/nested/search-test.txt"), "");
write(&r.join("outer/projects/nested/src/a.rs"), "");
write(&r.join("outer/projects/nested/debug.log"), "");
write(&r.join("outer/projects/nested/local.txt"), "");
write(&r.join("outer/projects/plain/file.txt"), "");
write(&r.join("loose/.gitignore"), "*.log\n");
write(&r.join("loose/a.txt"), "");
write(&r.join("loose/b.log"), "");
let walk = |dir: &str| {
let base = r.join(dir);
let found = Mutex::new(BTreeSet::new());
walk_files(&base, &[], |p| {
found.lock().insert(p.strip_prefix(&base).unwrap().to_string_lossy().replace('\\', "/"));
});
found.into_inner().into_iter().collect::<Vec<_>>()
};
let cases: [(&str, &[&str]); 4] = [
// The nested repository's own rules apply; the outer repository's do not.
("outer/projects/nested", &[".gitignore", "search-test.txt", "src/a.rs"]),
// A plain folder inside the outer repository is still governed by it, as in Git.
("outer/projects/plain", &[]),
("outer", &["projects/.gitignore", "top.txt"]),
// Outside any repository `.gitignore` still applies.
("loose", &[".gitignore", "a.txt"]),
];
for (dir, expected) in cases {
assert_eq!(walk(dir), expected, "{dir}");
}
}
fn wait_for(events: &Mutex<Vec<FsBatch>>, pred: impl Fn(&[FsBatch]) -> bool) -> bool {
let deadline = Instant::now() + Duration::from_secs(10);
while Instant::now() < deadline {