--- /dev/null
+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 (®ex, 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 (®ex);\r
++ if (input_format == DUMP_FORMAT_SUP)\r
++ regfree (®ex);\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