# Objective - Fix ignored file search in nested Git directories (even just empty `.git` directories, not real git repositories) - Fixes #52328 ## Solution Keep parent ignore rules when rebuilding a nested repository's ignore stack. This hides root-ignored files again after excluded-file search is turned off, without breaking global ignore handling. For example: ```ascii project/ ├── .git/ ├── .gitignore # log/ ├── app/ │ └── a.txt # hello └── log/ ├── .git/ # nested repository marker └── b.txt # hello ``` Both `a.txt` and `b.txt` have string `hello`. Initially, searching for `hello` returns only `a.txt`. Enabling `Also search files ignored by configuration` rescans `log/` and returns both files. Before this change, disabling the option still returned both files because the rescan dropped the parent `.gitignore` and marked `b.txt` as included. ## Testing - Did you test these changes? If so, how? To test changes in this pull request create a simple project with the following dir structure: ``` .git .gitignore a/a.txt b/b.txt b/.git # this is an empty directory ``` Add `b` folder as an ignore entry to the `.gitignore` file: `b/` - Are there any parts that need more testing? No - How can other people (reviewers) test your changes? Is there anything specific they need to know? Use the test scenario above - If relevant, what platforms did you test these changes on, and are there any important ones you can't test? macOS ## Self-Review Checklist: - [x] I've reviewed my own diff for quality, security, and reliability - [x] Unsafe blocks (if any) have justifying comments - [x] The content adheres to Zed's UI standards ([UX/UI](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist) and [icon](https://github.com/zed-industries/zed/blob/main/crates/icons/README.md) guidelines) - [x] Tests cover the new/changed behavior - [x] Performance impact has been considered and is acceptable ## Showcase By using the structure above here is how it works before changes here: https://github.com/user-attachments/assets/2028123e-388e-4390-b131-ddf4642ab5f3 Notice that `hello` string from file `b.txt` is listed in search results regardless of the option `Include excluded files` And here is after: https://github.com/user-attachments/assets/99c627a2-e16a-4fdd-979b-2e0bacbd827d Notice that `hello` string from file `b.txt` is **not** listed in search results when the option `Include excluded files` is disabled. Release Notes: - Fixed ignored file search in nested Git directories
130 lines
3.9 KiB
Rust
130 lines
3.9 KiB
Rust
use ignore::gitignore::Gitignore;
|
|
use std::{ffi::OsStr, path::Path, sync::Arc};
|
|
|
|
#[derive(Clone, Debug)]
|
|
pub struct IgnoreStack {
|
|
pub repo_root: Option<Arc<Path>>,
|
|
pub top: Arc<IgnoreStackEntry>,
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub enum IgnoreStackEntry {
|
|
None,
|
|
Global {
|
|
ignore: Arc<Gitignore>,
|
|
},
|
|
RepoExclude {
|
|
ignore: Arc<Gitignore>,
|
|
parent: Arc<IgnoreStackEntry>,
|
|
},
|
|
Some {
|
|
abs_base_path: Arc<Path>,
|
|
ignore: Arc<Gitignore>,
|
|
parent: Arc<IgnoreStackEntry>,
|
|
},
|
|
All,
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub enum IgnoreKind {
|
|
Gitignore(Arc<Path>),
|
|
RepoExclude,
|
|
}
|
|
|
|
impl IgnoreStack {
|
|
pub fn none() -> Self {
|
|
Self {
|
|
repo_root: None,
|
|
top: Arc::new(IgnoreStackEntry::None),
|
|
}
|
|
}
|
|
|
|
pub fn all() -> Self {
|
|
Self {
|
|
repo_root: None,
|
|
top: Arc::new(IgnoreStackEntry::All),
|
|
}
|
|
}
|
|
|
|
pub fn global(ignore: Arc<Gitignore>) -> Self {
|
|
Self {
|
|
repo_root: None,
|
|
top: Arc::new(IgnoreStackEntry::Global { ignore }),
|
|
}
|
|
}
|
|
|
|
pub fn append(self, kind: IgnoreKind, ignore: Arc<Gitignore>) -> Self {
|
|
let top = match self.top.as_ref() {
|
|
IgnoreStackEntry::All => self.top.clone(),
|
|
_ => Arc::new(match kind {
|
|
IgnoreKind::Gitignore(abs_base_path) => IgnoreStackEntry::Some {
|
|
abs_base_path,
|
|
ignore,
|
|
parent: self.top.clone(),
|
|
},
|
|
IgnoreKind::RepoExclude => IgnoreStackEntry::RepoExclude {
|
|
ignore,
|
|
parent: self.top.clone(),
|
|
},
|
|
}),
|
|
};
|
|
Self {
|
|
repo_root: self.repo_root,
|
|
top,
|
|
}
|
|
}
|
|
|
|
pub fn is_abs_path_ignored(&self, abs_path: &Path, is_dir: bool) -> bool {
|
|
if is_dir && abs_path.file_name() == Some(OsStr::new(".git")) {
|
|
return true;
|
|
}
|
|
|
|
match self.top.as_ref() {
|
|
IgnoreStackEntry::None => false,
|
|
IgnoreStackEntry::All => true,
|
|
IgnoreStackEntry::Global { ignore } => {
|
|
let combined_path;
|
|
let abs_path = if let Some(repo_root) = self.repo_root.as_ref() {
|
|
if let Ok(relative_path) = abs_path.strip_prefix(repo_root) {
|
|
combined_path = ignore.path().join(relative_path);
|
|
&combined_path
|
|
} else {
|
|
abs_path
|
|
}
|
|
} else {
|
|
abs_path
|
|
};
|
|
match ignore.matched(abs_path, is_dir) {
|
|
ignore::Match::None => false,
|
|
ignore::Match::Ignore(_) => true,
|
|
ignore::Match::Whitelist(_) => false,
|
|
}
|
|
}
|
|
IgnoreStackEntry::RepoExclude { ignore, parent } => {
|
|
match ignore.matched(abs_path, is_dir) {
|
|
ignore::Match::None => IgnoreStack {
|
|
repo_root: self.repo_root.clone(),
|
|
top: parent.clone(),
|
|
}
|
|
.is_abs_path_ignored(abs_path, is_dir),
|
|
ignore::Match::Ignore(_) => true,
|
|
ignore::Match::Whitelist(_) => false,
|
|
}
|
|
}
|
|
IgnoreStackEntry::Some {
|
|
abs_base_path,
|
|
ignore,
|
|
parent: prev,
|
|
} => match ignore.matched(abs_path.strip_prefix(abs_base_path).unwrap(), is_dir) {
|
|
ignore::Match::None => IgnoreStack {
|
|
repo_root: self.repo_root.clone(),
|
|
top: prev.clone(),
|
|
}
|
|
.is_abs_path_ignored(abs_path, is_dir),
|
|
ignore::Match::Ignore(_) => true,
|
|
ignore::Match::Whitelist(_) => false,
|
|
},
|
|
}
|
|
}
|
|
}
|