Reply code with TEXT/PLAIN

2012-03-24 Thread Mark Walters


I am not certain if this is a bug or a request to work around broken
mailers. I tried replying to a message today (with recent git) and got
an empty message. I looked at the json output for reply and it contains
the message but the content type is TEXT/PLAIN rather than
text/plain.

This seems to mean it doesn't match in notmuch-match-content-type
(notmuch-lib.el) called from notmuch-mua-get-quotable-parts (in
notmuch-mua.el); and so does not get included.

Making the match case-insensitive `fixed' the problem.

I don't know whether content-types are allowed  to be upper-case but I
seem to have several mails where they are. I am also not sure whether
the correct fix is in the emacs code, or in the cli reply format
(i.e. perhaps the reply format should lower-case the content-type).

Best wishes

Mark

PS Sorry Adam for the duplicate: I sent from a wrong address (my fault
this time)!


[BUG/PATCH 2/2] emacs: Fix replying from alternate addresses

2012-03-24 Thread Adam Wolfe Gordon
The bug was that notmuch-mua-mail used `mail-header` to check whether
it was passed a "From" header. The implementation of `mail-header`
must try to compare symbols instead of strings when looking for
headers, as it was returning nil when a From header was present. This
is probably because the mail functions construct headers as alists
with symbols for the header names, while our code uses strings for the
header names.

Since we don't use `mail-header` anywhere else, and `message-mail` is
perfectly happy to accept string header names, the fix is just to use
`assoc` to look for the From header, so that the strings get compared
properly.
---
 emacs/notmuch-mua.el |4 ++--
 test/emacs   |1 -
 2 files changed, 2 insertions(+), 3 deletions(-)

diff --git a/emacs/notmuch-mua.el b/emacs/notmuch-mua.el
index 6aae3a0..9805d79 100644
--- a/emacs/notmuch-mua.el
+++ b/emacs/notmuch-mua.el
@@ -187,7 +187,7 @@ OTHER-ARGS are passed through to `message-mail'."
   (when (not (string= "" user-agent))
(push (cons "User-Agent" user-agent) other-headers

-  (unless (mail-header 'From other-headers)
+  (unless (assoc "From" other-headers)
 (push (cons "From" (concat
(notmuch-user-name) " <" (notmuch-user-primary-email) 
">")) other-headers))

@@ -250,7 +250,7 @@ the From: address first."
   (interactive "P")
   (let ((other-headers
 (when (or prompt-for-sender notmuch-always-prompt-for-sender)
-  (list (cons 'From (notmuch-mua-prompt-for-sender))
+  (list (cons "From" (notmuch-mua-prompt-for-sender))
 (notmuch-mua-mail nil nil other-headers)))

 (defun notmuch-mua-new-forward-message (&optional prompt-for-sender)
diff --git a/test/emacs b/test/emacs
index fa5d706..08db1ee 100755
--- a/test/emacs
+++ b/test/emacs
@@ -275,7 +275,6 @@ EOF
 test_expect_equal_file OUTPUT EXPECTED

 test_begin_subtest "Reply from alternate address within emacs"
-test_subtest_known_broken
 add_message '[from]="Sender "' \
 [to]=test_suite_other at notmuchmail.org \
 [subject]=notmuch-reply-test \
-- 
1.7.5.4



[BUG/PATCH 1/2] test: Tests for reply from alternate addresses in emacs

2012-03-24 Thread Adam Wolfe Gordon
Since the recent reply changes were pushed, there has been a bug that
causes emacs to always reply from the primary address, even if the
JSON or default CLI reply output uses an alternate address.

This adds two tests to the emacs test library based on the two "Reply
form..." tests in the reply test library. One is currently marked
broken.
---
 test/emacs |   52 
 1 files changed, 52 insertions(+), 0 deletions(-)

diff --git a/test/emacs b/test/emacs
index 8a28705..fa5d706 100755
--- a/test/emacs
+++ b/test/emacs
@@ -274,6 +274,58 @@ Notmuch Test Suite  writes:
 EOF
 test_expect_equal_file OUTPUT EXPECTED

+test_begin_subtest "Reply from alternate address within emacs"
+test_subtest_known_broken
+add_message '[from]="Sender "' \
+[to]=test_suite_other at notmuchmail.org \
+[subject]=notmuch-reply-test \
+   '[date]="Tue, 05 Jan 2010 15:43:56 -"' \
+   '[body]="reply from alternate address"'
+
+test_emacs "(notmuch-search \"id:\\\"${gen_msg_id}\\\"\")
+   (notmuch-test-wait)
+   (notmuch-search-reply-to-thread)
+   (test-output)"
+sed -i -e 's/^In-Reply-To: <.*>$/In-Reply-To: /' OUTPUT
+cat  reply from alternate address
+EOF
+test_expect_equal_file OUTPUT EXPECTED
+
+test_begin_subtest "Reply from address in named group list within emacs"
+add_message '[from]="Sender "' \
+'[to]=group:test_suite at notmuchmail.org,someone at 
example.com\;' \
+ [cc]=test_suite_other at notmuchmail.org \
+ [subject]=notmuch-reply-test \
+'[date]="Tue, 05 Jan 2010 15:43:56 -"' \
+'[body]="Reply from address in named group list"'
+
+test_emacs "(notmuch-search \"id:\\\"${gen_msg_id}\\\"\")
+   (notmuch-test-wait)
+   (notmuch-search-reply-to-thread)
+   (test-output)"
+sed -i -e 's/^In-Reply-To: <.*>$/In-Reply-To: /' OUTPUT
+cat  Reply from address in named group list
+EOF
+test_expect_equal_file OUTPUT EXPECTED
+
 test_begin_subtest "Reply within emacs to a multipart/mixed message"
 test_emacs '(notmuch-show "id:20091118002059.067214ed at hikari")
(notmuch-show-reply)
-- 
1.7.5.4



[BUG/PATCH 0/2] Replying from other addresses in emacs

2012-03-24 Thread Adam Wolfe Gordon
Hi everyone,

This patch series adds a test for and fixes a bug that Dmitry pointed
out on IRC yesterday:

When replying to mail sent to an other_address, the CLI correctly sets
the From header to the address that received the message. This works
correctly in the default format and the JSON format.  However, emacs was
ignoring the provided From header and inserting the primary address, due
to an incompatibility between how we construct the header list and how
the `mail-header` function from the message library looks up header
values in the list.

People with other_addresses who are using the latest reply changes
should probably apply this patch right away. Without it, you might end
up replying to messages from the wrong address.

Adam Wolfe Gordon (2):
  test: Tests for reply from alternate addresses in emacs
  emacs: Fix replying from alternate addresses

 emacs/notmuch-mua.el |4 +-
 test/emacs   |   51 ++
 2 files changed, 53 insertions(+), 2 deletions(-)

-- 
1.7.5.4



Reply code with TEXT/PLAIN

2012-03-24 Thread Adam Wolfe Gordon
Hi Mark,

On Sat, Mar 24, 2012 at 15:49, Mark Walters  
wrote:
> I am not certain if this is a bug or a request to work around broken
> mailers. I tried replying to a message today (with recent git) and got
> an empty message. I looked at the json output for reply and it contains
> the message but the content type is TEXT/PLAIN rather than
> text/plain.
>
> This seems to mean it doesn't match in notmuch-match-content-type
> (notmuch-lib.el) called from notmuch-mua-get-quotable-parts (in
> notmuch-mua.el); and so does not get included.
>
> Making the match case-insensitive `fixed' the problem.
>
> I don't know whether content-types are allowed ?to be upper-case but I
> seem to have several mails where they are. I am also not sure whether
> the correct fix is in the emacs code, or in the cli reply format
> (i.e. perhaps the reply format should lower-case the content-type).

A bit of Googling indicates that MIME types should be case insensitive
(i.e. TEXT/PLAIN should match text/plain). Given this, I think it
makes sense to change the emacs function regardless of whether it
makes sense for the CLI to output them in lower-case.
notmuch-match-content-type was put in notmuch-lib.el instead of in
notmuch-mua.el because it was thought that it might become useful
elsewhere later, so having it compare mime types correctly ensures
that future uses don't need to sanitize input to it.

Since you've already made the change for yourself, do you want to send a patch?

Cheers.


[PATCH 3/3] cli: refactor "notmuch restore" message tagging into a separate function

2012-03-24 Thread Jani Nikula
Refactor to make tagging code easier to reuse in the future. No
functional changes.

Signed-off-by: Jani Nikula 
---
 notmuch-client.h  |5 +++
 notmuch-restore.c |   73 ++-
 notmuch-tag.c |   75 +
 3 files changed, 83 insertions(+), 70 deletions(-)

diff --git a/notmuch-client.h b/notmuch-client.h
index fa04fa2..7e3deee 100644
--- a/notmuch-client.h
+++ b/notmuch-client.h
@@ -203,6 +203,11 @@ json_quote_chararray (const void *ctx, const char *str, 
const size_t len);
 char *
 json_quote_str (const void *ctx, const char *str);

+int
+tag_message (void *ctx, notmuch_database_t *notmuch, const char *message_id,
+char *file_tags, notmuch_bool_t remove_all,
+notmuch_bool_t synchronize_flags);
+
 /* notmuch-config.c */

 typedef struct _notmuch_config notmuch_config_t;
diff --git a/notmuch-restore.c b/notmuch-restore.c
index 87d9772..bd6b884 100644
--- a/notmuch-restore.c
+++ b/notmuch-restore.c
@@ -88,11 +88,7 @@ notmuch_restore_command (unused (void *ctx), int argc, char 
*argv[])

 while ((line_len = getline (&line, &line_size, input)) != -1) {
regmatch_t match[3];
-   char *message_id, *file_tags, *tag, *next;
-   notmuch_message_t *message = NULL;
-   notmuch_status_t status;
-   notmuch_tags_t *db_tags;
-   char *db_tags_str;
+   char *message_id, *file_tags;

chomp_newline (line);

@@ -109,72 +105,9 @@ notmuch_restore_command (unused (void *ctx), int argc, 
char *argv[])
file_tags = xstrndup (line + match[2].rm_so,
  match[2].rm_eo - match[2].rm_so);

-   status = notmuch_database_find_message (notmuch, message_id, &message);
-   if (status || message == NULL) {
-   fprintf (stderr, "Warning: Cannot apply tags to %smessage: %s\n",
-message ? "" : "missing ", message_id);
-   if (status)
-   fprintf (stderr, "%s\n",
-notmuch_status_to_string(status));
-   goto NEXT_LINE;
-   }
-
-   /* In order to detect missing messages, this check/optimization is
-* intentionally done *after* first finding the message.  */
-   if (accumulate && (file_tags == NULL || *file_tags == '\0'))
-   {
-   goto NEXT_LINE;
-   }
-
-   db_tags_str = NULL;
-   for (db_tags = notmuch_message_get_tags (message);
-notmuch_tags_valid (db_tags);
-notmuch_tags_move_to_next (db_tags))
-   {
-   const char *tag = notmuch_tags_get (db_tags);
-
-   if (db_tags_str)
-   db_tags_str = talloc_asprintf_append (db_tags_str, " %s", tag);
-   else
-   db_tags_str = talloc_strdup (message, tag);
-   }
-
-   if (((file_tags == NULL || *file_tags == '\0') &&
-(db_tags_str == NULL || *db_tags_str == '\0')) ||
-   (file_tags && db_tags_str && strcmp (file_tags, db_tags_str) == 0))
-   {
-   goto NEXT_LINE;
-   }
-
-   notmuch_message_freeze (message);
-
-   if (!accumulate)
-   notmuch_message_remove_all_tags (message);
-
-   next = file_tags;
-   while (next) {
-   tag = strsep (&next, " ");
-   if (*tag == '\0')
-   continue;
-   status = notmuch_message_add_tag (message, tag);
-   if (status) {
-   fprintf (stderr,
-"Error applying tag %s to message %s:\n",
-tag, message_id);
-   fprintf (stderr, "%s\n",
-notmuch_status_to_string (status));
-   }
-   }
-
-   notmuch_message_thaw (message);
-
-   if (synchronize_flags)
-   notmuch_message_tags_to_maildir_flags (message);
+   tag_message (ctx, notmuch, message_id, file_tags, !accumulate,
+synchronize_flags);

-  NEXT_LINE:
-   if (message)
-   notmuch_message_destroy (message);
-   message = NULL;
free (message_id);
free (file_tags);
 }
diff --git a/notmuch-tag.c b/notmuch-tag.c
index c4e3f9d..3aeb878 100644
--- a/notmuch-tag.c
+++ b/notmuch-tag.c
@@ -161,6 +161,81 @@ tag_query (void *ctx, notmuch_database_t *notmuch, const 
char *query_string,
 }

 int
+tag_message (void *ctx, notmuch_database_t *notmuch, const char *message_id,
+char *file_tags, notmuch_bool_t remove_all,
+notmuch_bool_t synchronize_flags)
+{
+notmuch_status_t status;
+notmuch_tags_t *db_tags;
+char *db_tags_str;
+notmuch_message_t *message = NULL;
+const char *tag;
+char *next;
+int ret = 0;
+
+status = notmuch_database_find_message (notmuch, message_id, &message);
+if (status || message == NULL) {
+   fprintf (stderr, "Warning: Cannot apply tags to %smessage: %s\n",
+message ? "" : "missing ", message_id);
+   if (status)
+   fprintf (st

[PATCH 2/3] cli: refactor "notmuch tag" query tagging into a separate function

2012-03-24 Thread Jani Nikula
Refactor to make tagging code easier to reuse in the future. No
functional changes.

Signed-off-by: Jani Nikula 
---
 notmuch-tag.c |  101 -
 1 files changed, 57 insertions(+), 44 deletions(-)

diff --git a/notmuch-tag.c b/notmuch-tag.c
index 98b2126..c4e3f9d 100644
--- a/notmuch-tag.c
+++ b/notmuch-tag.c
@@ -106,6 +106,60 @@ _optimize_tag_query (void *ctx, const char 
*orig_query_string,
 return query_string;
 }

+static int
+tag_query (void *ctx, notmuch_database_t *notmuch, const char *query_string,
+  tag_operation_t *tag_ops, notmuch_bool_t synchronize_flags)
+{
+notmuch_query_t *query;
+notmuch_messages_t *messages;
+notmuch_message_t *message;
+int i;
+
+/* Optimize the query so it excludes messages that already have
+ * the specified set of tags. */
+query_string = _optimize_tag_query (ctx, query_string, tag_ops);
+if (query_string == NULL) {
+   fprintf (stderr, "Out of memory.\n");
+   return 1;
+}
+
+query = notmuch_query_create (notmuch, query_string);
+if (query == NULL) {
+   fprintf (stderr, "Out of memory.\n");
+   return 1;
+}
+
+/* tagging is not interested in any special sort order */
+notmuch_query_set_sort (query, NOTMUCH_SORT_UNSORTED);
+
+for (messages = notmuch_query_search_messages (query);
+notmuch_messages_valid (messages) && !interrupted;
+notmuch_messages_move_to_next (messages))
+{
+   message = notmuch_messages_get (messages);
+
+   notmuch_message_freeze (message);
+
+   for (i = 0; tag_ops[i].tag; i++) {
+   if (tag_ops[i].remove)
+   notmuch_message_remove_tag (message, tag_ops[i].tag);
+   else
+   notmuch_message_add_tag (message, tag_ops[i].tag);
+   }
+
+   notmuch_message_thaw (message);
+
+   if (synchronize_flags)
+   notmuch_message_tags_to_maildir_flags (message);
+
+   notmuch_message_destroy (message);
+}
+
+notmuch_query_destroy (query);
+
+return interrupted;
+}
+
 int
 notmuch_tag_command (void *ctx, int argc, char *argv[])
 {
@@ -114,12 +168,10 @@ notmuch_tag_command (void *ctx, int argc, char *argv[])
 char *query_string;
 notmuch_config_t *config;
 notmuch_database_t *notmuch;
-notmuch_query_t *query;
-notmuch_messages_t *messages;
-notmuch_message_t *message;
 struct sigaction action;
 notmuch_bool_t synchronize_flags;
 int i;
+int ret;

 /* Setup our handler for SIGINT */
 memset (&action, 0, sizeof (struct sigaction));
@@ -167,14 +219,6 @@ notmuch_tag_command (void *ctx, int argc, char *argv[])
return 1;
 }

-/* Optimize the query so it excludes messages that already have
- * the specified set of tags. */
-query_string = _optimize_tag_query (ctx, query_string, tag_ops);
-if (query_string == NULL) {
-   fprintf (stderr, "Out of memory.\n");
-   return 1;
-}
-
 config = notmuch_config_open (ctx, NULL, NULL);
 if (config == NULL)
return 1;
@@ -186,40 +230,9 @@ notmuch_tag_command (void *ctx, int argc, char *argv[])

 synchronize_flags = notmuch_config_get_maildir_synchronize_flags (config);

-query = notmuch_query_create (notmuch, query_string);
-if (query == NULL) {
-   fprintf (stderr, "Out of memory.\n");
-   return 1;
-}
-
-/* tagging is not interested in any special sort order */
-notmuch_query_set_sort (query, NOTMUCH_SORT_UNSORTED);
+ret = tag_query (ctx, notmuch, query_string, tag_ops, synchronize_flags);

-for (messages = notmuch_query_search_messages (query);
-notmuch_messages_valid (messages) && !interrupted;
-notmuch_messages_move_to_next (messages))
-{
-   message = notmuch_messages_get (messages);
-
-   notmuch_message_freeze (message);
-
-   for (i = 0; tag_ops[i].tag; i++) {
-   if (tag_ops[i].remove)
-   notmuch_message_remove_tag (message, tag_ops[i].tag);
-   else
-   notmuch_message_add_tag (message, tag_ops[i].tag);
-   }
-
-   notmuch_message_thaw (message);
-
-   if (synchronize_flags)
-   notmuch_message_tags_to_maildir_flags (message);
-
-   notmuch_message_destroy (message);
-}
-
-notmuch_query_destroy (query);
 notmuch_database_close (notmuch);

-return interrupted;
+return ret;
 }
-- 
1.7.5.4



[PATCH 1/3] cli: refactor "notmuch tag" data structures for tagging operations

2012-03-24 Thread Jani Nikula
To simplify code, keep all tagging operations in a single array
instead of separate add and remove arrays. Apply tag changes in the
order specified on the command line, instead of first removing and
then adding the tags.

This results in a minor functional change: If a tag is both added and
removed, the last specified operation is now used. Previously the tag
was always added.

Signed-off-by: Jani Nikula 
---
 notmuch-tag.c |   80 +---
 1 files changed, 36 insertions(+), 44 deletions(-)

diff --git a/notmuch-tag.c b/notmuch-tag.c
index 36b9b09..98b2126 100644
--- a/notmuch-tag.c
+++ b/notmuch-tag.c
@@ -53,10 +53,14 @@ _escape_tag (char *buf, const char *tag)
 return buf;
 }

+typedef struct {
+const char *tag;
+notmuch_bool_t remove;
+} tag_operation_t;
+
 static char *
-_optimize_tag_query (void *ctx, const char *orig_query_string, char *argv[],
-int *add_tags, int add_tags_count,
-int *remove_tags, int remove_tags_count)
+_optimize_tag_query (void *ctx, const char *orig_query_string,
+const tag_operation_t *tag_ops)
 {
 /* This is subtler than it looks.  Xapian ignores the '-' operator
  * at the beginning both queries and parenthesized groups and,
@@ -74,12 +78,9 @@ _optimize_tag_query (void *ctx, const char 
*orig_query_string, char *argv[],
 /* Allocate a buffer for escaping tags.  This is large enough to
  * hold a fully escaped tag with every character doubled plus
  * enclosing quotes and a NUL. */
-for (i = 0; i < add_tags_count; i++)
-   if (strlen (argv[add_tags[i]] + 1) > max_tag_len)
-   max_tag_len = strlen (argv[add_tags[i]] + 1);
-for (i = 0; i < remove_tags_count; i++)
-   if (strlen (argv[remove_tags[i]] + 1) > max_tag_len)
-   max_tag_len = strlen (argv[remove_tags[i]] + 1);
+for (i = 0; tag_ops[i].tag; i++)
+   if (strlen (tag_ops[i].tag) > max_tag_len)
+   max_tag_len = strlen (tag_ops[i].tag);
 escaped = talloc_array(ctx, char, max_tag_len * 2 + 3);
 if (!escaped)
return NULL;
@@ -90,16 +91,11 @@ _optimize_tag_query (void *ctx, const char 
*orig_query_string, char *argv[],
 else
query_string = talloc_asprintf (ctx, "( %s ) and (", orig_query_string);

-for (i = 0; i < add_tags_count && query_string; i++) {
-   query_string = talloc_asprintf_append_buffer (
-   query_string, "%snot tag:%s", join,
-   _escape_tag (escaped, argv[add_tags[i]] + 1));
-   join = " or ";
-}
-for (i = 0; i < remove_tags_count && query_string; i++) {
+for (i = 0; tag_ops[i].tag && query_string; i++) {
query_string = talloc_asprintf_append_buffer (
-   query_string, "%stag:%s", join,
-   _escape_tag (escaped, argv[remove_tags[i]] + 1));
+   query_string, "%s%stag:%s", join,
+   tag_ops[i].remove ? "" : "not ",
+   _escape_tag (escaped, tag_ops[i].tag));
join = " or ";
 }

@@ -113,9 +109,8 @@ _optimize_tag_query (void *ctx, const char 
*orig_query_string, char *argv[],
 int
 notmuch_tag_command (void *ctx, int argc, char *argv[])
 {
-int *add_tags, *remove_tags;
-int add_tags_count = 0;
-int remove_tags_count = 0;
+tag_operation_t *tag_ops;
+int tag_ops_count = 0;
 char *query_string;
 notmuch_config_t *config;
 notmuch_database_t *notmuch;
@@ -133,35 +128,34 @@ notmuch_tag_command (void *ctx, int argc, char *argv[])
 action.sa_flags = SA_RESTART;
 sigaction (SIGINT, &action, NULL);

-add_tags = talloc_size (ctx, argc * sizeof (int));
-if (add_tags == NULL) {
-   fprintf (stderr, "Out of memory.\n");
-   return 1;
-}
+argc--; argv++; /* skip subcommand argument */

-remove_tags = talloc_size (ctx, argc * sizeof (int));
-if (remove_tags == NULL) {
+/* Array of tagging operations (add or remove), terminated with an
+ * empty element. */
+tag_ops = talloc_array (ctx, tag_operation_t, argc + 1);
+if (tag_ops == NULL) {
fprintf (stderr, "Out of memory.\n");
return 1;
 }

-argc--; argv++; /* skip subcommand argument */
-
 for (i = 0; i < argc; i++) {
if (strcmp (argv[i], "--") == 0) {
i++;
break;
}
-   if (argv[i][0] == '+') {
-   add_tags[add_tags_count++] = i;
-   } else if (argv[i][0] == '-') {
-   remove_tags[remove_tags_count++] = i;
+   if (argv[i][0] == '+' || argv[i][0] == '-') {
+   tag_ops[tag_ops_count++] = (tag_operation_t) {
+   .tag = argv[i] + 1,
+   .remove = argv[i][0] == '-',
+   };
} else {
break;
}
 }

-if (add_tags_count == 0 && remove_tags_count == 0) {
+tag_ops[tag_ops_count].tag = NULL;
+
+if (tag_ops_count == 0) {
fprintf (stderr, "Error: 'notmuch tag' requires at least one tag to add 
or remove.\n");
   

[PATCH 0/3] cli: notmuch tag/restore refactoring

2012-03-24 Thread Jani Nikula
Hi, here's some refactoring of notmuch tag/restore with mostly
non-functional changes, in preparation of later, more interesting
changes to dump/restore/tag.

BR,
Jani.


Jani Nikula (3):
  cli: refactor "notmuch tag" data structures for tagging operations
  cli: refactor "notmuch tag" query tagging into a separate function
  cli: refactor "notmuch restore" message tagging into a separate
function

 notmuch-client.h  |5 +
 notmuch-restore.c |   73 +---
 notmuch-tag.c |  242 +++--
 3 files changed, 169 insertions(+), 151 deletions(-)

-- 
1.7.5.4



[PATCH] emacs: content-type comparison should be case insensitive.

2012-03-24 Thread Mark Walters
The function notmuch-match-content-type was comparing content types
case sensitively. Fix it so it tests case insensitively.

This fixes a bug where emacs would not include any body when replying
to a message with content-type TEXT/PLAIN.
---
 emacs/notmuch-lib.el |5 +++--
 1 files changed, 3 insertions(+), 2 deletions(-)

diff --git a/emacs/notmuch-lib.el b/emacs/notmuch-lib.el
index c146748..a754de7 100644
--- a/emacs/notmuch-lib.el
+++ b/emacs/notmuch-lib.el
@@ -185,8 +185,9 @@ the user hasn't set this variable with the old or new 
value."
(st2 (notmuch-split-content-type t2)))
 (if (or (string= (cadr st1) "*")
(string= (cadr st2) "*"))
-   (string= (car st1) (car st2))
-  (string= t1 t2
+   ;; Comparison of content types should be case insensitive.
+   (string= (downcase (car st1)) (downcase (car st2)))
+  (string= (downcase t1) (downcase t2)
 
 (defvar notmuch-multipart/alternative-discouraged
   '(
-- 
1.7.9.1

___
notmuch mailing list
notmuch@notmuchmail.org
http://notmuchmail.org/mailman/listinfo/notmuch


[BUG/PATCH 1/2] test: Tests for reply from alternate addresses in emacs

2012-03-24 Thread Adam Wolfe Gordon
Since the recent reply changes were pushed, there has been a bug that
causes emacs to always reply from the primary address, even if the
JSON or default CLI reply output uses an alternate address.

This adds two tests to the emacs test library based on the two "Reply
form..." tests in the reply test library. One is currently marked
broken.
---
 test/emacs |   52 
 1 files changed, 52 insertions(+), 0 deletions(-)

diff --git a/test/emacs b/test/emacs
index 8a28705..fa5d706 100755
--- a/test/emacs
+++ b/test/emacs
@@ -274,6 +274,58 @@ Notmuch Test Suite  writes:
 EOF
 test_expect_equal_file OUTPUT EXPECTED
 
+test_begin_subtest "Reply from alternate address within emacs"
+test_subtest_known_broken
+add_message '[from]="Sender "' \
+[to]=test_suite_ot...@notmuchmail.org \
+[subject]=notmuch-reply-test \
+   '[date]="Tue, 05 Jan 2010 15:43:56 -"' \
+   '[body]="reply from alternate address"'
+
+test_emacs "(notmuch-search \"id:\\\"${gen_msg_id}\\\"\")
+   (notmuch-test-wait)
+   (notmuch-search-reply-to-thread)
+   (test-output)"
+sed -i -e 's/^In-Reply-To: <.*>$/In-Reply-To: /' OUTPUT
+cat  reply from alternate address
+EOF
+test_expect_equal_file OUTPUT EXPECTED
+
+test_begin_subtest "Reply from address in named group list within emacs"
+add_message '[from]="Sender "' \
+'[to]=group:test_su...@notmuchmail.org,some...@example.com\;' \
+ [cc]=test_suite_ot...@notmuchmail.org \
+ [subject]=notmuch-reply-test \
+'[date]="Tue, 05 Jan 2010 15:43:56 -"' \
+'[body]="Reply from address in named group list"'
+
+test_emacs "(notmuch-search \"id:\\\"${gen_msg_id}\\\"\")
+   (notmuch-test-wait)
+   (notmuch-search-reply-to-thread)
+   (test-output)"
+sed -i -e 's/^In-Reply-To: <.*>$/In-Reply-To: /' OUTPUT
+cat  Reply from address in named group list
+EOF
+test_expect_equal_file OUTPUT EXPECTED
+
 test_begin_subtest "Reply within emacs to a multipart/mixed message"
 test_emacs '(notmuch-show "id:20091118002059.067214ed@hikari")
(notmuch-show-reply)
-- 
1.7.5.4

___
notmuch mailing list
notmuch@notmuchmail.org
http://notmuchmail.org/mailman/listinfo/notmuch


[BUG/PATCH 0/2] Replying from other addresses in emacs

2012-03-24 Thread Adam Wolfe Gordon
Hi everyone,

This patch series adds a test for and fixes a bug that Dmitry pointed
out on IRC yesterday:

When replying to mail sent to an other_address, the CLI correctly sets
the From header to the address that received the message. This works
correctly in the default format and the JSON format.  However, emacs was
ignoring the provided From header and inserting the primary address, due
to an incompatibility between how we construct the header list and how
the `mail-header` function from the message library looks up header
values in the list.

People with other_addresses who are using the latest reply changes
should probably apply this patch right away. Without it, you might end
up replying to messages from the wrong address.

Adam Wolfe Gordon (2):
  test: Tests for reply from alternate addresses in emacs
  emacs: Fix replying from alternate addresses

 emacs/notmuch-mua.el |4 +-
 test/emacs   |   51 ++
 2 files changed, 53 insertions(+), 2 deletions(-)

-- 
1.7.5.4

___
notmuch mailing list
notmuch@notmuchmail.org
http://notmuchmail.org/mailman/listinfo/notmuch


[BUG/PATCH 2/2] emacs: Fix replying from alternate addresses

2012-03-24 Thread Adam Wolfe Gordon
The bug was that notmuch-mua-mail used `mail-header` to check whether
it was passed a "From" header. The implementation of `mail-header`
must try to compare symbols instead of strings when looking for
headers, as it was returning nil when a From header was present. This
is probably because the mail functions construct headers as alists
with symbols for the header names, while our code uses strings for the
header names.

Since we don't use `mail-header` anywhere else, and `message-mail` is
perfectly happy to accept string header names, the fix is just to use
`assoc` to look for the From header, so that the strings get compared
properly.
---
 emacs/notmuch-mua.el |4 ++--
 test/emacs   |1 -
 2 files changed, 2 insertions(+), 3 deletions(-)

diff --git a/emacs/notmuch-mua.el b/emacs/notmuch-mua.el
index 6aae3a0..9805d79 100644
--- a/emacs/notmuch-mua.el
+++ b/emacs/notmuch-mua.el
@@ -187,7 +187,7 @@ OTHER-ARGS are passed through to `message-mail'."
   (when (not (string= "" user-agent))
(push (cons "User-Agent" user-agent) other-headers
 
-  (unless (mail-header 'From other-headers)
+  (unless (assoc "From" other-headers)
 (push (cons "From" (concat
(notmuch-user-name) " <" (notmuch-user-primary-email) 
">")) other-headers))
 
@@ -250,7 +250,7 @@ the From: address first."
   (interactive "P")
   (let ((other-headers
 (when (or prompt-for-sender notmuch-always-prompt-for-sender)
-  (list (cons 'From (notmuch-mua-prompt-for-sender))
+  (list (cons "From" (notmuch-mua-prompt-for-sender))
 (notmuch-mua-mail nil nil other-headers)))
 
 (defun notmuch-mua-new-forward-message (&optional prompt-for-sender)
diff --git a/test/emacs b/test/emacs
index fa5d706..08db1ee 100755
--- a/test/emacs
+++ b/test/emacs
@@ -275,7 +275,6 @@ EOF
 test_expect_equal_file OUTPUT EXPECTED
 
 test_begin_subtest "Reply from alternate address within emacs"
-test_subtest_known_broken
 add_message '[from]="Sender "' \
 [to]=test_suite_ot...@notmuchmail.org \
 [subject]=notmuch-reply-test \
-- 
1.7.5.4

___
notmuch mailing list
notmuch@notmuchmail.org
http://notmuchmail.org/mailman/listinfo/notmuch


Re: Reply code with TEXT/PLAIN

2012-03-24 Thread Adam Wolfe Gordon
Hi Mark,

On Sat, Mar 24, 2012 at 15:49, Mark Walters  wrote:
> I am not certain if this is a bug or a request to work around broken
> mailers. I tried replying to a message today (with recent git) and got
> an empty message. I looked at the json output for reply and it contains
> the message but the content type is TEXT/PLAIN rather than
> text/plain.
>
> This seems to mean it doesn't match in notmuch-match-content-type
> (notmuch-lib.el) called from notmuch-mua-get-quotable-parts (in
> notmuch-mua.el); and so does not get included.
>
> Making the match case-insensitive `fixed' the problem.
>
> I don't know whether content-types are allowed  to be upper-case but I
> seem to have several mails where they are. I am also not sure whether
> the correct fix is in the emacs code, or in the cli reply format
> (i.e. perhaps the reply format should lower-case the content-type).

A bit of Googling indicates that MIME types should be case insensitive
(i.e. TEXT/PLAIN should match text/plain). Given this, I think it
makes sense to change the emacs function regardless of whether it
makes sense for the CLI to output them in lower-case.
notmuch-match-content-type was put in notmuch-lib.el instead of in
notmuch-mua.el because it was thought that it might become useful
elsewhere later, so having it compare mime types correctly ensures
that future uses don't need to sanitize input to it.

Since you've already made the change for yourself, do you want to send a patch?

Cheers.
___
notmuch mailing list
notmuch@notmuchmail.org
http://notmuchmail.org/mailman/listinfo/notmuch


[PATCH 1/3] cli: refactor "notmuch tag" data structures for tagging operations

2012-03-24 Thread David Bremner
On Sat, 24 Mar 2012 18:14:35 +0200, Jani Nikula  wrote:
> + if (argv[i][0] == '+' || argv[i][0] == '-') {
> + tag_ops[tag_ops_count++] = (tag_operation_t) {
> + .tag = argv[i] + 1,
> + .remove = argv[i][0] == '-',
> + };

I'm not sure if this is a worthwhile use of a C99. Wouldn't it be
simpler to just use two assignments? and maybe increment the index
after? Still 3 lines of code.

Other than that, this patch looked ok to me.  I think it probably
deserves a NEWS patch that the ordering behaviour changed. I do think
the new order is more sensible, and the old one was never documented.


d


Reply code with TEXT/PLAIN

2012-03-24 Thread Mark Walters


I am not certain if this is a bug or a request to work around broken
mailers. I tried replying to a message today (with recent git) and got
an empty message. I looked at the json output for reply and it contains
the message but the content type is TEXT/PLAIN rather than
text/plain.

This seems to mean it doesn't match in notmuch-match-content-type
(notmuch-lib.el) called from notmuch-mua-get-quotable-parts (in
notmuch-mua.el); and so does not get included.

Making the match case-insensitive `fixed' the problem.

I don't know whether content-types are allowed  to be upper-case but I
seem to have several mails where they are. I am also not sure whether
the correct fix is in the emacs code, or in the cli reply format
(i.e. perhaps the reply format should lower-case the content-type).

Best wishes

Mark

PS Sorry Adam for the duplicate: I sent from a wrong address (my fault
this time)!
___
notmuch mailing list
notmuch@notmuchmail.org
http://notmuchmail.org/mailman/listinfo/notmuch


Questions from a user new to notmuch

2012-03-24 Thread Jani Nikula
On Sat, 24 Mar 2012 10:04:48 +, Patrick Totzke  wrote:
> Quoting Austin Clements (2012-03-22 22:39:07)
> >> 2. I received a message that was addressed to a distribution group and
> >>tried to reply.  Because the TO: address is not my address, notmuch
> >>fails to guess the proper FROM: address to set.  Is there a way to
> >>handle this use case?  This is with the emacs notmuch client.
> >
> >Not in general, since notmuch doesn't know where the mail was
> >addressed to.  I believe some people have solved problems like this
> >using Emacs hooks, but I don't know the details.
> 
> Does the notmuch CLI not use the 'Delivered-To' header for this?

To decide which from address to use for replying, the CLI (and therefore
emacs ui) looks for primary and other email addresses in the
reply-to/from, to, cc, bcc, envelope-to, x-original-to, and finally
received headers, in this order, before falling back to just using
primary email.

If someone is interested (read: I'm too busy right now), and it's
desirable, it would be a one-liner to add delivered-to in the to_headers
array in guess_from_received_header() in notmuch-reply.c. Plus a few
dozen lines of test code. ;)

BR,
Jani.


Questions from a user new to notmuch

2012-03-24 Thread Jani Nikula
On Thu, 22 Mar 2012 18:39:07 -0400, Austin Clements  wrote:
> Quoth Kyle Sexton on Mar 21 at 10:21 pm:
> > 1. Using offlineimap to store mail into a Maildir, is it safe to move an
> >already indexed message to a different folder from some other client?
> 
> Yes, mostly.  You'll have to run notmuch new for notmuch to detect the
> move, but it will detect it as a move.  I say "mostly" because until
> you run notmuch new, notmuch won't find the message file, so it won't
> be able to display the message and won't be able to synchronize any
> tag changes to its maildir flags.

If maildir.synchronize_flags is enabled (it is by default) you should
take extra care to have the mail store and notmuch database in sync (by
running "notmuch new") before changing tags that are synced with maildir
flags. Otherwise, the following scenario is possible:

1) Rename file with id:message-id in another client. Note that maildir
   flag changes are also renames.

2) "notmuch tag -unread id:message-id" will remove unread tag from the
   message, but won't find the message file, and is thus unable to sync
   the tag change to the S maildir flag. The syncing just silently
   fails.

3) The next "notmuch new" syncs maildir flags to tags, adding unread tag
   back to id:message-id, losing your tag change.

This could be considered a bug in notmuch, but not an easy one to fix
race free. See http://notmuchmail.org/special-tags/ and your
.notmuch-config for details about maildir flag synchronization.


BR,
Jani.


[PATCH 0/3] Rewrite default reply format

2012-03-24 Thread Tomi Ollila
Austin Clements  writes:

> The default reply format is the last bastion of the old message
> formatter style.  This series converts it to the new self-recursive
> style.  After this, there will be one last series to rip out the
> compatibility code and do final cleanup.

Works fine, patches look good... just 2 "spacing" questions:

in id:"1332473647-9133-2-git-send-email-amdragon at mit.edu"

+ typedef enum {
+ NOTMUCH_SHOW_TEXT_PART_REPLY = 1<<0,
+ } notmuch_show_text_part_flags;

Should this be like: NOTMUCH_SHOW_TEXT_PART_REPLY = (1 << 0),

and this

+ * If flags&NOTMUCH_SHOW_TEXT_PART_REPLY, this prepends "> " to each
+ * output line.
+ *

like:

+ * If flags & NOTMUCH_SHOW_TEXT_PART_REPLY, this prepends "> " to each


Tomi


[RFC] Split notmuch_database_close into two functions

2012-03-24 Thread Tomi Ollila
Justus Winter <4winter at informatik.uni-hamburg.de> writes:

> I propose to split the function notmuch_database_close into
> notmuch_database_close and notmuch_database_destroy so that long
> running processes like alot can close the database while still using
> data obtained from queries to that database.
>
> I've updated the tools, the go, ruby and python bindings to use
> notmuch_database_destroy instead of notmuch_database_close to destroy
> database objects.

This looks like a good idea. grep _destroy *.c outputs plenty of
other matches so this is not a new term here. I wonder what (backward)
compability issues there are, though.

In message id:"1332291311-28954-2-git-send-email-4winter at 
informatik.uni-hamburg.de"
there was typo in comment: notmuch_database_destroyed ?

>
> Cheers,
> Justus

Tomi


[PATCH] vim: fix regex after "notmuch show" output change

2012-03-24 Thread Tomi Ollila
Jakob  writes:

> The new field "excluded" was added to the output and made this regex fail.
> ---

Is this regexp part below good ?

> +... match:\([0-9]*\) excluded:\([[0-9]*\) filename:\(.*\)$', ...

( --> excluded:\([[0-9]*\) <-- )

Otherwise lookg good (I think).

Tomi

>  vim/plugin/notmuch.vim |5 +++--
>  1 files changed, 3 insertions(+), 2 deletions(-)
>
> diff --git a/vim/plugin/notmuch.vim b/vim/plugin/notmuch.vim
> index 21985c7..92e1b50 100644
> --- a/vim/plugin/notmuch.vim
> +++ b/vim/plugin/notmuch.vim
> @@ -48,7 +48,7 @@ let s:notmuch_defaults = {
>  \ 'g:notmuch_show_part_end_regexp':  'part}'
>,
>  \ 'g:notmuch_show_marker_regexp':'
> \\(message\\|header\\|body\\|attachment\\|part\\)[{}].*$',
>  \
> -\ 'g:notmuch_show_message_parse_regexp': '\(id:[^ ]*\) 
> depth:\([0-9]*\) match:\([0-9]*\) filename:\(.*\)$',
> +\ 'g:notmuch_show_message_parse_regexp': '\(id:[^ ]*\) 
> depth:\([0-9]*\) match:\([0-9]*\) excluded:\([[0-9]*\) filename:\(.*\)$',
>  \ 'g:notmuch_show_tags_regexp':  '(\([^)]*\))$'  
>  ,
>  \
>  \ 'g:notmuch_show_signature_regexp': '^\(-- \?\|_\+\)$'  
>  ,
> @@ -870,7 +870,8 @@ function! s:NM_cmd_show_parse(inlines)
>  let msg['id'] = m[1]
>  let msg['depth'] = m[2]
>  let msg['match'] = m[3]
> -let msg['filename'] = m[4]
> +let msg['excluded'] = m[4]
> +let msg['filename'] = m[5]
>  endif
>  
>  let in_message = 1
> -- 
> 1.7.9.1
>
> ___
> notmuch mailing list
> notmuch at notmuchmail.org
> http://notmuchmail.org/mailman/listinfo/notmuch


Questions from a user new to notmuch

2012-03-24 Thread Patrick Totzke
Quoting Austin Clements (2012-03-22 22:39:07)
>> 2. I received a message that was addressed to a distribution group and
>>tried to reply.  Because the TO: address is not my address, notmuch
>>fails to guess the proper FROM: address to set.  Is there a way to
>>handle this use case?  This is with the emacs notmuch client.
>
>Not in general, since notmuch doesn't know where the mail was
>addressed to.  I believe some people have solved problems like this
>using Emacs hooks, but I don't know the details.

Does the notmuch CLI not use the 'Delivered-To' header for this?
/p


[PATCH 3/3] cli: refactor "notmuch restore" message tagging into a separate function

2012-03-24 Thread Jani Nikula
Refactor to make tagging code easier to reuse in the future. No
functional changes.

Signed-off-by: Jani Nikula 
---
 notmuch-client.h  |5 +++
 notmuch-restore.c |   73 ++-
 notmuch-tag.c |   75 +
 3 files changed, 83 insertions(+), 70 deletions(-)

diff --git a/notmuch-client.h b/notmuch-client.h
index fa04fa2..7e3deee 100644
--- a/notmuch-client.h
+++ b/notmuch-client.h
@@ -203,6 +203,11 @@ json_quote_chararray (const void *ctx, const char *str, 
const size_t len);
 char *
 json_quote_str (const void *ctx, const char *str);
 
+int
+tag_message (void *ctx, notmuch_database_t *notmuch, const char *message_id,
+char *file_tags, notmuch_bool_t remove_all,
+notmuch_bool_t synchronize_flags);
+
 /* notmuch-config.c */
 
 typedef struct _notmuch_config notmuch_config_t;
diff --git a/notmuch-restore.c b/notmuch-restore.c
index 87d9772..bd6b884 100644
--- a/notmuch-restore.c
+++ b/notmuch-restore.c
@@ -88,11 +88,7 @@ notmuch_restore_command (unused (void *ctx), int argc, char 
*argv[])
 
 while ((line_len = getline (&line, &line_size, input)) != -1) {
regmatch_t match[3];
-   char *message_id, *file_tags, *tag, *next;
-   notmuch_message_t *message = NULL;
-   notmuch_status_t status;
-   notmuch_tags_t *db_tags;
-   char *db_tags_str;
+   char *message_id, *file_tags;
 
chomp_newline (line);
 
@@ -109,72 +105,9 @@ notmuch_restore_command (unused (void *ctx), int argc, 
char *argv[])
file_tags = xstrndup (line + match[2].rm_so,
  match[2].rm_eo - match[2].rm_so);
 
-   status = notmuch_database_find_message (notmuch, message_id, &message);
-   if (status || message == NULL) {
-   fprintf (stderr, "Warning: Cannot apply tags to %smessage: %s\n",
-message ? "" : "missing ", message_id);
-   if (status)
-   fprintf (stderr, "%s\n",
-notmuch_status_to_string(status));
-   goto NEXT_LINE;
-   }
-
-   /* In order to detect missing messages, this check/optimization is
-* intentionally done *after* first finding the message.  */
-   if (accumulate && (file_tags == NULL || *file_tags == '\0'))
-   {
-   goto NEXT_LINE;
-   }
-
-   db_tags_str = NULL;
-   for (db_tags = notmuch_message_get_tags (message);
-notmuch_tags_valid (db_tags);
-notmuch_tags_move_to_next (db_tags))
-   {
-   const char *tag = notmuch_tags_get (db_tags);
-
-   if (db_tags_str)
-   db_tags_str = talloc_asprintf_append (db_tags_str, " %s", tag);
-   else
-   db_tags_str = talloc_strdup (message, tag);
-   }
-
-   if (((file_tags == NULL || *file_tags == '\0') &&
-(db_tags_str == NULL || *db_tags_str == '\0')) ||
-   (file_tags && db_tags_str && strcmp (file_tags, db_tags_str) == 0))
-   {
-   goto NEXT_LINE;
-   }
-
-   notmuch_message_freeze (message);
-
-   if (!accumulate)
-   notmuch_message_remove_all_tags (message);
-
-   next = file_tags;
-   while (next) {
-   tag = strsep (&next, " ");
-   if (*tag == '\0')
-   continue;
-   status = notmuch_message_add_tag (message, tag);
-   if (status) {
-   fprintf (stderr,
-"Error applying tag %s to message %s:\n",
-tag, message_id);
-   fprintf (stderr, "%s\n",
-notmuch_status_to_string (status));
-   }
-   }
-
-   notmuch_message_thaw (message);
-
-   if (synchronize_flags)
-   notmuch_message_tags_to_maildir_flags (message);
+   tag_message (ctx, notmuch, message_id, file_tags, !accumulate,
+synchronize_flags);
 
-  NEXT_LINE:
-   if (message)
-   notmuch_message_destroy (message);
-   message = NULL;
free (message_id);
free (file_tags);
 }
diff --git a/notmuch-tag.c b/notmuch-tag.c
index c4e3f9d..3aeb878 100644
--- a/notmuch-tag.c
+++ b/notmuch-tag.c
@@ -161,6 +161,81 @@ tag_query (void *ctx, notmuch_database_t *notmuch, const 
char *query_string,
 }
 
 int
+tag_message (void *ctx, notmuch_database_t *notmuch, const char *message_id,
+char *file_tags, notmuch_bool_t remove_all,
+notmuch_bool_t synchronize_flags)
+{
+notmuch_status_t status;
+notmuch_tags_t *db_tags;
+char *db_tags_str;
+notmuch_message_t *message = NULL;
+const char *tag;
+char *next;
+int ret = 0;
+
+status = notmuch_database_find_message (notmuch, message_id, &message);
+if (status || message == NULL) {
+   fprintf (stderr, "Warning: Cannot apply tags to %smessage: %s\n",
+message ? "" : "missing ", message_id);
+   if (status)
+   fpr

[PATCH 2/3] cli: refactor "notmuch tag" query tagging into a separate function

2012-03-24 Thread Jani Nikula
Refactor to make tagging code easier to reuse in the future. No
functional changes.

Signed-off-by: Jani Nikula 
---
 notmuch-tag.c |  101 -
 1 files changed, 57 insertions(+), 44 deletions(-)

diff --git a/notmuch-tag.c b/notmuch-tag.c
index 98b2126..c4e3f9d 100644
--- a/notmuch-tag.c
+++ b/notmuch-tag.c
@@ -106,6 +106,60 @@ _optimize_tag_query (void *ctx, const char 
*orig_query_string,
 return query_string;
 }
 
+static int
+tag_query (void *ctx, notmuch_database_t *notmuch, const char *query_string,
+  tag_operation_t *tag_ops, notmuch_bool_t synchronize_flags)
+{
+notmuch_query_t *query;
+notmuch_messages_t *messages;
+notmuch_message_t *message;
+int i;
+
+/* Optimize the query so it excludes messages that already have
+ * the specified set of tags. */
+query_string = _optimize_tag_query (ctx, query_string, tag_ops);
+if (query_string == NULL) {
+   fprintf (stderr, "Out of memory.\n");
+   return 1;
+}
+
+query = notmuch_query_create (notmuch, query_string);
+if (query == NULL) {
+   fprintf (stderr, "Out of memory.\n");
+   return 1;
+}
+
+/* tagging is not interested in any special sort order */
+notmuch_query_set_sort (query, NOTMUCH_SORT_UNSORTED);
+
+for (messages = notmuch_query_search_messages (query);
+notmuch_messages_valid (messages) && !interrupted;
+notmuch_messages_move_to_next (messages))
+{
+   message = notmuch_messages_get (messages);
+
+   notmuch_message_freeze (message);
+
+   for (i = 0; tag_ops[i].tag; i++) {
+   if (tag_ops[i].remove)
+   notmuch_message_remove_tag (message, tag_ops[i].tag);
+   else
+   notmuch_message_add_tag (message, tag_ops[i].tag);
+   }
+
+   notmuch_message_thaw (message);
+
+   if (synchronize_flags)
+   notmuch_message_tags_to_maildir_flags (message);
+
+   notmuch_message_destroy (message);
+}
+
+notmuch_query_destroy (query);
+
+return interrupted;
+}
+
 int
 notmuch_tag_command (void *ctx, int argc, char *argv[])
 {
@@ -114,12 +168,10 @@ notmuch_tag_command (void *ctx, int argc, char *argv[])
 char *query_string;
 notmuch_config_t *config;
 notmuch_database_t *notmuch;
-notmuch_query_t *query;
-notmuch_messages_t *messages;
-notmuch_message_t *message;
 struct sigaction action;
 notmuch_bool_t synchronize_flags;
 int i;
+int ret;
 
 /* Setup our handler for SIGINT */
 memset (&action, 0, sizeof (struct sigaction));
@@ -167,14 +219,6 @@ notmuch_tag_command (void *ctx, int argc, char *argv[])
return 1;
 }
 
-/* Optimize the query so it excludes messages that already have
- * the specified set of tags. */
-query_string = _optimize_tag_query (ctx, query_string, tag_ops);
-if (query_string == NULL) {
-   fprintf (stderr, "Out of memory.\n");
-   return 1;
-}
-
 config = notmuch_config_open (ctx, NULL, NULL);
 if (config == NULL)
return 1;
@@ -186,40 +230,9 @@ notmuch_tag_command (void *ctx, int argc, char *argv[])
 
 synchronize_flags = notmuch_config_get_maildir_synchronize_flags (config);
 
-query = notmuch_query_create (notmuch, query_string);
-if (query == NULL) {
-   fprintf (stderr, "Out of memory.\n");
-   return 1;
-}
-
-/* tagging is not interested in any special sort order */
-notmuch_query_set_sort (query, NOTMUCH_SORT_UNSORTED);
+ret = tag_query (ctx, notmuch, query_string, tag_ops, synchronize_flags);
 
-for (messages = notmuch_query_search_messages (query);
-notmuch_messages_valid (messages) && !interrupted;
-notmuch_messages_move_to_next (messages))
-{
-   message = notmuch_messages_get (messages);
-
-   notmuch_message_freeze (message);
-
-   for (i = 0; tag_ops[i].tag; i++) {
-   if (tag_ops[i].remove)
-   notmuch_message_remove_tag (message, tag_ops[i].tag);
-   else
-   notmuch_message_add_tag (message, tag_ops[i].tag);
-   }
-
-   notmuch_message_thaw (message);
-
-   if (synchronize_flags)
-   notmuch_message_tags_to_maildir_flags (message);
-
-   notmuch_message_destroy (message);
-}
-
-notmuch_query_destroy (query);
 notmuch_database_close (notmuch);
 
-return interrupted;
+return ret;
 }
-- 
1.7.5.4

___
notmuch mailing list
notmuch@notmuchmail.org
http://notmuchmail.org/mailman/listinfo/notmuch


[PATCH 1/3] cli: refactor "notmuch tag" data structures for tagging operations

2012-03-24 Thread Jani Nikula
To simplify code, keep all tagging operations in a single array
instead of separate add and remove arrays. Apply tag changes in the
order specified on the command line, instead of first removing and
then adding the tags.

This results in a minor functional change: If a tag is both added and
removed, the last specified operation is now used. Previously the tag
was always added.

Signed-off-by: Jani Nikula 
---
 notmuch-tag.c |   80 +---
 1 files changed, 36 insertions(+), 44 deletions(-)

diff --git a/notmuch-tag.c b/notmuch-tag.c
index 36b9b09..98b2126 100644
--- a/notmuch-tag.c
+++ b/notmuch-tag.c
@@ -53,10 +53,14 @@ _escape_tag (char *buf, const char *tag)
 return buf;
 }
 
+typedef struct {
+const char *tag;
+notmuch_bool_t remove;
+} tag_operation_t;
+
 static char *
-_optimize_tag_query (void *ctx, const char *orig_query_string, char *argv[],
-int *add_tags, int add_tags_count,
-int *remove_tags, int remove_tags_count)
+_optimize_tag_query (void *ctx, const char *orig_query_string,
+const tag_operation_t *tag_ops)
 {
 /* This is subtler than it looks.  Xapian ignores the '-' operator
  * at the beginning both queries and parenthesized groups and,
@@ -74,12 +78,9 @@ _optimize_tag_query (void *ctx, const char 
*orig_query_string, char *argv[],
 /* Allocate a buffer for escaping tags.  This is large enough to
  * hold a fully escaped tag with every character doubled plus
  * enclosing quotes and a NUL. */
-for (i = 0; i < add_tags_count; i++)
-   if (strlen (argv[add_tags[i]] + 1) > max_tag_len)
-   max_tag_len = strlen (argv[add_tags[i]] + 1);
-for (i = 0; i < remove_tags_count; i++)
-   if (strlen (argv[remove_tags[i]] + 1) > max_tag_len)
-   max_tag_len = strlen (argv[remove_tags[i]] + 1);
+for (i = 0; tag_ops[i].tag; i++)
+   if (strlen (tag_ops[i].tag) > max_tag_len)
+   max_tag_len = strlen (tag_ops[i].tag);
 escaped = talloc_array(ctx, char, max_tag_len * 2 + 3);
 if (!escaped)
return NULL;
@@ -90,16 +91,11 @@ _optimize_tag_query (void *ctx, const char 
*orig_query_string, char *argv[],
 else
query_string = talloc_asprintf (ctx, "( %s ) and (", orig_query_string);
 
-for (i = 0; i < add_tags_count && query_string; i++) {
-   query_string = talloc_asprintf_append_buffer (
-   query_string, "%snot tag:%s", join,
-   _escape_tag (escaped, argv[add_tags[i]] + 1));
-   join = " or ";
-}
-for (i = 0; i < remove_tags_count && query_string; i++) {
+for (i = 0; tag_ops[i].tag && query_string; i++) {
query_string = talloc_asprintf_append_buffer (
-   query_string, "%stag:%s", join,
-   _escape_tag (escaped, argv[remove_tags[i]] + 1));
+   query_string, "%s%stag:%s", join,
+   tag_ops[i].remove ? "" : "not ",
+   _escape_tag (escaped, tag_ops[i].tag));
join = " or ";
 }
 
@@ -113,9 +109,8 @@ _optimize_tag_query (void *ctx, const char 
*orig_query_string, char *argv[],
 int
 notmuch_tag_command (void *ctx, int argc, char *argv[])
 {
-int *add_tags, *remove_tags;
-int add_tags_count = 0;
-int remove_tags_count = 0;
+tag_operation_t *tag_ops;
+int tag_ops_count = 0;
 char *query_string;
 notmuch_config_t *config;
 notmuch_database_t *notmuch;
@@ -133,35 +128,34 @@ notmuch_tag_command (void *ctx, int argc, char *argv[])
 action.sa_flags = SA_RESTART;
 sigaction (SIGINT, &action, NULL);
 
-add_tags = talloc_size (ctx, argc * sizeof (int));
-if (add_tags == NULL) {
-   fprintf (stderr, "Out of memory.\n");
-   return 1;
-}
+argc--; argv++; /* skip subcommand argument */
 
-remove_tags = talloc_size (ctx, argc * sizeof (int));
-if (remove_tags == NULL) {
+/* Array of tagging operations (add or remove), terminated with an
+ * empty element. */
+tag_ops = talloc_array (ctx, tag_operation_t, argc + 1);
+if (tag_ops == NULL) {
fprintf (stderr, "Out of memory.\n");
return 1;
 }
 
-argc--; argv++; /* skip subcommand argument */
-
 for (i = 0; i < argc; i++) {
if (strcmp (argv[i], "--") == 0) {
i++;
break;
}
-   if (argv[i][0] == '+') {
-   add_tags[add_tags_count++] = i;
-   } else if (argv[i][0] == '-') {
-   remove_tags[remove_tags_count++] = i;
+   if (argv[i][0] == '+' || argv[i][0] == '-') {
+   tag_ops[tag_ops_count++] = (tag_operation_t) {
+   .tag = argv[i] + 1,
+   .remove = argv[i][0] == '-',
+   };
} else {
break;
}
 }
 
-if (add_tags_count == 0 && remove_tags_count == 0) {
+tag_ops[tag_ops_count].tag = NULL;
+
+if (tag_ops_count == 0) {
fprintf (stderr, "Error: 'notmuch tag' requires at least one tag to add 
or remove.\n");

[PATCH 0/3] cli: notmuch tag/restore refactoring

2012-03-24 Thread Jani Nikula
Hi, here's some refactoring of notmuch tag/restore with mostly
non-functional changes, in preparation of later, more interesting
changes to dump/restore/tag.

BR,
Jani.


Jani Nikula (3):
  cli: refactor "notmuch tag" data structures for tagging operations
  cli: refactor "notmuch tag" query tagging into a separate function
  cli: refactor "notmuch restore" message tagging into a separate
function

 notmuch-client.h  |5 +
 notmuch-restore.c |   73 +---
 notmuch-tag.c |  242 +++--
 3 files changed, 169 insertions(+), 151 deletions(-)

-- 
1.7.5.4

___
notmuch mailing list
notmuch@notmuchmail.org
http://notmuchmail.org/mailman/listinfo/notmuch


Re: Questions from a user new to notmuch

2012-03-24 Thread Jani Nikula
On Sat, 24 Mar 2012 10:04:48 +, Patrick Totzke 
 wrote:
> Quoting Austin Clements (2012-03-22 22:39:07)
> >> 2. I received a message that was addressed to a distribution group and
> >>tried to reply.  Because the TO: address is not my address, notmuch
> >>fails to guess the proper FROM: address to set.  Is there a way to
> >>handle this use case?  This is with the emacs notmuch client.
> >
> >Not in general, since notmuch doesn't know where the mail was
> >addressed to.  I believe some people have solved problems like this
> >using Emacs hooks, but I don't know the details.
> 
> Does the notmuch CLI not use the 'Delivered-To' header for this?

To decide which from address to use for replying, the CLI (and therefore
emacs ui) looks for primary and other email addresses in the
reply-to/from, to, cc, bcc, envelope-to, x-original-to, and finally
received headers, in this order, before falling back to just using
primary email.

If someone is interested (read: I'm too busy right now), and it's
desirable, it would be a one-liner to add delivered-to in the to_headers
array in guess_from_received_header() in notmuch-reply.c. Plus a few
dozen lines of test code. ;)

BR,
Jani.
___
notmuch mailing list
notmuch@notmuchmail.org
http://notmuchmail.org/mailman/listinfo/notmuch


Re: Questions from a user new to notmuch

2012-03-24 Thread Jani Nikula
On Thu, 22 Mar 2012 18:39:07 -0400, Austin Clements  wrote:
> Quoth Kyle Sexton on Mar 21 at 10:21 pm:
> > 1. Using offlineimap to store mail into a Maildir, is it safe to move an
> >already indexed message to a different folder from some other client?
> 
> Yes, mostly.  You'll have to run notmuch new for notmuch to detect the
> move, but it will detect it as a move.  I say "mostly" because until
> you run notmuch new, notmuch won't find the message file, so it won't
> be able to display the message and won't be able to synchronize any
> tag changes to its maildir flags.

If maildir.synchronize_flags is enabled (it is by default) you should
take extra care to have the mail store and notmuch database in sync (by
running "notmuch new") before changing tags that are synced with maildir
flags. Otherwise, the following scenario is possible:

1) Rename file with id:message-id in another client. Note that maildir
   flag changes are also renames.

2) "notmuch tag -unread id:message-id" will remove unread tag from the
   message, but won't find the message file, and is thus unable to sync
   the tag change to the S maildir flag. The syncing just silently
   fails.

3) The next "notmuch new" syncs maildir flags to tags, adding unread tag
   back to id:message-id, losing your tag change.

This could be considered a bug in notmuch, but not an easy one to fix
race free. See http://notmuchmail.org/special-tags/ and your
.notmuch-config for details about maildir flag synchronization.


BR,
Jani.
___
notmuch mailing list
notmuch@notmuchmail.org
http://notmuchmail.org/mailman/listinfo/notmuch


Re: [PATCH 0/3] Rewrite default reply format

2012-03-24 Thread Tomi Ollila
Austin Clements  writes:

> The default reply format is the last bastion of the old message
> formatter style.  This series converts it to the new self-recursive
> style.  After this, there will be one last series to rip out the
> compatibility code and do final cleanup.

Works fine, patches look good... just 2 "spacing" questions:

in id:"1332473647-9133-2-git-send-email-amdra...@mit.edu"

+ typedef enum {
+ NOTMUCH_SHOW_TEXT_PART_REPLY = 1<<0,
+ } notmuch_show_text_part_flags;

Should this be like: NOTMUCH_SHOW_TEXT_PART_REPLY = (1 << 0),

and this

+ * If flags&NOTMUCH_SHOW_TEXT_PART_REPLY, this prepends "> " to each
+ * output line.
+ *

like:

+ * If flags & NOTMUCH_SHOW_TEXT_PART_REPLY, this prepends "> " to each


Tomi
___
notmuch mailing list
notmuch@notmuchmail.org
http://notmuchmail.org/mailman/listinfo/notmuch


Re: Questions from a user new to notmuch

2012-03-24 Thread Patrick Totzke
Quoting Austin Clements (2012-03-22 22:39:07)
>> 2. I received a message that was addressed to a distribution group and
>>tried to reply.  Because the TO: address is not my address, notmuch
>>fails to guess the proper FROM: address to set.  Is there a way to
>>handle this use case?  This is with the emacs notmuch client.
>
>Not in general, since notmuch doesn't know where the mail was
>addressed to.  I believe some people have solved problems like this
>using Emacs hooks, but I don't know the details.

Does the notmuch CLI not use the 'Delivered-To' header for this?
/p
___
notmuch mailing list
notmuch@notmuchmail.org
http://notmuchmail.org/mailman/listinfo/notmuch


Re: [RFC] Split notmuch_database_close into two functions

2012-03-24 Thread Tomi Ollila
Justus Winter <4win...@informatik.uni-hamburg.de> writes:

> I propose to split the function notmuch_database_close into
> notmuch_database_close and notmuch_database_destroy so that long
> running processes like alot can close the database while still using
> data obtained from queries to that database.
>
> I've updated the tools, the go, ruby and python bindings to use
> notmuch_database_destroy instead of notmuch_database_close to destroy
> database objects.

This looks like a good idea. grep _destroy *.c outputs plenty of
other matches so this is not a new term here. I wonder what (backward)
compability issues there are, though.

In message 
id:"1332291311-28954-2-git-send-email-4win...@informatik.uni-hamburg.de"
there was typo in comment: notmuch_database_destroyed ?

>
> Cheers,
> Justus

Tomi
___
notmuch mailing list
notmuch@notmuchmail.org
http://notmuchmail.org/mailman/listinfo/notmuch


Re: [PATCH] vim: fix regex after "notmuch show" output change

2012-03-24 Thread Tomi Ollila
Jakob  writes:

> The new field "excluded" was added to the output and made this regex fail.
> ---

Is this regexp part below good ?

> +... match:\([0-9]*\) excluded:\([[0-9]*\) filename:\(.*\)$', ...

( --> excluded:\([[0-9]*\) <-- )

Otherwise lookg good (I think).

Tomi

>  vim/plugin/notmuch.vim |5 +++--
>  1 files changed, 3 insertions(+), 2 deletions(-)
>
> diff --git a/vim/plugin/notmuch.vim b/vim/plugin/notmuch.vim
> index 21985c7..92e1b50 100644
> --- a/vim/plugin/notmuch.vim
> +++ b/vim/plugin/notmuch.vim
> @@ -48,7 +48,7 @@ let s:notmuch_defaults = {
>  \ 'g:notmuch_show_part_end_regexp':  'part}'
>,
>  \ 'g:notmuch_show_marker_regexp':'
> \\(message\\|header\\|body\\|attachment\\|part\\)[{}].*$',
>  \
> -\ 'g:notmuch_show_message_parse_regexp': '\(id:[^ ]*\) 
> depth:\([0-9]*\) match:\([0-9]*\) filename:\(.*\)$',
> +\ 'g:notmuch_show_message_parse_regexp': '\(id:[^ ]*\) 
> depth:\([0-9]*\) match:\([0-9]*\) excluded:\([[0-9]*\) filename:\(.*\)$',
>  \ 'g:notmuch_show_tags_regexp':  '(\([^)]*\))$'  
>  ,
>  \
>  \ 'g:notmuch_show_signature_regexp': '^\(-- \?\|_\+\)$'  
>  ,
> @@ -870,7 +870,8 @@ function! s:NM_cmd_show_parse(inlines)
>  let msg['id'] = m[1]
>  let msg['depth'] = m[2]
>  let msg['match'] = m[3]
> -let msg['filename'] = m[4]
> +let msg['excluded'] = m[4]
> +let msg['filename'] = m[5]
>  endif
>  
>  let in_message = 1
> -- 
> 1.7.9.1
>
> ___
> notmuch mailing list
> notmuch@notmuchmail.org
> http://notmuchmail.org/mailman/listinfo/notmuch
___
notmuch mailing list
notmuch@notmuchmail.org
http://notmuchmail.org/mailman/listinfo/notmuch