[PATCH 1/2] cli/new: support /<regex>/ in new.ignore

classic Classic list List threaded Threaded
2 messages Options
Jani Nikula Jani Nikula
Reply | Threaded
Open this post in threaded view
|

[PATCH 1/2] cli/new: support /<regex>/ in new.ignore

Add support for using /<regex>/ style regular expressions in
new.ignore, mixed with the old style verbatim file and directory
basenames. The regex is matched against the relative path from the
database path.
---
 doc/man1/notmuch-config.rst |  21 +++++--
 notmuch-new.c               | 134 ++++++++++++++++++++++++++++++++++++++++----
 2 files changed, 140 insertions(+), 15 deletions(-)

diff --git a/doc/man1/notmuch-config.rst b/doc/man1/notmuch-config.rst
index 6a51e64f1517..0af86f9beee4 100644
--- a/doc/man1/notmuch-config.rst
+++ b/doc/man1/notmuch-config.rst
@@ -75,11 +75,22 @@ The available configuration items are described below.
         Default: ``unread;inbox``.
 
     **new.ignore**
-        A list of file and directory names, without path, that will not
-        be searched for messages by **notmuch new**. All the files and
-        directories matching any of the names specified here will be
-        ignored, regardless of the location in the mail store directory
-        hierarchy.
+        A list to specify files and directories that will not be
+        searched for messages by **notmuch new**. Each entry in the
+        list is either:
+
+          A file or a directory name, without path, that will be
+          ignored, regardless of the location in the mail store
+          directory hierarchy.
+
+        Or:
+
+          A regular expression delimited with // that will be matched
+          against the path of the file or directory relative to the
+          database path. Matching files and directories will be
+          ignored. The beginning and end of string must be explictly
+          anchored. For example, /.*/foo$/ would match "bar/foo" and
+          "bar/baz/foo", but not "foo" or "bar/foobar".
 
         Default: empty list.
 
diff --git a/notmuch-new.c b/notmuch-new.c
index 0f50457eb894..5a262e419b44 100644
--- a/notmuch-new.c
+++ b/notmuch-new.c
@@ -42,13 +42,17 @@ enum verbosity {
 };
 
 typedef struct {
+    const char *db_path;
+
     int output_is_a_tty;
     enum verbosity verbosity;
     bool debug;
     const char **new_tags;
     size_t new_tags_length;
-    const char **new_ignore;
-    size_t new_ignore_length;
+    const char **ignore_verbatim;
+    size_t ignore_verbatim_length;
+    regex_t *ignore_regex;
+    size_t ignore_regex_length;
 
     int total_files;
     int processed_files;
@@ -240,18 +244,125 @@ _special_directory (const char *entry)
     return strcmp (entry, ".") == 0 || strcmp (entry, "..") == 0;
 }
 
