V3b of batch tagging/dump/restore patches
authordavid <david@tethera.net>
Fri, 7 Dec 2012 01:26:38 +0000 (21:26 +2000)
committerW. Trevor King <wking@tremily.us>
Fri, 7 Nov 2014 17:51:38 +0000 (09:51 -0800)
03/08d5adf5676fdc04a626cf4d5fc4d98745469f [new file with mode: 0644]

diff --git a/03/08d5adf5676fdc04a626cf4d5fc4d98745469f b/03/08d5adf5676fdc04a626cf4d5fc4d98745469f
new file mode 100644 (file)
index 0000000..a066566
--- /dev/null
@@ -0,0 +1,589 @@
+Return-Path: <bremner@tethera.net>\r
+X-Original-To: notmuch@notmuchmail.org\r
+Delivered-To: notmuch@notmuchmail.org\r
+Received: from localhost (localhost [127.0.0.1])\r
+       by olra.theworths.org (Postfix) with ESMTP id ABFA3431FB6\r
+       for <notmuch@notmuchmail.org>; Thu,  6 Dec 2012 17:27:10 -0800 (PST)\r
+X-Virus-Scanned: Debian amavisd-new at olra.theworths.org\r
+X-Spam-Flag: NO\r
+X-Spam-Score: 0\r
+X-Spam-Level: \r
+X-Spam-Status: No, score=0 tagged_above=-999 required=5 tests=[none]\r
+       autolearn=disabled\r
+Received: from olra.theworths.org ([127.0.0.1])\r
+       by localhost (olra.theworths.org [127.0.0.1]) (amavisd-new, port 10024)\r
+       with ESMTP id u-Cm-Q3sqx-V for <notmuch@notmuchmail.org>;\r
+       Thu,  6 Dec 2012 17:27:09 -0800 (PST)\r
+Received: from tesseract.cs.unb.ca (tesseract.cs.unb.ca [131.202.240.238])\r
+       (using TLSv1 with cipher AES256-SHA (256/256 bits))\r
+       (No client certificate requested)\r
+       by olra.theworths.org (Postfix) with ESMTPS id 3550A431FAE\r
+       for <notmuch@notmuchmail.org>; Thu,  6 Dec 2012 17:27:09 -0800 (PST)\r
+Received: from fctnnbsc30w-142167090129.dhcp-dynamic.fibreop.nb.bellaliant.net\r
+       ([142.167.90.129] helo=zancas.localnet)\r
+       by tesseract.cs.unb.ca with esmtpsa\r
+       (TLS1.0:DHE_RSA_AES_128_CBC_SHA1:16) (Exim 4.72)\r
+       (envelope-from <bremner@tethera.net>) id 1TgmiZ-0003Np-7E\r
+       for notmuch@notmuchmail.org; Thu, 06 Dec 2012 21:27:08 -0400\r
+Received: from bremner by zancas.localnet with local (Exim 4.80)\r
+       (envelope-from <bremner@tethera.net>) id 1TgmiT-0004kE-LV\r
+       for notmuch@notmuchmail.org; Thu, 06 Dec 2012 21:27:01 -0400\r
+From: david@tethera.net\r
+To: notmuch@notmuchmail.org\r
+Subject: V3b of batch tagging/dump/restore patches\r
+Date: Thu,  6 Dec 2012 21:26:38 -0400\r
+Message-Id: <1354843607-17980-1-git-send-email-david@tethera.net>\r
+X-Mailer: git-send-email 1.7.10.4\r
+X-Spam_bar: -\r
+X-BeenThere: notmuch@notmuchmail.org\r
+X-Mailman-Version: 2.1.13\r
+Precedence: list\r
+List-Id: "Use and development of the notmuch mail system."\r
+       <notmuch.notmuchmail.org>\r
+List-Unsubscribe: <http://notmuchmail.org/mailman/options/notmuch>,\r
+       <mailto:notmuch-request@notmuchmail.org?subject=unsubscribe>\r
+List-Archive: <http://notmuchmail.org/pipermail/notmuch>\r
+List-Post: <mailto:notmuch@notmuchmail.org>\r
+List-Help: <mailto:notmuch-request@notmuchmail.org?subject=help>\r
+List-Subscribe: <http://notmuchmail.org/mailman/listinfo/notmuch>,\r
+       <mailto:notmuch-request@notmuchmail.org?subject=subscribe>\r
+X-List-Received-Date: Fri, 07 Dec 2012 01:27:10 -0000\r
+\r
+Here is a second piece of the tagging/dump/restore series.\r
+\r
+it obsoletes 8 of the patches in the series \r
+\r
+   id:1353792017-31459-1-git-send-email-david@tethera.net\r
+\r
+ [Patch v3b 1/9] notmuch-dump: add --format=(batch-tag|sup)\r
+ [Patch v3b 3/9] util: add string-util.[ch]\r
+ [Patch v3b 4/9] tag-util.[ch]: New files for common tagging routines\r
+ [Patch v3b 5/9] notmuch-restore: add support for input format\r
+ [Patch v3b 6/9] test: update dump-restore roundtripping test for\r
+ [Patch v3b 7/9] test: second set of dump/restore --format=batch-tag\r
+ [Patch v3b 8/9] notmuch-{dump,restore}.1: document new format\r
+ [Patch v3b 9/9] tag-util: optimization of tag application\r
+\r
+It adds one new patch\r
+\r
+ [Patch v3b 2/9] test: add sanity check for dump --format=batch-tag.\r
+\r
+I still have to work through some of the comments on the batch\r
+tagging; I still intend for that to follow fairly shortly, I just want\r
+to break the series at a logical point.\r
+\r
+Most of the changes are detailed in the following log (of changes\r
+before I squashed them)\r
+\r
+commit 5045d2f58beb4c3bc8e10f9419341e1c1b7748f2\r
+Author: David Bremner <bremner@debian.org>\r
+Date:   Tue Dec 4 13:39:48 2012 -0400\r
+\r
+    fixup for tag-util error messages\r
+\r
+diff --git a/tag-util.c b/tag-util.c\r
+index 2bb8355..ea05ee5 100644\r
+--- a/tag-util.c\r
++++ b/tag-util.c\r
+@@ -86,9 +86,8 @@ parse_tag_line (void *ctx, char *line,\r
\r
+     /* tok now points to the query string */\r
+     if (hex_decode_inplace (tok) != HEX_SUCCESS) {\r
+-      /* FIXME: line has been modified! */\r
+-      fprintf (stderr, "Warning: Ignoring invalid input line: %s\n",\r
+-               line);\r
++      fprintf (stderr, "Hex decoding of %s failed\n",\r
++               tok);\r
+       return 1;\r
+     }\r
\r
+\r
+commit 5a1d697dc408c67424d586b6377976fdfb86d4ed\r
+Author: David Bremner <bremner@debian.org>\r
+Date:   Tue Dec 4 13:40:09 2012 -0400\r
+\r
+    fixup for restore error messages\r
+\r
+diff --git a/notmuch-restore.c b/notmuch-restore.c\r
+index 22fcd2d..e7584bb 100644\r
+--- a/notmuch-restore.c\r
++++ b/notmuch-restore.c\r
+@@ -77,7 +77,7 @@ parse_sup_line (void *ctx, char *line,\r
\r
+     rerr = xregexec (&regex, line, 3, match, 0);\r
+     if (rerr == REG_NOMATCH) {\r
+-      fprintf (stderr, "Warning: Ignoring invalid input line: %s\n",\r
++      fprintf (stderr, "Warning: Ignoring invalid sup format line: %s\n",\r
+                line);\r
+       return 1;\r
+     }\r
+\r
+commit 7136d3b4e974a4ba8e247f328c71362efd0c9e11\r
+Author: David Bremner <bremner@debian.org>\r
+Date:   Tue Dec 4 22:35:30 2012 -0400\r
+\r
+    fixup for tag-util error handling and permit the null set of tag operations\r
+\r
+diff --git a/tag-util.c b/tag-util.c\r
+index ea05ee5..de7ecc8 100644\r
+--- a/tag-util.c\r
++++ b/tag-util.c\r
+@@ -21,6 +21,8 @@ parse_tag_line (void *ctx, char *line,\r
+ {\r
+     char *tok = line;\r
+     size_t tok_len = 0;\r
++    char *line_for_error=talloc_strdup (ctx, line);\r
++    int ret=0;\r
\r
+     chomp_newline (line);\r
\r
+@@ -29,8 +31,10 @@ parse_tag_line (void *ctx, char *line,\r
+       tok++;\r
\r
+     /* Skip empty and comment lines. */\r
+-    if (*tok == '\0' || *tok == '#')\r
+-          return 1;\r
++    if (*tok == '\0' || *tok == '#') {\r
++      ret=1;\r
++      goto DONE;\r
++    }\r
\r
+     tag_op_list_reset (tag_ops);\r
\r
+@@ -51,8 +55,9 @@ parse_tag_line (void *ctx, char *line,\r
\r
+       /* If tag is terminated by NUL, there's no query string. */\r
+       if (*(tok + tok_len) == '\0') {\r
+-          tok = NULL;\r
+-          break;\r
++          fprintf (stderr, "no query string: %s\n", line_for_error);\r
++          ret = 1;\r
++          goto DONE;\r
+       }\r
\r
+       /* Terminate, and start next token after terminator. */\r
+@@ -63,37 +68,43 @@ parse_tag_line (void *ctx, char *line,\r
\r
+       /* Maybe refuse empty tags. */\r
+       if (!(flags & TAG_FLAG_BE_GENEROUS) && *tag == '\0') {\r
+-          tok = NULL;\r
+-          break;\r
++          fprintf (stderr, "Error: empty tag: %s\n", line_for_error);\r
++          goto DONE;\r
+       }\r
\r
+       /* Decode tag. */\r
+       if (hex_decode_inplace (tag) != HEX_SUCCESS) {\r
+-          tok = NULL;\r
+-          break;\r
++          fprintf (stderr, "Hex decoding of tag %s failed\n",\r
++               tag);\r
++          ret = 1;\r
++          goto DONE;\r
+       }\r
\r
+-      if (tag_op_list_append (ctx, tag_ops, tag, remove))\r
+-          return -1;\r
++      if (tag_op_list_append (ctx, tag_ops, tag, remove)) {\r
++          ret = -1;\r
++          goto DONE;\r
++      }\r
+     }\r
\r
+-    if (tok == NULL || tag_ops->count == 0) {\r
+-      /* FIXME: line has been modified! */\r
++    if (tok == NULL) {\r
+       fprintf (stderr, "Warning: Ignoring invalid input line: %s\n",\r
+-               line);\r
+-      return 1;\r
++               line_for_error);\r
++      ret = 1;\r
++      goto DONE;\r
+     }\r
\r
+     /* tok now points to the query string */\r
+     if (hex_decode_inplace (tok) != HEX_SUCCESS) {\r
+-      fprintf (stderr, "Hex decoding of %s failed\n",\r
++      fprintf (stderr, "Hex decoding of query %s failed\n",\r
+                tok);\r
+-      return 1;\r
++      ret = 1;\r
++      goto DONE;\r
+     }\r
\r
+     *query_string = tok;\r
+-\r
+-    return 0;\r
++ DONE:\r
++    talloc_free (line_for_error);\r
++    return ret;\r
+ }\r
\r
+ static inline void\r
+\r
+commit 5912c738d3683aae24ca5529839eb0513520d190\r
+Author: David Bremner <bremner@debian.org>\r
+Date:   Thu Dec 6 07:49:15 2012 -0400\r
+\r
+    fixup for id:87wqx1qrmq.fsf@nikula.org; use size_t for tag_op_list count\r
+\r
+diff --git a/tag-util.c b/tag-util.c\r
+index de7ecc8..1a0cf53 100644\r
+--- a/tag-util.c\r
++++ b/tag-util.c\r
+@@ -1,6 +1,7 @@\r
+ #include "string-util.h"\r
+ #include "tag-util.h"\r
+ #include "hex-escape.h"\r
++#include <assert.h>\r
\r
+ struct _tag_operation_t {\r
+     const char *tag;\r
+@@ -9,8 +10,8 @@ struct _tag_operation_t {\r
\r
+ struct _tag_op_list_t {\r
+     tag_operation_t *ops;\r
+-    int count;\r
+-    int size;\r
++    size_t count;\r
++    size_t size;\r
+ };\r
\r
+ int\r
+@@ -44,7 +45,7 @@ parse_tag_line (void *ctx, char *line,\r
+       char *tag;\r
\r
+       /* Optional explicit end of tags marker. */\r
+-      if (strncmp (tok, "--", tok_len) == 0) {\r
++      if (tok_len == 2 && strncmp (tok, "--", tok_len) == 0) {\r
+           tok = strtok_len (tok + tok_len, " ", &tok_len);\r
+           break;\r
+       }\r
+@@ -126,17 +127,16 @@ makes_changes (notmuch_message_t *message,\r
+              tag_op_list_t *list,\r
+              tag_op_flag_t flags)\r
+ {\r
+-\r
+-    int i;\r
+-\r
+     notmuch_tags_t *tags;\r
+     notmuch_bool_t changes = FALSE;\r
++    size_t i;\r
\r
+     /* First, do we delete an existing tag? */\r
+     changes = FALSE;\r
+     for (tags = notmuch_message_get_tags (message);\r
+        ! changes && notmuch_tags_valid (tags);\r
+        notmuch_tags_move_to_next (tags)) {\r
++\r
+       const char *cur_tag = notmuch_tags_get (tags);\r
+       int last_op =  (flags & TAG_FLAG_REMOVE_ALL) ? -1 : 0;\r
\r
+@@ -182,8 +182,7 @@ tag_op_list_apply (notmuch_message_t *message,\r
+                  tag_op_list_t *list,\r
+                  tag_op_flag_t flags)\r
+ {\r
+-    int i;\r
+-\r
++    size_t i;\r
+     notmuch_status_t status = 0;\r
+     tag_operation_t *tag_ops = list->ops;\r
\r
+@@ -199,7 +198,7 @@ tag_op_list_apply (notmuch_message_t *message,\r
+     if (flags & TAG_FLAG_REMOVE_ALL) {\r
+       status = notmuch_message_remove_all_tags (message);\r
+       if (status) {\r
+-          message_error (message, status, "removing all tags" );\r
++          message_error (message, status, "removing all tags");\r
+           return status;\r
+       }\r
+     }\r
+@@ -241,8 +240,8 @@ tag_op_list_apply (notmuch_message_t *message,\r
+ }\r
\r
\r
+-/* Array of tagging operations (add or remove), terminated with an\r
+- * empty element. Size will be increased as necessary. */\r
++/* Array of tagging operations (add or remove.  Size will be increased\r
++ * as necessary. */\r
\r
+ tag_op_list_t *\r
+ tag_op_list_create (void *ctx)\r
+@@ -299,6 +298,7 @@ tag_op_list_append (void *ctx,\r
+ notmuch_bool_t\r
+ tag_op_list_isremove (const tag_op_list_t *list, size_t i)\r
+ {\r
++    assert (i < list->count);\r
+     return list->ops[i].remove;\r
+ }\r
\r
+@@ -329,5 +329,6 @@ tag_op_list_size (const tag_op_list_t *list)\r
+ const char *\r
+ tag_op_list_tag (const tag_op_list_t *list, size_t i)\r
+ {\r
++    assert (i < list->count);\r
+     return list->ops[i].tag;\r
+ }\r
+\r
+commit e1af69d57854d5c6e927b0870be97e9d2e2f28ea\r
+Author: David Bremner <bremner@debian.org>\r
+Date:   Thu Dec 6 07:51:43 2012 -0400\r
+\r
+    changes for id:87wqx1qrmq.fsf@nikula.org. tag_op_list_t.count -> size_t\r
+\r
+diff --git a/tag-util.c b/tag-util.c\r
+index 1a0cf53..ad13147 100644\r
+--- a/tag-util.c\r
++++ b/tag-util.c\r
+@@ -22,8 +22,8 @@ parse_tag_line (void *ctx, char *line,\r
+ {\r
+     char *tok = line;\r
+     size_t tok_len = 0;\r
+-    char *line_for_error=talloc_strdup (ctx, line);\r
+-    int ret=0;\r
++    char *line_for_error = talloc_strdup (ctx, line);\r
++    int ret = 0;\r
\r
+     chomp_newline (line);\r
\r
+@@ -33,7 +33,7 @@ parse_tag_line (void *ctx, char *line,\r
\r
+     /* Skip empty and comment lines. */\r
+     if (*tok == '\0' || *tok == '#') {\r
+-      ret=1;\r
++      ret = 1;\r
+       goto DONE;\r
+     }\r
\r
+@@ -68,7 +68,7 @@ parse_tag_line (void *ctx, char *line,\r
+       tag = tok + 1;\r
\r
+       /* Maybe refuse empty tags. */\r
+-      if (!(flags & TAG_FLAG_BE_GENEROUS) && *tag == '\0') {\r
++      if (! (flags & TAG_FLAG_BE_GENEROUS) && *tag == '\0') {\r
+           fprintf (stderr, "Error: empty tag: %s\n", line_for_error);\r
+           goto DONE;\r
+       }\r
+@@ -76,7 +76,7 @@ parse_tag_line (void *ctx, char *line,\r
+       /* Decode tag. */\r
+       if (hex_decode_inplace (tag) != HEX_SUCCESS) {\r
+           fprintf (stderr, "Hex decoding of tag %s failed\n",\r
+-               tag);\r
++                   tag);\r
+           ret = 1;\r
+           goto DONE;\r
+       }\r
+@@ -103,7 +103,7 @@ parse_tag_line (void *ctx, char *line,\r
+     }\r
\r
+     *query_string = tok;\r
+- DONE:\r
++  DONE:\r
+     talloc_free (line_for_error);\r
+     return ret;\r
+ }\r
+\r
+commit 3f00fa4eba68876635df86ea54f60b68d172f580\r
+Author: David Bremner <bremner@debian.org>\r
+Date:   Thu Dec 6 08:30:58 2012 -0400\r
+\r
+    changes for id:87zk1wd1ko.fsf@nikula.org\r
+\r
+diff --git a/notmuch-restore.c b/notmuch-restore.c\r
+index e7584bb..41b742f 100644\r
+--- a/notmuch-restore.c\r
++++ b/notmuch-restore.c\r
+@@ -48,11 +48,10 @@ tag_message (unused (void *ctx),\r
\r
+     /* In order to detect missing messages, this check/optimization is\r
+      * intentionally done *after* first finding the message. */\r
+-    if ( (flags & TAG_FLAG_REMOVE_ALL) || (tag_op_list_size (tag_ops)))\r
++    if ((flags & TAG_FLAG_REMOVE_ALL) || tag_op_list_size (tag_ops))\r
+       tag_op_list_apply (message, tag_ops, flags);\r
\r
+-    if (message)\r
+-      notmuch_message_destroy (message);\r
++    notmuch_message_destroy (message);\r
\r
+     return ret;\r
+ }\r
+@@ -184,6 +183,12 @@ notmuch_restore_command (unused (void *ctx), int argc, char *argv[])\r
+     if (line_len == 0)\r
+       return 0;\r
\r
++    tag_ops = tag_op_list_create (ctx);\r
++    if (tag_ops == NULL) {\r
++      fprintf (stderr, "Out of memory.\n");\r
++      return 1;\r
++    }\r
++\r
+     for (p = line; *p; p++) {\r
+       if (*p == '(')\r
+           input_format = DUMP_FORMAT_SUP;\r
+@@ -198,28 +203,28 @@ notmuch_restore_command (unused (void *ctx), int argc, char *argv[])\r
+                      REG_EXTENDED) )\r
+           INTERNAL_ERROR ("compile time constant regex failed.");\r
\r
+-    tag_ops = tag_op_list_create (ctx);\r
+-    if (tag_ops == NULL) {\r
+-      fprintf (stderr, "Out of memory.\n");\r
+-      return 1;\r
+-    }\r
+-\r
+     do {\r
+       char *query_string;\r
\r
+       if (input_format == DUMP_FORMAT_SUP) {\r
+-          ret =  parse_sup_line (ctx, line, &query_string, tag_ops);\r
++          ret = parse_sup_line (ctx, line, &query_string, tag_ops);\r
+       } else {\r
+-          ret =  parse_tag_line (ctx, line, TAG_FLAG_BE_GENEROUS,\r
+-                                 &query_string, tag_ops);\r
++          ret = parse_tag_line (ctx, line, TAG_FLAG_BE_GENEROUS,\r
++                                &query_string, tag_ops);\r
\r
+           if (ret == 0) {\r
+-              if ( strncmp ("id:", query_string, 3) != 0) {\r
++              if (strncmp ("id:", query_string, 3) != 0) {\r
+                   fprintf (stderr, "Unsupported query: %s\n", query_string);\r
+                   continue;\r
+               }\r
+-              /* delete id: from front of string; tag_message expects a\r
+-               * raw message-id */\r
++              /* delete id: from front of string; tag_message\r
++               * expects a raw message-id.\r
++               *\r
++               * XXX: Note that query string id:foo and bar will be\r
++               * interpreted as a message id "foo and bar". This\r
++               * should eventually be fixed to give a better error\r
++               * message.\r
++               */\r
+               query_string = query_string + 3;\r
+           }\r
+       }\r
+@@ -233,8 +238,8 @@ notmuch_restore_command (unused (void *ctx), int argc, char *argv[])\r
\r
+     }  while ((line_len = getline (&line, &line_size, input)) != -1);\r
\r
+-\r
+-    regfree (&regex);\r
++    if (input_format == DUMP_FORMAT_SUP)\r
++      regfree (&regex);\r
\r
+     if (line)\r
+       free (line);\r
+\r
+commit 473fa928931080004706cff169c7cc9337601172\r
+Author: David Bremner <bremner@debian.org>\r
+Date:   Thu Dec 6 13:33:42 2012 -0400\r
+\r
+    fixup: notmuch-restore only auto-detect in auto mode\r
+\r
+diff --git a/notmuch-restore.c b/notmuch-restore.c\r
+index 41b742f..ceec2d3 100644\r
+--- a/notmuch-restore.c\r
++++ b/notmuch-restore.c\r
+@@ -189,7 +189,7 @@ notmuch_restore_command (unused (void *ctx), int argc, char *argv[])\r
+       return 1;\r
+     }\r
\r
+-    for (p = line; *p; p++) {\r
++    for (p = line; (input_format == DUMP_FORMAT_AUTO) && *p; p++) {\r
+       if (*p == '(')\r
+           input_format = DUMP_FORMAT_SUP;\r
+     }\r
+\r
+commit ee7d25521e3f6f70b3f4fb79f586a11d39efec15\r
+Author: David Bremner <bremner@debian.org>\r
+Date:   Thu Dec 6 19:39:37 2012 -0400\r
+\r
+    changes for id:87wqx0d124.fsf@nikula.org; no deprecation for the moment\r
+\r
+diff --git a/man/man1/notmuch-dump.1 b/man/man1/notmuch-dump.1\r
+index 9f59905..770b00f 100644\r
+--- a/man/man1/notmuch-dump.1\r
++++ b/man/man1/notmuch-dump.1\r
+@@ -64,15 +64,16 @@ and tags containing whitespace or non-\fBascii\fR(7) characters.\r
+ Each line has the form\r
\r
+ .RS 4\r
+-.RI "+<" "encoded-tag" "> " "" "+<" "encoded-tag" "> ... -- " "" " <" encoded-message-id >\r
++.RI "+<" "encoded-tag" "> " "" "+<" "encoded-tag" "> ... -- " "" " id:<" encoded-message-id >\r
\r
+ where encoded means that every byte not matching the regex\r
+-.B [A-Za-z0-9+-_@=.:,]\r
++.B [A-Za-z0-9@=.,_+-]\r
+ is replace by\r
+ .B %nn\r
+ where nn is the two digit hex encoding.\r
+ The astute reader will notice this is a special case of the batch input\r
+-format for \fBnotmuch-tag\fR(1).\r
++format for \fBnotmuch-tag\fR(1); note that the single message-id query is\r
++mandatory for \fBnotmuch-restore\fR(1).\r
\r
+ .RE\r
\r
+diff --git a/man/man1/notmuch-restore.1 b/man/man1/notmuch-restore.1\r
+index 3860829..6bba628 100644\r
+--- a/man/man1/notmuch-restore.1\r
++++ b/man/man1/notmuch-restore.1\r
+@@ -32,8 +32,8 @@ replacing each message's tags as they are read in from the dump file.\r
+ .TP 4\r
+ .B \-\-format=(sup|batch-tag|auto)\r
\r
+-Notmuch restore supports two plain text dump formats, with one message-id\r
+-per line, and a list of tags.\r
++Notmuch restore supports two plain text dump formats, with each line\r
++specifying a message-id and a set of tags.\r
+ For details of the actual formats, see \fBnotmuch-dump\fR(1).\r
\r
+ .RS 4\r
+\r
+commit 96c383be46cdb8ceaf7ed15590ef876799d6357e\r
+Author: David Bremner <bremner@debian.org>\r
+Date:   Thu Dec 6 20:40:55 2012 -0400\r
+\r
+    Changes for id:87txs4cy7v.fsf@nikula.org\r
+\r
+diff --git a/tag-util.c b/tag-util.c\r
+index ad13147..9ab07e9 100644\r
+--- a/tag-util.c\r
++++ b/tag-util.c\r
+@@ -140,9 +140,11 @@ makes_changes (notmuch_message_t *message,\r
+       const char *cur_tag = notmuch_tags_get (tags);\r
+       int last_op =  (flags & TAG_FLAG_REMOVE_ALL) ? -1 : 0;\r
\r
+-      for (i = 0; i < list->count; i++) {\r
++      /* slight contortions to count down with an unsigned index */\r
++      for (i = list->count; i-- > 0; /*nothing*/) {\r
+           if (strcmp (cur_tag, list->ops[i].tag) == 0) {\r
+               last_op = list->ops[i].remove ? -1 : 1;\r
++              break;\r
+           }\r
+       }\r
\r
+@@ -157,6 +159,9 @@ makes_changes (notmuch_message_t *message,\r
+     for (i = 0; i < list->count; i++) {\r
+       notmuch_bool_t exists = FALSE;\r
\r
++      if (list->ops[i].remove)\r
++          continue;\r
++\r
+       for (tags = notmuch_message_get_tags (message);\r
+            notmuch_tags_valid (tags);\r
+            notmuch_tags_move_to_next (tags)) {\r
+@@ -168,9 +173,11 @@ makes_changes (notmuch_message_t *message,\r
+       }\r
+       notmuch_tags_destroy (tags);\r
\r
+-      /* the following test is conservative, it's ok to think we\r
+-       * make changes when we don't */\r
+-      if ( ! exists && ! list->ops[i].remove )\r
++      /* the following test is conservative,\r
++       * in the sense it ignores cases like +foo ... -foo\r
++       * but this is OK from a correctness point of view\r
++       */\r
++      if (! exists)\r
+           return TRUE;\r
+     }\r
+     return FALSE;\r