--- /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 arlo.cworth.org (Postfix) with ESMTP id CE3D16DE0946\r
+ for <notmuch@notmuchmail.org>; Sun, 12 Jun 2016 18:06:47 -0700 (PDT)\r
+X-Virus-Scanned: Debian amavisd-new at cworth.org\r
+X-Spam-Flag: NO\r
+X-Spam-Score: -0.011\r
+X-Spam-Level: \r
+X-Spam-Status: No, score=-0.011 tagged_above=-999 required=5\r
+ tests=[AWL=-0.000, SPF_PASS=-0.001, T_RP_MATCHES_RCVD=-0.01]\r
+ autolearn=disabled\r
+Received: from arlo.cworth.org ([127.0.0.1])\r
+ by localhost (arlo.cworth.org [127.0.0.1]) (amavisd-new, port 10024)\r
+ with ESMTP id LPkA8tm9Q8yb for <notmuch@notmuchmail.org>;\r
+ Sun, 12 Jun 2016 18:06:40 -0700 (PDT)\r
+Received: from fethera.tethera.net (fethera.tethera.net [198.245.60.197])\r
+ by arlo.cworth.org (Postfix) with ESMTPS id 9E8B16DE02DB\r
+ for <notmuch@notmuchmail.org>; Sun, 12 Jun 2016 18:06:14 -0700 (PDT)\r
+Received: from remotemail by fethera.tethera.net with local (Exim 4.84)\r
+ (envelope-from <bremner@tethera.net>)\r
+ id 1bCGKG-0003xs-H7; Sun, 12 Jun 2016 21:06:00 -0400\r
+Received: (nullmailer pid 5682 invoked by uid 1000);\r
+ Mon, 13 Jun 2016 01:06:04 -0000\r
+From: David Bremner <david@tethera.net>\r
+To: notmuch@notmuchmail.org\r
+Subject: [PATCH 8/8] cli: optionally restore message properties from dump file\r
+Date: Sun, 12 Jun 2016 22:05:55 -0300\r
+Message-Id: <1465779955-5539-9-git-send-email-david@tethera.net>\r
+X-Mailer: git-send-email 2.8.1\r
+In-Reply-To: <1465779955-5539-1-git-send-email-david@tethera.net>\r
+References: <1465779955-5539-1-git-send-email-david@tethera.net>\r
+X-BeenThere: notmuch@notmuchmail.org\r
+X-Mailman-Version: 2.1.20\r
+Precedence: list\r
+List-Id: "Use and development of the notmuch mail system."\r
+ <notmuch.notmuchmail.org>\r
+List-Unsubscribe: <https://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: <https://notmuchmail.org/mailman/listinfo/notmuch>,\r
+ <mailto:notmuch-request@notmuchmail.org?subject=subscribe>\r
+X-List-Received-Date: Mon, 13 Jun 2016 01:06:47 -0000\r
+\r
+This somewhat mimics the config line parsing, except there can be\r
+arbitrarily many key value pairs, so one more level of looping is\r
+required.\r
+---\r
+ doc/man1/notmuch-restore.rst | 13 +++++--\r
+ notmuch-restore.c | 83 +++++++++++++++++++++++++++++++++++++++++--\r
+ test/T610-message-property.sh | 28 +++++++++++++++\r
+ 3 files changed, 119 insertions(+), 5 deletions(-)\r
+\r
+diff --git a/doc/man1/notmuch-restore.rst b/doc/man1/notmuch-restore.rst\r
+index 87fa22e..a0c1b3c 100644\r
+--- a/doc/man1/notmuch-restore.rst\r
++++ b/doc/man1/notmuch-restore.rst\r
+@@ -50,7 +50,7 @@ Supported options for **restore** include\r
+ format, this heuristic, based the fact that batch-tag format\r
+ contains no parentheses, should be accurate.\r
+ \r
+- ``--include=(config|tags)``\r
++ ``--include=(config|properties|tags)``\r
+ \r
+ Control what kind of metadata is restored.\r
+ \r
+@@ -60,13 +60,20 @@ Supported options for **restore** include\r
+ with "#@ ", followed by a space seperated key-value pair.\r
+ Both key and value are hex encoded if needed.\r
+ \r
++ **properties**\r
++\r
++ Output per-message (key,value) metadata. Each line starts\r
++ with "#= ", followed by a message id, and a space seperated\r
++ list of key=value pairs. pair. Ids, keys and values are\r
++ hex encoded if needed.\r
++\r
+ **tags**\r
+ \r
+ Output per-message metadata, namely tags. See *format* above\r
+ for more details.\r
+ \r
+- The default is to restore both tags and configuration\r
+- information\r
++ The default is to restore all available types of data. The\r
++ option can be specified multiple times to select some subset.\r
+ \r
+ ``--input=``\ <filename>\r
+ Read input from given file instead of stdin.\r
+diff --git a/notmuch-restore.c b/notmuch-restore.c\r
+index 371237c..d2ada61 100644\r
+--- a/notmuch-restore.c\r
++++ b/notmuch-restore.c\r
+@@ -57,6 +57,72 @@ process_config_line (notmuch_database_t *notmuch, const char* line)\r
+ return ret;\r
+ }\r
+ \r
++static int\r
++process_properties_line (notmuch_database_t *notmuch, const char* line)\r
++\r
++{\r
++ const char *id_p, *tok;\r
++ size_t id_len = 0, tok_len = 0;\r
++ char *id;\r
++\r
++ notmuch_message_t *message = NULL;\r
++ const char *delim = " \t\n";\r
++ int ret = EXIT_FAILURE;\r
++\r
++ void *local = talloc_new(NULL);\r
++\r
++ id_p = strtok_len_c (line, delim, &id_len);\r
++ id = talloc_strndup (local, id_p, id_len);\r
++ if (hex_decode_inplace (id) != HEX_SUCCESS) {\r
++ fprintf (stderr, "hex decoding failure on line %s\n", line);\r
++ goto DONE;\r
++ }\r
++\r
++ if (print_status_database ("notmuch restore", notmuch,\r
++ notmuch_database_find_message (notmuch, id, &message)))\r
++ goto DONE;\r
++\r
++ if (print_status_database ("notmuch restore", notmuch,\r
++ notmuch_message_remove_all_properties (message)))\r
++ goto DONE;\r
++\r
++ tok = id_p + id_len;\r
++\r
++ while ((tok = strtok_len_c (tok + tok_len, delim, &tok_len)) != NULL) {\r
++ char *key, *value;\r
++ size_t off = strcspn (tok, "=");\r
++ if (off > tok_len) {\r
++ fprintf (stderr, "unparsable token %s\n", tok);\r
++ goto DONE;\r
++ }\r
++\r
++ key = talloc_strndup (local, tok, off);\r
++ value = talloc_strndup (local, tok+off+1, tok_len - off - 1);\r
++\r
++ if (hex_decode_inplace (key) != HEX_SUCCESS) {\r
++ fprintf (stderr, "hex decoding failure on key %s\n", key);\r
++ goto DONE;\r
++ }\r
++\r
++ if (hex_decode_inplace (value) != HEX_SUCCESS) {\r
++ fprintf (stderr, "hex decoding failure on value %s\n", value);\r
++ goto DONE;\r
++ }\r
++\r
++ if (print_status_database ("notmuch restore", notmuch,\r
++ notmuch_message_add_property (message, key, value)))\r
++ goto DONE;\r
++\r
++ }\r
++\r
++ ret = EXIT_SUCCESS;\r
++\r
++ DONE:\r
++ talloc_free (local);\r
++ return ret;\r
++}\r
++\r
++\r
+ static regex_t regex;\r
+ \r
+ /* Non-zero return indicates an error in retrieving the message,\r
+@@ -188,6 +254,7 @@ notmuch_restore_command (notmuch_config_t *config, int argc, char *argv[])\r
+ { 0, 0 } } },\r
+ { NOTMUCH_OPT_KEYWORD_FLAGS, &include, "include", 'I',\r
+ (notmuch_keyword_t []){ { "config", DUMP_INCLUDE_CONFIG },\r
++ { "properties", DUMP_INCLUDE_PROPERTIES },\r
+ { "tags", DUMP_INCLUDE_TAGS} } },\r
+ \r
+ { NOTMUCH_OPT_STRING, &input_file_name, "input", 'i', 0 },\r
+@@ -206,7 +273,7 @@ notmuch_restore_command (notmuch_config_t *config, int argc, char *argv[])\r
+ notmuch_exit_if_unmatched_db_uuid (notmuch);\r
+ \r
+ if (include == 0) {\r
+- include = DUMP_INCLUDE_CONFIG | DUMP_INCLUDE_TAGS;\r
++ include = DUMP_INCLUDE_CONFIG | DUMP_INCLUDE_PROPERTIES | DUMP_INCLUDE_TAGS;\r
+ }\r
+ \r
+ name_for_error = input_file_name ? input_file_name : "stdin";\r
+@@ -273,13 +340,18 @@ notmuch_restore_command (notmuch_config_t *config, int argc, char *argv[])\r
+ if (ret)\r
+ goto DONE;\r
+ }\r
++ if ((include & DUMP_INCLUDE_PROPERTIES) && line_len >= 2 && line[0] == '#' && line[1] == '=') {\r
++ ret = process_properties_line(notmuch, line+2);\r
++ if (ret)\r
++ goto DONE;\r
++ }\r
+ \r
+ } while ((line_len == 0) ||\r
+ (line[0] == '#') ||\r
+ /* the cast is safe because we checked about for line_len < 0 */\r
+ (strspn (line, " \t\n") == (unsigned)line_len));\r
+ \r
+- if (! (include & DUMP_INCLUDE_TAGS)) {\r
++ if (! ((include & DUMP_INCLUDE_TAGS) || (include & DUMP_INCLUDE_PROPERTIES))) {\r
+ ret = EXIT_SUCCESS;\r
+ goto DONE;\r
+ }\r
+@@ -306,6 +378,13 @@ notmuch_restore_command (notmuch_config_t *config, int argc, char *argv[])\r
+ talloc_free (line_ctx);\r
+ \r
+ line_ctx = talloc_new (config);\r
++\r
++ if ((include & DUMP_INCLUDE_PROPERTIES) && line_len >= 2 && line[0] == '#' && line[1] == '=') {\r
++ ret = process_properties_line(notmuch, line+2);\r
++ if (ret)\r
++ goto DONE;\r
++ }\r
++\r
+ if (input_format == DUMP_FORMAT_SUP) {\r
+ ret = parse_sup_line (line_ctx, line, &query_string, tag_ops);\r
+ } else {\r
+diff --git a/test/T610-message-property.sh b/test/T610-message-property.sh\r
+index e594979..8952eb7 100755\r
+--- a/test/T610-message-property.sh\r
++++ b/test/T610-message-property.sh\r
+@@ -209,4 +209,32 @@ EOF\r
+ notmuch dump | grep '^#=' > OUTPUT\r
+ test_expect_equal_file PROPERTIES OUTPUT\r
+ \r
++\r
++test_begin_subtest "restore missing message property (single line)"\r
++notmuch dump | grep '^#=' > BEFORE1\r
++cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR}\r
++EXPECT0(notmuch_message_remove_property (message, "testkey1", "bob"));\r
++EOF\r
++notmuch restore < BEFORE1\r
++notmuch dump | grep '^#=' > OUTPUT\r
++test_expect_equal_file PROPERTIES OUTPUT\r
++\r
++\r
++test_begin_subtest "restore missing message property (full dump)"\r
++notmuch dump > BEFORE2\r
++cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR}\r
++EXPECT0(notmuch_message_remove_property (message, "testkey1", "bob"));\r
++EOF\r
++notmuch restore < BEFORE2\r
++notmuch dump | grep '^#=' > OUTPUT\r
++test_expect_equal_file PROPERTIES OUTPUT\r
++\r
++test_begin_subtest "restore clear extra message property"\r
++cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR}\r
++EXPECT0(notmuch_message_add_property (message, "testkey1", "charles"));\r
++EOF\r
++notmuch restore < BEFORE2\r
++notmuch dump | grep '^#=' > OUTPUT\r
++test_expect_equal_file PROPERTIES OUTPUT\r
++\r
+ test_done\r
+-- \r
+2.8.1\r
+\r