IMAP clients (such as webmail) use folders to mark messages as junk
etc., some even to mark messages as trash ("move to trash"). Such a
change is reported by notmuch as a rename; the message is not tagged
with new.tags since it is not new, so that there is no way to act upon a
rename.

Introduce new.rename_tags (default: not set) which are added by `notmuch
new` to renamed messages. This allows to act upon renames, e.g. to keep
the IMAP folder structure in sync with tags with a tool like `afew` or
homecooked scripts simply by filtering for this tag in the same ways as
one would filter for new messages using new.tags.

Signed-off-by: Michael J Gruber <g...@grubix.eu>
---
Changed since v1:
- acted upon review comments (blank line, _thaw position)
- added 3 tests (mv, cp, cp-rm)
- treat copies as renames, too

The reasoning behind the latter is: If you use a mapping between folders
and tags, then a copy to an additional location should alert the
"mapper" to update that mapping; that's what the rename tag is for.
Maybe it should be named "renew" after all? But it's just the
folder/label name that is/needs to be renewed, nothing else about the
message.

Interdiff against v1:
  diff --git a/notmuch-new.c b/notmuch-new.c
  index e6d3dc82..e893fa21 100644
  --- a/notmuch-new.c
  +++ b/notmuch-new.c
  @@ -401,6 +401,13 @@ add_file (notmuch_database_t *notmuch, const char 
*filename,
        break;
       /* Non-fatal issues (go on to next file). */
       case NOTMUCH_STATUS_DUPLICATE_MESSAGE_ID:
  +     notmuch_message_freeze (message);
  +
  +     for (tag = state->rename_tags; tag != NULL && *tag != NULL; tag++) {
  +         notmuch_message_add_tag (message, *tag);
  +     }
  +
  +     notmuch_message_thaw (message);
        if (state->synchronize_flags)
            notmuch_message_maildir_flags_to_tags (message);
        break;
  @@ -958,10 +965,9 @@ remove_filename (notmuch_database_t *notmuch,
            notmuch_message_add_tag (message, *tag);
        }
   
  -
  +     notmuch_message_thaw (message);
        if (add_files_state->synchronize_flags == true)
            notmuch_message_maildir_flags_to_tags (message);
  -     notmuch_message_thaw (message);
        status = NOTMUCH_STATUS_SUCCESS;
       } else if (status == NOTMUCH_STATUS_SUCCESS) {
        add_files_state->removed_messages++;
  diff --git a/test/T340-maildir-sync.sh b/test/T340-maildir-sync.sh
  index 7fece5f2..44f32ad2 100755
  --- a/test/T340-maildir-sync.sh
  +++ b/test/T340-maildir-sync.sh
  @@ -196,6 +196,36 @@ notmuch search 'subject:"File in new"' | 
notmuch_search_sanitize > output
   test_expect_equal "$(< output)" \
   "thread:XXX   2001-01-05 [1/1] Notmuch Test Suite; File in new/ (test 
unread)"
   
  +test_begin_subtest "Renamed files get default renamed tags"
  +OLDCONFIG=$(notmuch config get new.rename_tags)
  +notmuch config set new.rename_tags "renamed"
  +mv $MAIL_DIR/new/file-in-new $MAIL_DIR/new/file-in-new-renamed
  +notmuch new
  +notmuch config set new.rename_tags $OLDCONFIG
  +notmuch search 'subject:"File in new"' | notmuch_search_sanitize > output
  +test_expect_equal "$(< output)" \
  +"thread:XXX   2001-01-05 [1/1] Notmuch Test Suite; File in new/ (renamed 
test unread)"
  +
  +test_begin_subtest "Copied files do get new default renamed tags"
  +OLDCONFIG=$(notmuch config get new.rename_tags)
  +notmuch config set new.rename_tags "copied"
  +cp $MAIL_DIR/new/file-in-new-renamed $MAIL_DIR/new/file-in-new-copied
  +notmuch new
  +notmuch config set new.rename_tags $OLDCONFIG
  +notmuch search 'subject:"File in new"' | notmuch_search_sanitize > output
  +test_expect_equal "$(< output)" \
  +"thread:XXX   2001-01-05 [1/1(2)] Notmuch Test Suite; File in new/ (copied 
renamed test unread)"
  +
  +test_begin_subtest "Renamed files (cp+rm) get default renamed tags"
  +OLDCONFIG=$(notmuch config get new.rename_tags)
  +notmuch config set new.rename_tags "cprm"
  +rm $MAIL_DIR/new/file-in-new-renamed
  +notmuch new
  +notmuch config set new.rename_tags $OLDCONFIG
  +notmuch search 'subject:"File in new"' | notmuch_search_sanitize > output
  +test_expect_equal "$(< output)" \
  +"thread:XXX   2001-01-05 [1/1] Notmuch Test Suite; File in new/ (copied cprm 
