From 1c3d5ab15860809f4e437b95dde2cb5926f84bae Mon Sep 17 00:00:00 2001 From: Christian Kamm Date: Tue, 28 Nov 2017 09:41:52 +0100 Subject: [PATCH] Excludes: Introduce dir-only regex matches --- src/csync/csync_exclude.cpp | 62 +++++++++++++------ src/csync/csync_exclude.h | 3 +- .../csync/csync_tests/check_csync_exclude.cpp | 31 +++++++++- 3 files changed, 75 insertions(+), 21 deletions(-) diff --git a/src/csync/csync_exclude.cpp b/src/csync/csync_exclude.cpp index 37f898486..b3220d59e 100644 --- a/src/csync/csync_exclude.cpp +++ b/src/csync/csync_exclude.cpp @@ -463,7 +463,7 @@ void ExcludedFiles::clearManualExcludes() bool ExcludedFiles::reloadExcludeFiles() { _allExcludes.clear(); - _regex = QRegularExpression(); + _bnameRegexFileDir = QRegularExpression(); bool success = true; foreach (const QString &file, _excludeFiles) { @@ -538,7 +538,12 @@ CSYNC_EXCLUDE_TYPE ExcludedFiles::traversalPatternMatch(const char *path, int fi bname = path; } QString p = QString::fromUtf8(bname); - auto m = _regex.match(p); + QRegularExpressionMatch m; + if (filetype == CSYNC_FTW_TYPE_DIR) { + m = _bnameRegexDir.match(p); + } else { + m = _bnameRegexFileDir.match(p); + } if (m.hasMatch()) { if (!m.captured(1).isEmpty()) { match = CSYNC_FILE_EXCLUDE_LIST; @@ -552,6 +557,7 @@ CSYNC_EXCLUDE_TYPE ExcludedFiles::traversalPatternMatch(const char *path, int fi CSYNC_EXCLUDE_TYPE ExcludedFiles::fullPatternMatch(const char *path, int filetype) const { + // bname check must also apply to all path components return _csync_excluded_common(_allExcludes, path, filetype, true); } @@ -573,39 +579,59 @@ void ExcludedFiles::prepare() _nonRegexExcludes.clear(); // Start out with regexes that would match nothing - QString exclude_only = "a^"; - QString exclude_and_remove = "a^"; + QString patternFileDirKeep = "a^"; + QString patternFileDirRemove = "a^"; + QString patternDirKeep = "a^"; + QString patternDirRemove = "a^"; + auto regexAppend = [](QString &pattern, QString v) { + if (!pattern.isEmpty()) + pattern.append("|"); + pattern.append(v); + }; for (auto exclude : _allExcludes) { - QString *builderToUse = &exclude_only; if (exclude[0] == '\n') continue; // empty line if (exclude[0] == '\r') continue; // empty line + bool matchDirOnly = exclude.endsWith('/'); + if (matchDirOnly) + exclude = exclude.left(exclude.size() - 1); + + bool removeExcluded = (exclude[0] == ']'); + if (removeExcluded) + exclude = exclude.mid(1); + /* If an exclude entry contains some fnmatch-ish characters, we use the C-style codepath without QRegularEpression */ if (strchr(exclude, '/') || strchr(exclude, '[') || strchr(exclude, '{') || strchr(exclude, '\\')) { _nonRegexExcludes.append(exclude); continue; } - /* Those will attempt to use QRegularExpression */ - if (exclude[0] == ']') { - exclude = exclude.mid(1); - builderToUse = &exclude_and_remove; - } - if (builderToUse->size() > 0) { - builderToUse->append("|"); - } - builderToUse->append(convertToBnameRegexpSyntax(QString::fromUtf8(exclude))); + /* Use QRegularExpression, append to the right pattern */ + auto &patternFileDir = removeExcluded ? patternFileDirRemove : patternFileDirKeep; + auto &patternDir = removeExcluded ? patternDirRemove : patternDirKeep; + + auto regexExclude = convertToBnameRegexpSyntax(QString::fromUtf8(exclude)); + // patterns always match against directories + regexAppend(patternDir, regexExclude); + // but some don't match against files + if (!matchDirOnly) + regexAppend(patternFileDir, regexExclude); } - QString pattern = "^(" + exclude_only + ")$|^(" + exclude_and_remove + ")$"; - _regex.setPattern(pattern); + _bnameRegexFileDir.setPattern( + "^(" + patternFileDirKeep + ")$|^(" + patternFileDirRemove + ")$"); + _bnameRegexDir.setPattern( + "^(" + patternDirKeep + ")$|^(" + patternDirRemove + ")$"); + QRegularExpression::PatternOptions patternOptions = QRegularExpression::OptimizeOnFirstUsageOption; if (OCC::Utility::fsCasePreserving()) patternOptions |= QRegularExpression::CaseInsensitiveOption; - _regex.setPatternOptions(patternOptions); - _regex.optimize(); + _bnameRegexFileDir.setPatternOptions(patternOptions); + _bnameRegexFileDir.optimize(); + _bnameRegexDir.setPatternOptions(patternOptions); + _bnameRegexDir.optimize(); } diff --git a/src/csync/csync_exclude.h b/src/csync/csync_exclude.h index 7dc5808bd..08d0a63b1 100644 --- a/src/csync/csync_exclude.h +++ b/src/csync/csync_exclude.h @@ -165,7 +165,8 @@ private: QList _nonRegexExcludes; /// see prepare() - QRegularExpression _regex; + QRegularExpression _bnameRegexFileDir; + QRegularExpression _bnameRegexDir; }; #endif /* _CSYNC_EXCLUDE_H */ diff --git a/test/csync/csync_tests/check_csync_exclude.cpp b/test/csync/csync_tests/check_csync_exclude.cpp index 9329ca6de..c8e3a48ad 100644 --- a/test/csync/csync_tests/check_csync_exclude.cpp +++ b/test/csync/csync_tests/check_csync_exclude.cpp @@ -113,12 +113,12 @@ static void check_csync_exclude_add(void **) excludedFiles->prepare(); assert_true(excludedFiles->_nonRegexExcludes.contains("/tmp/check_csync1/*")); - assert_false(excludedFiles->_regex.pattern().contains("csync1")); + assert_false(excludedFiles->_bnameRegexFileDir.pattern().contains("csync1")); excludedFiles->addManualExclude("foo"); excludedFiles->prepare(); assert_true(excludedFiles->_nonRegexExcludes.size() == 1); - assert_true(excludedFiles->_regex.pattern().contains("foo")); + assert_true(excludedFiles->_bnameRegexFileDir.pattern().contains("foo")); } static void check_csync_excluded(void **) @@ -356,6 +356,32 @@ static void check_csync_excluded_traversal(void **) assert_int_equal(check_file_traversal("a * ?"), CSYNC_FILE_EXCLUDE_LIST); } +static void check_csync_dir_only(void **) +{ + excludedFiles->addManualExclude("filedir"); + excludedFiles->addManualExclude("dir/"); + excludedFiles->prepare(); + + assert_int_equal(check_file_traversal("other"), CSYNC_NOT_EXCLUDED); + assert_int_equal(check_file_traversal("filedir"), CSYNC_FILE_EXCLUDE_LIST); + assert_int_equal(check_file_traversal("dir"), CSYNC_NOT_EXCLUDED); + assert_int_equal(check_file_traversal("s/other"), CSYNC_NOT_EXCLUDED); + assert_int_equal(check_file_traversal("s/filedir"), CSYNC_FILE_EXCLUDE_LIST); + assert_int_equal(check_file_traversal("s/dir"), CSYNC_NOT_EXCLUDED); + + assert_int_equal(check_dir_traversal("other"), CSYNC_NOT_EXCLUDED); + assert_int_equal(check_dir_traversal("filedir"), CSYNC_FILE_EXCLUDE_LIST); + assert_int_equal(check_dir_traversal("dir"), CSYNC_FILE_EXCLUDE_LIST); + assert_int_equal(check_dir_traversal("s/other"), CSYNC_NOT_EXCLUDED); + assert_int_equal(check_dir_traversal("s/filedir"), CSYNC_FILE_EXCLUDE_LIST); + assert_int_equal(check_dir_traversal("s/dir"), CSYNC_FILE_EXCLUDE_LIST); + + assert_int_equal(check_dir_full("filedir/foo"), CSYNC_FILE_EXCLUDE_LIST); + assert_int_equal(check_file_full("filedir/foo"), CSYNC_FILE_EXCLUDE_LIST); + assert_int_equal(check_dir_full("dir/foo"), CSYNC_FILE_EXCLUDE_LIST); + assert_int_equal(check_file_full("dir/foo"), CSYNC_FILE_EXCLUDE_LIST); +} + static void check_csync_pathes(void **) { excludedFiles->addManualExclude("/exclude"); @@ -495,6 +521,7 @@ int torture_run_tests(void) cmocka_unit_test_setup_teardown(check_csync_exclude_add, setup, teardown), cmocka_unit_test_setup_teardown(check_csync_excluded, setup_init, teardown), cmocka_unit_test_setup_teardown(check_csync_excluded_traversal, setup_init, teardown), + cmocka_unit_test_setup_teardown(check_csync_dir_only, setup_init, teardown), cmocka_unit_test_setup_teardown(check_csync_pathes, setup_init, teardown), cmocka_unit_test_setup_teardown(check_csync_is_windows_reserved_word, setup_init, teardown), cmocka_unit_test_setup_teardown(check_csync_excluded_performance, setup_init, teardown),