+static bool
+_setup_ignore (notmuch_config_t *config, add_files_state_t *state)
+{
+    const char **ignore_list, **ignore;
+    int nregex = 0, nverbatim = 0;
+    const char **verbatim = NULL;
+    regex_t *regex = NULL;
+
+    ignore_list = notmuch_config_get_new_ignore (config, NULL);
+    if (! ignore_list)
+ return true;
+
+    for (ignore = ignore_list; *ignore; ignore++) {
+ const char *s = *ignore;
+ size_t len = strlen (s);
+
+ if (len == 0) {
+    fprintf (stderr, "Error: Empty string in new.ignore list\n");
+    return false;
+ }
+
+ if (s[0] == '/') {
+    regex_t *preg;
+    char *r;
+    int rerr;
+
+    if (len < 3 || s[len - 1] != '/') {
+ fprintf (stderr, "Error: Malformed pattern '%s' in new.ignore\n",
+ s);
+ return false;
+    }
+
+    r = talloc_strndup (config, s + 1, len - 2);
+    regex = talloc_realloc (config, regex, regex_t, nregex + 1);
+    preg = &regex[nregex];
+
+    rerr = regcomp (preg, r, REG_EXTENDED | REG_NOSUB);
+    if (rerr) {
+ size_t error_size = regerror (rerr, preg, NULL, 0);
+ char *error = talloc_size (r, error_size);
+
+ regerror (rerr, preg, error, error_size);
+
+ fprintf (stderr, "Error: Invalid regex '%s' in new.ignore: %s\n",
+ r, error);
+ return false;
+    }
+    nregex++;
+
+    talloc_free (r);
+ } else {
+    verbatim = talloc_realloc (config, verbatim, const char *,
+       nverbatim + 1);
+    verbatim[nverbatim++] = s;
+ }
+    }
+
+    state->ignore_regex = regex;
+    state->ignore_regex_length = nregex;
+    state->ignore_verbatim = verbatim;
+    state->ignore_verbatim_length = nverbatim;
+
+    return true;
+}
+
+static char *
+_get_relative_path (const char *db_path, const char *dirpath, const char *entry)
+{
+    size_t db_path_len = strlen (db_path);
+
+    /* paranoia? */
+    if (strncmp (dirpath, db_path, db_path_len) != 0) {
+ fprintf (stderr, "Warning: '%s' is not a subdirectory of '%s'\n",
+ dirpath, db_path);
+ return NULL;
+    }
+
+    dirpath += db_path_len;
+    while (*dirpath == '/')
+ dirpath++;
+
+    if (*dirpath)
+ return talloc_asprintf (NULL, "%s/%s", dirpath, entry);
+    else
+ return talloc_strdup (NULL, entry);
+}
+
 /* Test if the file/directory is to be ignored.
  */
 static bool
-_entry_in_ignore_list (const char *entry, add_files_state_t *state)
+_entry_in_ignore_list (add_files_state_t *state, const char *dirpath,
+       const char *entry)
 {
+    bool ret = false;
     size_t i;
+    char *path;
 
-    for (i = 0; i < state->new_ignore_length; i++)
- if (strcmp (entry, state->new_ignore[i]) == 0)
+    for (i = 0; i < state->ignore_verbatim_length; i++) {
+ if (strcmp (entry, state->ignore_verbatim[i]) == 0)
     return true;
+    }
 
-    return false;
+    if (state->ignore_regex_length == 0)
+ return false;
+
+    path = _get_relative_path (state->db_path, dirpath, entry);
+    if (! path)
+ return false;
+
+    for (i = 0; i < state->ignore_regex_length; i++) {
+ if (regexec (&state->ignore_regex[i], path, 0, NULL, 0) == 0) {
+    ret = true;
+    break;
+ }
+    }
+
+    talloc_free (path);
+
+    return ret;
 }
 
 /* Add a single file to the database. */