renamed test unread)"
  +
   for tag in draft flagged passed replied; do
       test_begin_subtest "$tag is valid in new.tags"
       OLDCONFIG=$(notmuch config get new.tags)

 NEWS                        | 11 +++++++++++
 doc/man1/notmuch-config.rst |  6 ++++++
 notmuch-client.h            |  8 ++++++++
 notmuch-config.c            | 26 ++++++++++++++++++++++++++
 notmuch-new.c               | 29 +++++++++++++++++++++++++++++
 test/T340-maildir-sync.sh   | 30 ++++++++++++++++++++++++++++++
 6 files changed, 110 insertions(+)

diff --git a/NEWS b/NEWS
index 240d594b..e3b75e74 100644
--- a/NEWS
+++ b/NEWS
@@ -1,3 +1,14 @@
+Notmuch 0.28 (UNRELEASED)
+=========================
+
+New command-line features
+-------------------------
+
+User-configurable tags for renamed messages
+
+  A new "new.rename_tags" option is available in the configuration file to
+  determine which tags are applied to renamed (moved) messages.
+
 Notmuch 0.27 (2018-06-13)
 =========================
 
diff --git a/doc/man1/notmuch-config.rst b/doc/man1/notmuch-config.rst
index 89909808..9e4198a1 100644
--- a/doc/man1/notmuch-config.rst
+++ b/doc/man1/notmuch-config.rst
@@ -77,6 +77,12 @@ The available configuration items are described below.
 
     Default: ``unread;inbox``.
 
+**new.rename_tags**
+    A list of tags that will be added to all messages which
+    **notmuch new** identifies as renamed (moved).
+
+    Default: not set.
+
 **new.ignore**
     A list to specify files and directories that will not be searched
     for messages by **notmuch new**. Each entry in the list is either:
diff --git a/notmuch-client.h b/notmuch-client.h
index 6c84ecc0..5e1e6b66 100644
--- a/notmuch-client.h
+++ b/notmuch-client.h
@@ -316,6 +316,14 @@ notmuch_config_set_new_tags (notmuch_config_t *config,
                             const char *new_tags[],
                             size_t length);
 
+const char **
+notmuch_config_get_rename_tags (notmuch_config_t *config,
+                            size_t *length);
+void
+notmuch_config_set_rename_tags (notmuch_config_t *config,
+                            const char *rename_tags[],
+                            size_t length);
+
 const char **
 notmuch_config_get_new_ignore (notmuch_config_t *config,
                               size_t *length);
diff --git a/notmuch-config.c b/notmuch-config.c
index e1b16609..02f7d247 100644
--- a/notmuch-config.c
+++ b/notmuch-config.c
@@ -132,6 +132,8 @@ struct _notmuch_config {
     size_t user_other_email_length;
     const char **new_tags;
     size_t new_tags_length;
+    const char **rename_tags;
+    size_t rename_tags_length;
     const char **new_ignore;
     size_t new_ignore_length;
     bool maildir_synchronize_flags;
@@ -712,6 +714,14 @@ notmuch_config_get_new_tags (notmuch_config_t *config,   
size_t *length)
                             &(config->new_tags_length), length);
 }
 
+const char **
+notmuch_config_get_rename_tags (notmuch_config_t *config,   size_t *length)
+{
+    return _config_get_list (config, "new", "rename_tags",
+                            &(config->rename_tags),
+                            &(config->rename_tags_length), length);
+}
+
 const char **
 notmuch_config_get_new_ignore (notmuch_config_t *config, size_t *length)
 {
@@ -738,6 +748,15 @@ notmuch_config_set_new_tags (notmuch_config_t *config,
                     &(config->new_tags));
 }
 
