1
0
mirror of https://github.com/BurntSushi/ripgrep.git synced 2024-12-12 19:18:24 +02:00

Add option to ignore nested git repositories

This implements the suggestion made in #23 to provide an option to ignore nested
git repositories.

A nested git repository is identified by the presence of a .git file or
directory. It's a directory in the regular case, but it's a file for git
worktrees and git submodules.

This option is disabled by default.
This commit is contained in:
Zane Duffield 2024-03-09 16:28:20 +11:00
parent e9abbc1a02
commit e886ad0615
6 changed files with 140 additions and 1 deletions

View File

@ -146,6 +146,10 @@ _rg() {
'--ignore-file-case-insensitive[process ignore files case insensitively]' '--ignore-file-case-insensitive[process ignore files case insensitively]'
$no'--no-ignore-file-case-insensitive[process ignore files case sensitively]' $no'--no-ignore-file-case-insensitive[process ignore files case sensitively]'
+ '(ignore-nested-git)' # Ignore nested git repository option
'--ignore-nested-git[ignore nested git repositories]'
$no"--no-ignore-nested-git[don't ignore nested git repositories]"
+ '(ignore-exclude)' # Local exclude (ignore)-file options + '(ignore-exclude)' # Local exclude (ignore)-file options
"--no-ignore-exclude[don't respect local exclude (ignore) files]" "--no-ignore-exclude[don't respect local exclude (ignore) files]"
$no'--ignore-exclude[respect local exclude (ignore) files]' $no'--ignore-exclude[respect local exclude (ignore) files]'

View File

@ -83,6 +83,7 @@ pub(super) const FLAGS: &[&dyn Flag] = &[
&IgnoreCase, &IgnoreCase,
&IgnoreFile, &IgnoreFile,
&IgnoreFileCaseInsensitive, &IgnoreFileCaseInsensitive,
&IgnoreNestedGit,
&IncludeZero, &IncludeZero,
&InvertMatch, &InvertMatch,
&JSON, &JSON,
@ -3238,6 +3239,62 @@ fn test_ignore_file_case_insensitive() {
assert_eq!(true, args.ignore_file_case_insensitive); assert_eq!(true, args.ignore_file_case_insensitive);
} }
/// --ignore-nested-git
#[derive(Debug)]
struct IgnoreNestedGit;
impl Flag for IgnoreNestedGit {
fn is_switch(&self) -> bool {
true
}
fn name_long(&self) -> &'static str {
"ignore-nested-git"
}
fn name_negated(&self) -> Option<&'static str> {
Some("no-ignore-nested-git")
}
fn doc_category(&self) -> Category {
Category::Filter
}
fn doc_short(&self) -> &'static str {
r"Ignore nested git repositories."
}
fn doc_long(&self) -> &'static str {
r"
Ignore any nested directory containing a \fB.git\fP file or directory.
This will prevent ripgrep from recursing into any git worktrees, submodules,
or regular repositories.
.sp
Note that this does not affect top-level directories.
"
}
fn update(&self, v: FlagValue, args: &mut LowArgs) -> anyhow::Result<()> {
args.ignore_nested_git = v.unwrap_switch();
Ok(())
}
}
#[cfg(test)]
#[test]
fn test_ignore_nested_git() {
let args = parse_low_raw(None::<&str>).unwrap();
assert_eq!(false, args.ignore_nested_git);
let args = parse_low_raw(["--ignore-nested-git"]).unwrap();
assert_eq!(true, args.ignore_nested_git);
let args =
parse_low_raw(["--ignore-nested-git", "--no-ignore-nested-git"])
.unwrap();
assert_eq!(false, args.ignore_nested_git);
let args =
parse_low_raw(["--no-ignore-nested-git", "--ignore-nested-git"])
.unwrap();
assert_eq!(true, args.ignore_nested_git);
}
/// --include-zero /// --include-zero
#[derive(Debug)] #[derive(Debug)]
struct IncludeZero; struct IncludeZero;

View File

