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.
This commit is contained in:
Jani Nikula 2017-10-14 16:16:27 +03:00 committed by David Bremner
parent 89f651a403
commit f2a6790583
2 changed files with 140 additions and 15 deletions

View file

@ -79,11 +79,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.

View file

@ -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;
}
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);
@ -981,9 +1092,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;