Show error if pattern matches leading dot but --hidden is not given, closes #615

This commit is contained in:
sharkdp 2020-12-06 15:57:33 +01:00
parent 17bd256ae6
commit cadaef3f07
3 changed files with 75 additions and 1 deletions

View File

@ -30,7 +30,7 @@ use crate::filetypes::FileTypes;
use crate::filter::OwnerFilter;
use crate::filter::{SizeFilter, TimeFilter};
use crate::options::Options;
use crate::regex_helper::pattern_has_uppercase_char;
use crate::regex_helper::{pattern_has_uppercase_char, pattern_matches_strings_with_leading_dot};
// We use jemalloc for performance reasons, see https://github.com/sharkdp/fd/pull/481
// FIXME: re-enable jemalloc on macOS, see comment in Cargo.toml file for more infos
@ -431,6 +431,17 @@ fn run() -> Result<ExitCode> {
}),
};
if cfg!(unix)
&& config.ignore_hidden
&& pattern_matches_strings_with_leading_dot(&pattern_regex)
{
return Err(anyhow!(
"The pattern seems to only match files with a leading dot, but hidden files are \
filtered by default. Consider adding -H/--hidden to search hidden files as well \
or adjust your search pattern."
));
}
let re = RegexBuilder::new(&pattern_regex)
.case_insensitive(!config.case_sensitive)
.dot_matches_new_line(true)

View File

@ -34,6 +34,45 @@ fn hir_has_uppercase_char(hir: &Hir) -> bool {
}
}
/// Determine if a regex pattern only matches strings starting with a literal dot (hidden files)
pub fn pattern_matches_strings_with_leading_dot(pattern: &str) -> bool {
let mut parser = ParserBuilder::new().allow_invalid_utf8(true).build();
parser
.parse(pattern)
.map(|hir| hir_matches_strings_with_leading_dot(&hir))
.unwrap_or(false)
}
/// See above.
fn hir_matches_strings_with_leading_dot(hir: &Hir) -> bool {
use regex_syntax::hir::*;
// Note: this only really detects the simplest case where a regex starts with
// "^\\.", i.e. a start text anchor and a literal dot character. There are a lot
// of other patterns that ONLY match hidden files, e.g. ^(\\.foo|\\.bar) which are
// not (yet) detected by this algorithm.
match *hir.kind() {
HirKind::Concat(ref hirs) => {
let mut hirs = hirs.iter();
if let Some(hir) = hirs.next() {
if *hir.kind() != HirKind::Anchor(Anchor::StartText) {
return false;
}
} else {
return false;
}
if let Some(hir) = hirs.next() {
*hir.kind() == HirKind::Literal(Literal::Unicode('.'))
} else {
false
}
}
_ => false,
}
}
#[test]
fn pattern_has_uppercase_char_simple() {
assert!(pattern_has_uppercase_char("A"));
@ -50,3 +89,12 @@ fn pattern_has_uppercase_char_advanced() {
assert!(!pattern_has_uppercase_char(r"\Acargo"));
assert!(!pattern_has_uppercase_char(r"carg\x6F"));
}
#[test]
fn matches_strings_with_leading_dot_simple() {
assert!(pattern_matches_strings_with_leading_dot("^\\.gitignore"));
assert!(!pattern_matches_strings_with_leading_dot("^.gitignore"));
assert!(!pattern_matches_strings_with_leading_dot("\\.gitignore"));
assert!(!pattern_matches_strings_with_leading_dot("^gitignore"));
}

View File

@ -1699,3 +1699,18 @@ fn test_number_parsing_errors() {
te.assert_failure(&["--max-results=a"]);
}
/// Print error if search pattern starts with a dot and --hidden is not set
/// (Unix only, hidden files on Windows work differently)
#[test]
#[cfg(unix)]
fn test_error_if_hidden_not_set_and_pattern_starts_with_dot() {
let te = TestEnv::new(&[], &[".gitignore", ".whatever", "non-hidden"]);
te.assert_failure(&["^\\.gitignore"]);
te.assert_failure(&["--glob", ".gitignore"]);
te.assert_output(&["--hidden", "^\\.gitignore"], ".gitignore");
te.assert_output(&["--hidden", "--glob", ".gitignore"], ".gitignore");
te.assert_output(&[".gitignore"], "");
}