+void
+notmuch_config_set_rename_tags (notmuch_config_t *config,
+                                    const char *list[],
+                                    size_t length)
+{
+    _config_set_list (config, "new", "rename_tags", list, length,
+                    &(config->rename_tags));
+}
+
 void
 notmuch_config_set_new_ignore (notmuch_config_t *config,
                               const char *list[],
@@ -867,6 +886,13 @@ notmuch_config_command_get (notmuch_config_t *config, char 
*item)
        tags = notmuch_config_get_new_tags (config, &length);
        for (i = 0; i < length; i++)
            printf ("%s\n", tags[i]);
+    } else if (strcmp(item, "new.rename_tags") == 0) {
+       const char **tags;
+       size_t i, length;
+
+       tags = notmuch_config_get_rename_tags (config, &length);
+       for (i = 0; i < length; i++)
+           printf ("%s\n", tags[i]);
     } else if (STRNCMP_LITERAL (item, BUILT_WITH_PREFIX) == 0) {
        printf ("%s\n",
                notmuch_built_with (item + strlen (BUILT_WITH_PREFIX)) ? "true" 
: "false");
diff --git a/notmuch-new.c b/notmuch-new.c
index 6a54a1a1..e893fa21 100644
--- a/notmuch-new.c
+++ b/notmuch-new.c
@@ -50,6 +50,8 @@ typedef struct {
     bool full_scan;
     const char **new_tags;
     size_t new_tags_length;
+    const char **rename_tags;
+    size_t rename_tags_length;
     const char **ignore_verbatim;
     size_t ignore_verbatim_length;
     regex_t *ignore_regex;
@@ -399,6 +401,13 @@ add_file (notmuch_database_t *notmuch, const char 
*filename,
        break;
     /* Non-fatal issues (go on to next file). */
     case NOTMUCH_STATUS_DUPLICATE_MESSAGE_ID:
+       notmuch_message_freeze (message);
+
+       for (tag = state->rename_tags; tag != NULL && *tag != NULL; tag++) {
+           notmuch_message_add_tag (message, *tag);
+       }
+
+       notmuch_message_thaw (message);
        if (state->synchronize_flags)
            notmuch_message_maildir_flags_to_tags (message);
        break;
@@ -948,7 +957,15 @@ remove_filename (notmuch_database_t *notmuch,
 
     status = notmuch_database_remove_message (notmuch, path);
     if (status == NOTMUCH_STATUS_DUPLICATE_MESSAGE_ID) {
+        const char **tag;
        add_files_state->renamed_messages++;
+       notmuch_message_freeze (message);
+
+       for (tag = add_files_state->rename_tags; tag != NULL && *tag != NULL; 
tag++) {
+           notmuch_message_add_tag (message, *tag);
+       }
+
+       notmuch_message_thaw (message);
        if (add_files_state->synchronize_flags == true)
            notmuch_message_maildir_flags_to_tags (message);
        status = NOTMUCH_STATUS_SUCCESS;
@@ -1095,6 +1112,7 @@ 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.rename_tags = notmuch_config_get_rename_tags (config, 
&add_files_state.rename_tags_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;
@@ -1113,6 +1131,17 @@ notmuch_new_command (notmuch_config_t *config, int argc, 
char *argv[])
        }
     }
 
+    for (i = 0; i < add_files_state.rename_tags_length; i++) {
+       const char *error_msg;
+
+       error_msg = illegal_tag (add_files_state.rename_tags[i], false);
+       if (error_msg) {
+           fprintf (stderr, "Error: tag '%s' in rename.tags: %s\n",
+                    add_files_state.rename_tags[i], error_msg);
+           return EXIT_FAILURE;
+       }
+    }
+
     if (hooks) {
        ret = notmuch_run_hook (db_path, "pre-new");
        if (ret)
diff --git a/test/T340-maildir-sync.sh b/test/T340-maildir-sync.sh
index 7fece5f2..44f32ad2 100755
--- a/test/T340-maildir-sync.sh
+++ b/test/T340-maildir-sync.sh
@@ -196,6 +196,36 @@ notmuch search 'subject:"File in new"' | 
notmuch_search_sanitize > output
 test_expect_equal "$(< output)" \
 "thread:XXX   2001-01-05 [1/1] Notmuch Test Suite; File in new/ (test unread)"
 
+test_begin_subtest "Renamed files get default renamed tags"
+OLDCONFIG=$(notmuch config get new.rename_tags)
+notmuch config set new.rename_tags "renamed"
+mv $MAIL_DIR/new/file-in-new $MAIL_DIR/new/file-in-new-renamed
+notmuch new
+notmuch config set new.rename_tags $OLDCONFIG
+notmuch search 'subject:"File in new"' | notmuch_search_sanitize > output
+test_expect_equal "$(< output)" \
+"thread:XXX   2001-01-05 [1/1] Notmuch Test Suite; File in new/ (renamed test 
unread)"
+
+test_begin_subtest "Copied files do get new default renamed tags"
+OLDCONFIG=$(notmuch config get new.rename_tags)
+notmuch config set new.rename_tags "copied"
+cp $MAIL_DIR/new/file-in-new-renamed $MAIL_DIR/new/file-in-new-copied
+notmuch new
+notmuch config set new.rename_tags $OLDCONFIG
+notmuch search 'subject:"File in new"' | notmuch_search_sanitize > output
+test_expect_equal "$(< output)" \
+"thread:XXX   2001-01-05 [1/1(2)] Notmuch Test Suite; File in new/ (copied 
renamed test unread)"
+
+test_begin_subtest "Renamed files (cp+rm) get default renamed tags"
+OLDCONFIG=$(notmuch config get new.rename_tags)
+notmuch config set new.rename_tags "cprm"
+rm $MAIL_DIR/new/file-in-new-renamed
+notmuch new
+notmuch config set new.rename_tags $OLDCONFIG
+notmuch search 'subject:"File in new"' | notmuch_search_sanitize > output
+test_expect_equal "$(< output)" \
+"thread:XXX   2001-01-05 [1/1] Notmuch Test Suite; File in new/ (copied cprm 
renamed test unread)"
+
 for tag in draft flagged passed replied; do
     test_begin_subtest "$tag is valid in new.tags"
     OLDCONFIG=$(notmuch config get new.tags)
-- 
2.19.0.612.g94276ab026

_______________________________________________
notmuch mailing list
notmuch@notmuchmail.org
https://notmuchmail.org/mailman/listinfo/notmuch

Reply via email to