Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add option to ignore nested git repositories #2751

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 4 additions & 0 deletions crates/core/flags/complete/rg.zsh
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,10 @@ _rg() {
'--ignore-file-case-insensitive[process ignore files case insensitively]'
$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
"--no-ignore-exclude[don't respect local exclude (ignore) files]"
$no'--ignore-exclude[respect local exclude (ignore) files]'
Expand Down
57 changes: 57 additions & 0 deletions crates/core/flags/defs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ pub(super) const FLAGS: &[&dyn Flag] = &[
&IgnoreCase,
&IgnoreFile,
&IgnoreFileCaseInsensitive,
&IgnoreNestedGit,
&IncludeZero,
&InvertMatch,
&JSON,
Expand Down Expand Up @@ -3238,6 +3239,62 @@ fn test_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
#[derive(Debug)]
struct IncludeZero;
Expand Down
5 changes: 4 additions & 1 deletion crates/core/flags/hiargs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ pub(crate) struct HiArgs {
hyperlink_config: grep::printer::HyperlinkConfig,
ignore_file_case_insensitive: bool,
ignore_file: Vec<PathBuf>,
ignore_nested_git: bool,
include_zero: bool,
invert_match: bool,
is_terminal_stdout: bool,
Expand Down Expand Up @@ -275,6 +276,7 @@ impl HiArgs {
hyperlink_config,
ignore_file: low.ignore_file,
ignore_file_case_insensitive: low.ignore_file_case_insensitive,
ignore_nested_git: low.ignore_nested_git,
include_zero: low.include_zero,
invert_match: low.invert_match,
is_terminal_stdout: state.is_terminal_stdout,
Expand Down Expand Up @@ -893,7 +895,8 @@ impl HiArgs {
.git_ignore(!self.no_ignore_vcs)
.git_exclude(!self.no_ignore_vcs && !self.no_ignore_exclude)
.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 {
builder.add_custom_ignore_filename(".rgignore");
}
Expand Down
1 change: 1 addition & 0 deletions crates/core/flags/lowargs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ pub(crate) struct LowArgs {
pub(crate) iglobs: Vec<String>,
pub(crate) ignore_file: Vec<PathBuf>,
pub(crate) ignore_file_case_insensitive: bool,
pub(crate) ignore_nested_git: bool,
pub(crate) include_zero: bool,
pub(crate) invert_match: bool,
pub(crate) line_number: Option<bool>,
Expand Down
66 changes: 66 additions & 0 deletions crates/ignore/src/dir.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ enum IgnoreMatchInner<'a> {
Gitignore(&'a gitignore::Glob),
Types(types::Glob<'a>),
Hidden,
NestedRepo,
}

impl<'a> IgnoreMatch<'a> {
Expand All @@ -64,6 +65,9 @@ impl<'a> IgnoreMatch<'a> {
fn hidden() -> IgnoreMatch<'static> {
IgnoreMatch(IgnoreMatchInner::Hidden)
}
fn nested_repo() -> IgnoreMatch<'static> {
IgnoreMatch(IgnoreMatchInner::NestedRepo)
}
}

/// Options for the ignore matcher, shared between the matcher itself and the
Expand All @@ -84,6 +88,8 @@ struct IgnoreOptions {
git_exclude: bool,
/// Whether to ignore files case insensitively
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
/// git-related ignore rules.
require_git: bool,
Expand Down Expand Up @@ -342,6 +348,7 @@ impl Ignore {
|| opts.git_global
|| opts.git_ignore
|| opts.git_exclude
|| opts.ignore_nested_git_repo
|| has_custom_ignore_files
|| has_explicit_ignores
}
Expand Down Expand Up @@ -422,6 +429,14 @@ impl Ignore {
mut m_gi_exclude,
mut m_explicit,
) = (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 =
!self.0.opts.require_git || self.parents().any(|ig| ig.0.has_git);
let mut saw_git = false;
Expand Down Expand Up @@ -599,6 +614,7 @@ impl IgnoreBuilder {
git_ignore: true,
git_exclude: true,
ignore_case_insensitive: false,
ignore_nested_git_repo: false,
require_git: true,
},
}
Expand Down Expand Up @@ -773,6 +789,17 @@ impl IgnoreBuilder {
self.opts.ignore_case_insensitive = yes;
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.
Expand Down Expand Up @@ -887,6 +914,10 @@ mod tests {
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) {
std::fs::create_dir_all(path).unwrap();
}
Expand Down Expand Up @@ -1132,6 +1163,41 @@ mod tests {
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]
fn absolute_parent() {
let td = tmpdir();
Expand Down
8 changes: 8 additions & 0 deletions crates/ignore/src/walk.rs
Original file line number Diff line number Diff line change
Expand Up @@ -811,6 +811,14 @@ impl WalkBuilder {
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.
///
/// If a compare function is set, the resulting iterator will return all
Expand Down