--- /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 03E90429E31\r
+ for <notmuch@notmuchmail.org>; Sat, 8 Dec 2012 07:08:28 -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 VaK-AAmUb56J for <notmuch@notmuchmail.org>;\r
+ Sat, 8 Dec 2012 07:08:24 -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 5776C429E45\r
+ for <notmuch@notmuchmail.org>; Sat, 8 Dec 2012 07:08:15 -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>)\r
+ id 1ThM0k-0005dO-0c; Sat, 08 Dec 2012 11:08:14 -0400\r
+Received: from bremner by zancas.localnet with local (Exim 4.80)\r
+ (envelope-from <bremner@tethera.net>)\r
+ id 1ThM0e-0005Gx-IF; Sat, 08 Dec 2012 11:08:08 -0400\r
+From: david@tethera.net\r
+To: notmuch@notmuchmail.org\r
+Subject: [Patch v4 05/10] notmuch-restore: add support for input format\r
+ 'batch-tag'\r
+Date: Sat, 8 Dec 2012 11:07:51 -0400\r
+Message-Id: <1354979276-20099-6-git-send-email-david@tethera.net>\r
+X-Mailer: git-send-email 1.7.10.4\r
+In-Reply-To: <1354979276-20099-1-git-send-email-david@tethera.net>\r
+References: <1354979276-20099-1-git-send-email-david@tethera.net>\r
+X-Spam_bar: -\r
+Cc: David Bremner <bremner@debian.org>\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: Sat, 08 Dec 2012 15:08:28 -0000\r
+\r
+From: David Bremner <bremner@debian.org>\r
+\r
+This can be enabled with the new --format=batch-tag command line\r
+option to "notmuch restore". The input must consist of lines of the\r
+format:\r
+\r
+ +<tag>|-<tag> [...] [--] id:<msg-id>\r
+\r
+Each line is interpreted similarly to "notmuch tag" command line\r
+arguments. The delimiter is one or more spaces ' '. Any characters in\r
+<tag> and <search-terms> MAY be hex encoded with %NN where NN is the\r
+hexadecimal value of the character. Any ' ' and '%' characters in\r
+<tag> and <msg-id> MUST be hex encoded (using %20 and %25,\r
+respectively). Any characters that are not part of <tag> or\r
+<search-terms> MUST NOT be hex encoded.\r
+\r
+Leading and trailing space ' ' is ignored. Empty lines and lines\r
+beginning with '#' are ignored.\r
+\r
+Commit message mainly stolen from Jani's batch tagging commit, to\r
+follow.\r
+---\r
+ notmuch-restore.c | 220 +++++++++++++++++++++++++++++++++--------------------\r
+ 1 file changed, 138 insertions(+), 82 deletions(-)\r
+\r
+diff --git a/notmuch-restore.c b/notmuch-restore.c\r
+index f03dcac..44bf88d 100644\r
+--- a/notmuch-restore.c\r
++++ b/notmuch-restore.c\r
+@@ -19,18 +19,22 @@\r
+ */\r
+ \r
+ #include "notmuch-client.h"\r
++#include "dump-restore-private.h"\r
++#include "tag-util.h"\r
++#include "string-util.h"\r
++\r
++static volatile sig_atomic_t interrupted;\r
++static regex_t regex;\r
+ \r
+ static int\r
+-tag_message (notmuch_database_t *notmuch, const char *message_id,\r
+- char *file_tags, notmuch_bool_t remove_all,\r
+- notmuch_bool_t synchronize_flags)\r
++tag_message (unused (void *ctx),\r
++ notmuch_database_t *notmuch,\r
++ const char *message_id,\r
++ tag_op_list_t *tag_ops,\r
++ tag_op_flag_t flags)\r
+ {\r
+ notmuch_status_t status;\r
+- notmuch_tags_t *db_tags;\r
+- char *db_tags_str;\r
+ notmuch_message_t *message = NULL;\r
+- const char *tag;\r
+- char *next;\r
+ int ret = 0;\r
+ \r
+ status = notmuch_database_find_message (notmuch, message_id, &message);\r
+@@ -44,55 +48,67 @@ tag_message (notmuch_database_t *notmuch, const char *message_id,\r
+ \r
+ /* In order to detect missing messages, this check/optimization is\r
+ * intentionally done *after* first finding the message. */\r
+- if (! remove_all && (file_tags == NULL || *file_tags == '\0'))\r
+- goto DONE;\r
+-\r
+- db_tags_str = NULL;\r
+- for (db_tags = notmuch_message_get_tags (message);\r
+- notmuch_tags_valid (db_tags);\r
+- notmuch_tags_move_to_next (db_tags)) {\r
+- tag = notmuch_tags_get (db_tags);\r
+-\r
+- if (db_tags_str)\r
+- db_tags_str = talloc_asprintf_append (db_tags_str, " %s", tag);\r
+- else\r
+- db_tags_str = talloc_strdup (message, tag);\r
+- }\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 (((file_tags == NULL || *file_tags == '\0') &&\r
+- (db_tags_str == NULL || *db_tags_str == '\0')) ||\r
+- (file_tags && db_tags_str && strcmp (file_tags, db_tags_str) == 0))\r
+- goto DONE;\r
++ notmuch_message_destroy (message);\r
+ \r
+- notmuch_message_freeze (message);\r
++ return ret;\r
++}\r
+ \r
+- if (remove_all)\r
+- notmuch_message_remove_all_tags (message);\r
++/* Sup dump output is one line per message. We match a sequence of\r
++ * non-space characters for the message-id, then one or more\r
++ * spaces, then a list of space-separated tags as a sequence of\r
++ * characters within literal '(' and ')'. */\r
+ \r
+- next = file_tags;\r
+- while (next) {\r
+- tag = strsep (&next, " ");\r
+- if (*tag == '\0')\r
+- continue;\r
+- status = notmuch_message_add_tag (message, tag);\r
+- if (status) {\r
+- fprintf (stderr, "Error applying tag %s to message %s:\n",\r
+- tag, message_id);\r
+- fprintf (stderr, "%s\n", notmuch_status_to_string (status));\r
+- ret = 1;\r
+- }\r
++static int\r
++parse_sup_line (void *ctx, char *line,\r
++ char **query_str, tag_op_list_t *tag_ops)\r
++{\r
++\r
++ regmatch_t match[3];\r
++ char *file_tags;\r
++ int rerr;\r
++\r
++ tag_op_list_reset (tag_ops);\r
++\r
++ chomp_newline (line);\r
++\r
++ /* Silently ignore blank lines */\r
++ if (line[0] == '\0') {\r
++ return 1;\r
+ }\r
+ \r
+- notmuch_message_thaw (message);\r
++ rerr = xregexec (®ex, line, 3, match, 0);\r
++ if (rerr == REG_NOMATCH) {\r
++ fprintf (stderr, "Warning: Ignoring invalid sup format line: %s\n",\r
++ line);\r
++ return 1;\r
++ }\r
+ \r
+- if (synchronize_flags)\r
+- notmuch_message_tags_to_maildir_flags (message);\r
++ *query_str = talloc_strndup (ctx, line + match[1].rm_so,\r
++ match[1].rm_eo - match[1].rm_so);\r
++ file_tags = talloc_strndup (ctx, line + match[2].rm_so,\r
++ match[2].rm_eo - match[2].rm_so);\r
+ \r
+- DONE:\r
+- if (message)\r
+- notmuch_message_destroy (message);\r
++ char *tok = file_tags;\r
++ size_t tok_len = 0;\r
++\r
++ tag_op_list_reset (tag_ops);\r
++\r
++ while ((tok = strtok_len (tok + tok_len, " ", &tok_len)) != NULL) {\r
++\r
++ if (*(tok + tok_len) != '\0') {\r
++ *(tok + tok_len) = '\0';\r
++ tok_len++;\r
++ }\r
++\r
++ if (tag_op_list_append (ctx, tag_ops, tok, FALSE))\r
++ return -1;\r
++ }\r
++\r
++ return 0;\r
+ \r
+- return ret;\r
+ }\r
+ \r
+ int\r
+@@ -100,16 +116,19 @@ notmuch_restore_command (unused (void *ctx), int argc, char *argv[])\r
+ {\r
+ notmuch_config_t *config;\r
+ notmuch_database_t *notmuch;\r
+- notmuch_bool_t synchronize_flags;\r
+ notmuch_bool_t accumulate = FALSE;\r
++ tag_op_flag_t flags = 0;\r
++ tag_op_list_t *tag_ops;\r
++\r
+ char *input_file_name = NULL;\r
+ FILE *input = stdin;\r
+ char *line = NULL;\r
+ size_t line_size;\r
+ ssize_t line_len;\r
+- regex_t regex;\r
+- int rerr;\r
++\r
++ int ret = 0;\r
+ int opt_index;\r
++ int input_format = DUMP_FORMAT_AUTO;\r
+ \r
+ config = notmuch_config_open (ctx, NULL, NULL);\r
+ if (config == NULL)\r
+@@ -119,9 +138,15 @@ notmuch_restore_command (unused (void *ctx), int argc, char *argv[])\r
+ NOTMUCH_DATABASE_MODE_READ_WRITE, ¬much))\r
+ return 1;\r
+ \r
+- synchronize_flags = notmuch_config_get_maildir_synchronize_flags (config);\r
++ if (notmuch_config_get_maildir_synchronize_flags (config))\r
++ flags |= TAG_FLAG_MAILDIR_SYNC;\r
+ \r
+ notmuch_opt_desc_t options[] = {\r
++ { NOTMUCH_OPT_KEYWORD, &input_format, "format", 'f',\r
++ (notmuch_keyword_t []){ { "auto", DUMP_FORMAT_AUTO },\r
++ { "batch-tag", DUMP_FORMAT_BATCH_TAG },\r
++ { "sup", DUMP_FORMAT_SUP },\r
++ { 0, 0 } } },\r
+ { NOTMUCH_OPT_STRING, &input_file_name, "input", 'i', 0 },\r
+ { NOTMUCH_OPT_BOOLEAN, &accumulate, "accumulate", 'a', 0 },\r
+ { 0, 0, 0, 0, 0 }\r
+@@ -134,6 +159,9 @@ notmuch_restore_command (unused (void *ctx), int argc, char *argv[])\r
+ return 1;\r
+ }\r
+ \r
++ if (! accumulate)\r
++ flags |= TAG_FLAG_REMOVE_ALL;\r
++\r
+ if (input_file_name) {\r
+ input = fopen (input_file_name, "r");\r
+ if (input == NULL) {\r
+@@ -149,49 +177,77 @@ notmuch_restore_command (unused (void *ctx), int argc, char *argv[])\r
+ argv[opt_index]);\r
+ return 1;\r
+ }\r
++ char *p;\r
+ \r
+- /* Dump output is one line per message. We match a sequence of\r
+- * non-space characters for the message-id, then one or more\r
+- * spaces, then a list of space-separated tags as a sequence of\r
+- * characters within literal '(' and ')'. */\r
+- if ( xregcomp (®ex,\r
+- "^([^ ]+) \\(([^)]*)\\)$",\r
+- REG_EXTENDED) )\r
+- INTERNAL_ERROR ("compile time constant regex failed.");\r
+-\r
+- while ((line_len = getline (&line, &line_size, input)) != -1) {\r
+- regmatch_t match[3];\r
+- char *message_id, *file_tags;\r
+-\r
+- chomp_newline (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
+- line);\r
+- continue;\r
++ line_len = getline (&line, &line_size, input);\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; (input_format == DUMP_FORMAT_AUTO) && *p; p++) {\r
++ if (*p == '(')\r
++ input_format = DUMP_FORMAT_SUP;\r
++ }\r
++\r
++ if (input_format == DUMP_FORMAT_AUTO)\r
++ input_format = DUMP_FORMAT_BATCH_TAG;\r
++\r
++ if (input_format == DUMP_FORMAT_SUP)\r
++ if ( xregcomp (®ex,\r
++ "^([^ ]+) \\(([^)]*)\\)$",\r
++ REG_EXTENDED) )\r
++ INTERNAL_ERROR ("compile time constant regex failed.");\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
++ } else {\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
++ fprintf (stderr, "Unsupported query: %s\n", query_string);\r
++ continue;\r
++ }\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
+ \r
+- message_id = xstrndup (line + match[1].rm_so,\r
+- match[1].rm_eo - match[1].rm_so);\r
+- file_tags = xstrndup (line + match[2].rm_so,\r
+- match[2].rm_eo - match[2].rm_so);\r
++ if (ret > 0)\r
++ continue;\r
+ \r
+- tag_message (notmuch, message_id, file_tags, ! accumulate,\r
+- synchronize_flags);\r
++ if (ret < 0 || tag_message (ctx, notmuch, query_string,\r
++ tag_ops, flags))\r
++ break;\r
+ \r
+- free (message_id);\r
+- free (file_tags);\r
+- }\r
++ } while ((line_len = getline (&line, &line_size, input)) != -1);\r
+ \r
+- regfree (®ex);\r
++ if (input_format == DUMP_FORMAT_SUP)\r
++ regfree (®ex);\r
+ \r
+ if (line)\r
+ free (line);\r
+ \r
+ notmuch_database_destroy (notmuch);\r
++\r
+ if (input != stdin)\r
+ fclose (input);\r
+ \r
+- return 0;\r
++ return ret;\r
+ }\r
+-- \r
+1.7.10.4\r
+\r