@ -59,6 +59,7 @@ pub(crate) struct HiArgs {
hyperlink_config: grep::printer::HyperlinkConfig, hyperlink_config: grep::printer::HyperlinkConfig,
ignore_file_case_insensitive: bool, ignore_file_case_insensitive: bool,
ignore_file: Vec<PathBuf>, ignore_file: Vec<PathBuf>,
ignore_nested_git: bool,
include_zero: bool, include_zero: bool,
invert_match: bool, invert_match: bool,
is_terminal_stdout: bool, is_terminal_stdout: bool,
@ -275,6 +276,7 @@ impl HiArgs {
hyperlink_config, hyperlink_config,
ignore_file: low.ignore_file, ignore_file: low.ignore_file,
ignore_file_case_insensitive: low.ignore_file_case_insensitive, ignore_file_case_insensitive: low.ignore_file_case_insensitive,
ignore_nested_git: low.ignore_nested_git,
include_zero: low.include_zero, include_zero: low.include_zero,
invert_match: low.invert_match, invert_match: low.invert_match,
is_terminal_stdout: state.is_terminal_stdout, is_terminal_stdout: state.is_terminal_stdout,
@ -893,7 +895,8 @@ impl HiArgs {
.git_ignore(!self.no_ignore_vcs) .git_ignore(!self.no_ignore_vcs)
.git_exclude(!self.no_ignore_vcs && !self.no_ignore_exclude) .git_exclude(!self.no_ignore_vcs && !self.no_ignore_exclude)
.require_git(!self.no_require_git) .require_git(!self.no_require_git)
.ignore_case_insensitive(self.ignore_file_case_insensitive); .ignore_case_insensitive(self.ignore_file_case_insensitive)
.ignore_nested_git_repo(self.ignore_nested_git);
if !self.no_ignore_dot { if !self.no_ignore_dot {
builder.add_custom_ignore_filename(".rgignore"); builder.add_custom_ignore_filename(".rgignore");
} }

View File

@ -64,6 +64,7 @@ pub(crate) struct LowArgs {
pub(crate) iglobs: Vec<String>, pub(crate) iglobs: Vec<String>,
pub(crate) ignore_file: Vec<PathBuf>, pub(crate) ignore_file: Vec<PathBuf>,
pub(crate) ignore_file_case_insensitive: bool, pub(crate) ignore_file_case_insensitive: bool,
pub(crate) ignore_nested_git: bool,
pub(crate) include_zero: bool, pub(crate) include_zero: bool,
pub(crate) invert_match: bool, pub(crate) invert_match: bool,
pub(crate) line_number: Option<bool>, pub(crate) line_number: Option<bool>,

View File

@ -46,6 +46,7 @@ enum IgnoreMatchInner<'a> {
Gitignore(&'a gitignore::Glob), Gitignore(&'a gitignore::Glob),
Types(types::Glob<'a>), Types(types::Glob<'a>),
Hidden, Hidden,
NestedRepo,
} }
impl<'a> IgnoreMatch<'a> { impl<'a> IgnoreMatch<'a> {
@ -64,6 +65,9 @@ impl<'a> IgnoreMatch<'a> {
fn hidden() -> IgnoreMatch<'static> { fn hidden() -> IgnoreMatch<'static> {
IgnoreMatch(IgnoreMatchInner::Hidden) IgnoreMatch(IgnoreMatchInner::Hidden)
} }
fn nested_repo() -> IgnoreMatch<'static> {
IgnoreMatch(IgnoreMatchInner::NestedRepo)
}
} }
/// Options for the ignore matcher, shared between the matcher itself and the /// Options for the ignore matcher, shared between the matcher itself and the
@ -84,6 +88,8 @@ struct IgnoreOptions {
git_exclude: bool, git_exclude: bool,
/// Whether to ignore files case insensitively /// Whether to ignore files case insensitively
ignore_case_insensitive: bool, ignore_case_insensitive: bool,
/// Whether to ignore nested git repositories.
ignore_nested_git_repo: bool,
/// Whether a git repository must be present in order to apply any /// Whether a git repository must be present in order to apply any
/// git-related ignore rules. /// git-related ignore rules.
require_git: bool, require_git: bool,
@ -342,6 +348,7 @@ impl Ignore {
|| opts.git_global || opts.git_global
|| opts.git_ignore || opts.git_ignore
|| opts.git_exclude || opts.git_exclude
|| opts.ignore_nested_git_repo
|| has_custom_ignore_files || has_custom_ignore_files
|| has_explicit_ignores || has_explicit_ignores
} }
@ -422,6 +429,14 @@ impl Ignore {
mut m_gi_exclude, mut m_gi_exclude,
mut m_explicit, mut m_explicit,
) = (Match::None, Match::None, Match::None, Match::None, Match::None); ) = (Match::None, Match::None, Match::None, Match::None, Match::None);
if is_dir
&& self.0.opts.ignore_nested_git_repo
&& path.join(".git").exists()
{
return Match::Ignore(IgnoreMatch::nested_repo());
}
let any_git = let any_git =
!self.0.opts.require_git || self.parents().any(|ig| ig.0.has_git); !self.0.opts.require_git || self.parents().any(|ig| ig.0.has_git);
let mut saw_git = false; let mut saw_git = false;
@ -599,6 +614,7 @@ impl IgnoreBuilder {
git_ignore: true, git_ignore: true,
git_exclude: true, git_exclude: true,
ignore_case_insensitive: false, ignore_case_insensitive: false,
ignore_nested_git_repo: false,
require_git: true, require_git: true,
}, },
} }
@ -773,6 +789,17 @@ impl IgnoreBuilder {
self.opts.ignore_case_insensitive = yes; self.opts.ignore_case_insensitive = yes;
self self
} }
/// Enables ignoring nested git repositories.
///
/// This is disabled by default.
pub(crate) fn ignore_nested_git_repo(
&mut self,
yes: bool,
) -> &mut IgnoreBuilder {
self.opts.ignore_nested_git_repo = yes;
self
}
} }
/// Creates a new gitignore matcher for the directory given. /// Creates a new gitignore matcher for the directory given.
@ -887,6 +914,10 @@ mod tests {
file.write_all(contents.as_bytes()).unwrap(); file.write_all(contents.as_bytes()).unwrap();
} }
fn rmfile<P: AsRef<Path>>(path: P) {
std::fs::remove_file(path).unwrap();
}
fn mkdirp<P: AsRef<Path>>(path: P) { fn mkdirp<P: AsRef<Path>>(path: P) {
std::fs::create_dir_all(path).unwrap(); std::fs::create_dir_all(path).unwrap();
} }
@ -1132,6 +1163,41 @@ mod tests {
assert!(ig2.matched("bar", false).is_ignore()); assert!(ig2.matched("bar", false).is_ignore());
} }
//
#[test]
fn ignore_nested_git() {
let td = tmpdir();
let repo = td.path().join("foo");
mkdirp(&repo);
let dotgit_path = repo.join(".git");
wfile(&dotgit_path, "");
wfile(repo.join("bar"), "");
let (ig_default, err) =
IgnoreBuilder::new().build().add_child(td.path());
assert!(err.is_none());
let (ig_git, err) = IgnoreBuilder::new()
.ignore_nested_git_repo(true)
.build()
.add_child(td.path());
assert!(err.is_none());
// is_dir = false, so no check for .git child
assert!(ig_git.matched(&repo, false).is_none());
// on the same level as .git; it's expected that the parent directory wouldn't be recursed in this case
assert!(ig_git.matched(repo.join("bar"), false).is_none());
// is_dir = true and has .git child
assert!(ig_git.matched(&repo, true).is_ignore());
// but by default, don't ignore dir with .git child
assert!(ig_default.matched(&repo, true).is_none());
// also test with .git as a directory
rmfile(&dotgit_path);
mkdirp(&dotgit_path);
assert!(ig_git.matched(&repo, true).is_ignore());
}
#[test] #[test]
fn absolute_parent() { fn absolute_parent() {
let td = tmpdir(); let td = tmpdir();

View File

@ -811,6 +811,14 @@ impl WalkBuilder {
self self
} }
/// Enables ignoring nested git repositories, including submodules.
///
/// This is disabled by default.
pub fn ignore_nested_git_repo(&mut self, yes: bool) -> &mut WalkBuilder {
self.ig_builder.ignore_nested_git_repo(yes);
self
}
/// Set a function for sorting directory entries by their path. /// Set a function for sorting directory entries by their path.
/// ///
/// If a compare function is set, the resulting iterator will return all /// If a compare function is set, the resulting iterator will return all