@@ -461,7 +572,7 @@ add_files (notmuch_database_t *notmuch,
  * and because we don't care if dirent_type fails on entries
  * that are explicitly ignored.
  */
- if (_entry_in_ignore_list (entry->d_name, state)) {
+ if (_entry_in_ignore_list (state, path, entry->d_name)) {
     if (state->debug)
  printf ("(D) add_files, pass 1: explicitly ignoring %s/%s\n",
  path, entry->d_name);
@@ -526,7 +637,7 @@ add_files (notmuch_database_t *notmuch,
     continue;
 
  /* Ignore files & directories user has configured to be ignored */
- if (_entry_in_ignore_list (entry->d_name, state)) {
+ if (_entry_in_ignore_list (state, path, entry->d_name)) {
     if (state->debug)
  printf ("(D) add_files, pass 2: explicitly ignoring %s/%s\n",
  path, entry->d_name);
@@ -756,7 +867,7 @@ count_files (const char *path, int *count, add_files_state_t *state)
  /* Ignore any files/directories the user has configured to be
  * ignored
  */
- if (_entry_in_ignore_list (entry->d_name, state)) {
+ if (_entry_in_ignore_list (state, path, entry->d_name)) {
     if (state->debug)
  printf ("(D) count_files: explicitly ignoring %s/%s\n",
  path, entry->d_name);
@@ -980,9 +1091,12 @@ notmuch_new_command (notmuch_config_t *config, int argc, char *argv[])
  add_files_state.verbosity = VERBOSITY_VERBOSE;
 
     add_files_state.new_tags = notmuch_config_get_new_tags (config, &add_files_state.new_tags_length);
-    add_files_state.new_ignore = notmuch_config_get_new_ignore (config, &add_files_state.new_ignore_length);
     add_files_state.synchronize_flags = notmuch_config_get_maildir_synchronize_flags (config);
     db_path = notmuch_config_get_database_path (config);
+    add_files_state.db_path = db_path;
+
+    if (! _setup_ignore (config, &add_files_state))
+ return EXIT_FAILURE;
 
     for (i = 0; i < add_files_state.new_tags_length; i++) {
  const char *error_msg;
--
2.11.0

_______________________________________________
notmuch mailing list
[hidden email]
https://notmuchmail.org/mailman/listinfo/notmuch
Jani Nikula Jani Nikula
Reply | Threaded
Open this post in threaded view
|

[PATCH 2/2] test: test regexp based new.ignore

Just some basics.
---
 test/T050-new.sh | 22 ++++++++++++++++++++++
 1 file changed, 22 insertions(+)

diff --git a/test/T050-new.sh b/test/T050-new.sh
index 272ed417aa2e..ee9ce08c8e86 100755
--- a/test/T050-new.sh
+++ b/test/T050-new.sh
@@ -259,6 +259,28 @@ ln -s i_do_not_exist "${MAIL_DIR}"/broken_link
 output=$(NOTMUCH_NEW 2>&1)
 test_expect_equal "$output" "No new mail."
 
+test_begin_subtest "Ignore files and directories specified in new.ignore (regexp)"
+notmuch config set new.ignore ".git" "/^bro.*ink\$/" "/ignored.*file/"
+output=$(NOTMUCH_NEW --debug 2>&1 | sort)
+test_expect_equal "$output" \
+"(D) add_files, pass 1: explicitly ignoring ${MAIL_DIR}/.git
+(D) add_files, pass 1: explicitly ignoring ${MAIL_DIR}/.ignored_hidden_file
+(D) add_files, pass 1: explicitly ignoring ${MAIL_DIR}/broken_link
+(D) add_files, pass 1: explicitly ignoring ${MAIL_DIR}/ignored_file
+(D) add_files, pass 1: explicitly ignoring ${MAIL_DIR}/one/ignored_file
+(D) add_files, pass 1: explicitly ignoring ${MAIL_DIR}/one/two/ignored_file
+(D) add_files, pass 1: explicitly ignoring ${MAIL_DIR}/one/two/three/.git
+(D) add_files, pass 1: explicitly ignoring ${MAIL_DIR}/one/two/three/ignored_file
+(D) add_files, pass 2: explicitly ignoring ${MAIL_DIR}/.git
+(D) add_files, pass 2: explicitly ignoring ${MAIL_DIR}/.ignored_hidden_file
+(D) add_files, pass 2: explicitly ignoring ${MAIL_DIR}/broken_link
+(D) add_files, pass 2: explicitly ignoring ${MAIL_DIR}/ignored_file
+(D) add_files, pass 2: explicitly ignoring ${MAIL_DIR}/one/ignored_file
+(D) add_files, pass 2: explicitly ignoring ${MAIL_DIR}/one/two/ignored_file
+(D) add_files, pass 2: explicitly ignoring ${MAIL_DIR}/one/two/three/.git
+(D) add_files, pass 2: explicitly ignoring ${MAIL_DIR}/one/two/three/ignored_file
+No new mail."
+
 test_begin_subtest "Quiet: No new mail."
 output=$(NOTMUCH_NEW --quiet)
 test_expect_equal "$output" ""
--
2.11.0

_______________________________________________
notmuch mailing list
[hidden email]
https://notmuchmail.org/mailman/listinfo/notmuch