--- /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 EA22A6DE0173\r
+ for <notmuch@notmuchmail.org>; Sun, 12 Jun 2016 18:06:16 -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 S2iLMys9U66d for <notmuch@notmuchmail.org>;\r
+ Sun, 12 Jun 2016 18:06:08 -0700 (PDT)\r
+Received: from fethera.tethera.net (fethera.tethera.net [198.245.60.197])\r
+ by arlo.cworth.org (Postfix) with ESMTPS id C46EA6DE0159\r
+ for <notmuch@notmuchmail.org>; Sun, 12 Jun 2016 18:06:08 -0700 (PDT)\r
+Received: from remotemail by fethera.tethera.net with local (Exim 4.84)\r
+ (envelope-from <bremner@tethera.net>)\r
+ id 1bCGK9-0003wa-7e; Sun, 12 Jun 2016 21:05:53 -0400\r
+Received: (nullmailer pid 5680 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 7/8] CLI: add properties to dump output\r
+Date: Sun, 12 Jun 2016 22:05:54 -0300\r
+Message-Id: <1465779955-5539-8-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
+MIME-Version: 1.0\r
+Content-Type: text/plain; charset=UTF-8\r
+Content-Transfer-Encoding: 8bit\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:17 -0000\r
+\r
+Part of providing extensibility via properties is to make sure that user\r
+data is not lost. Thus we need to be able to dump and restore\r
+properties.\r
+---\r
+ doc/man1/notmuch-dump.rst | 18 ++++++---\r
+ notmuch-client.h | 3 ++\r
+ notmuch-dump.c | 85 +++++++++++++++++++++++++++++++++++++++----\r
+ notmuch-new.c | 2 +-\r
+ test/T610-message-property.sh | 21 +++++++++++\r
+ 5 files changed, 116 insertions(+), 13 deletions(-)\r
+\r
+diff --git a/doc/man1/notmuch-dump.rst b/doc/man1/notmuch-dump.rst\r
+index 94986a8..751850b 100644\r
+--- a/doc/man1/notmuch-dump.rst\r
++++ b/doc/man1/notmuch-dump.rst\r
+@@ -71,7 +71,7 @@ Supported options for **dump** include\r
+ characters. Note also that tags with spaces will not be\r
+ correctly restored with this format.\r
+ \r
+- ``--include=(config|tags)``\r
++ ``--include=(config|properties|tags)``\r
+ \r
+ Control what kind of metadata is included in the output.\r
+ \r
+@@ -81,14 +81,22 @@ Supported options for **dump** include\r
+ starts with "#@ ", followed by a space seperated key-value\r
+ pair. 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 hex\r
++ encoded if needed.\r
++\r
+ **tags**\r
+ \r
+- Output per-message metadata, namely tags. See *format* above\r
++ Output per-message boolean metadata, namely tags. See *format* above\r
+ for description of the output.\r
+ \r
+- The default is to include both tags and configuration\r
+- information. As of version 2 of the dump format, there is a\r
+- header line of the following form\r
++ The default is to include all available types of data. The\r
++ option can be specified multiple times to select some subset. As\r
++ of version 2 of the dump format, there is a header line of the\r
++ following form\r
+ \r
+ |\r
+ | #notmuch-dump <*format*>:<*version*> <*included*>\r
+diff --git a/notmuch-client.h b/notmuch-client.h\r
+index ebc092b..9ce2aef 100644\r
+--- a/notmuch-client.h\r
++++ b/notmuch-client.h\r
+@@ -449,8 +449,11 @@ typedef enum dump_formats {\r
+ typedef enum dump_includes {\r
+ DUMP_INCLUDE_TAGS = 1,\r
+ DUMP_INCLUDE_CONFIG = 2,\r
++ DUMP_INCLUDE_PROPERTIES = 4\r
+ } dump_include_t;\r
+ \r
++#define DUMP_INCLUDE_DEFAULT (DUMP_INCLUDE_TAGS | DUMP_INCLUDE_CONFIG | DUMP_INCLUDE_PROPERTIES)\r
++\r
+ #define NOTMUCH_DUMP_VERSION 2\r
+ \r
+ int\r
+diff --git a/notmuch-dump.c b/notmuch-dump.c\r
+index d80ed8b8..ec82660 100644\r
+--- a/notmuch-dump.c\r
++++ b/notmuch-dump.c\r
+@@ -69,12 +69,77 @@ database_dump_config (notmuch_database_t *notmuch, gzFile output)\r
+ static void\r
+ print_dump_header (gzFile output, int output_format, int include)\r
+ {\r
+- gzprintf (output, "#notmuch-dump %s:%d %s%s%s\n",\r
++ const char *sep = "";\r
++\r
++ gzprintf (output, "#notmuch-dump %s:%d ",\r
+ (output_format == DUMP_FORMAT_SUP) ? "sup" : "batch-tag",\r
+- NOTMUCH_DUMP_VERSION,\r
+- (include & DUMP_INCLUDE_CONFIG) ? "config" : "",\r
+- (include & DUMP_INCLUDE_TAGS) && (include & DUMP_INCLUDE_CONFIG) ? "," : "",\r
+- (include & DUMP_INCLUDE_TAGS) ? "tags" : "");\r
++ NOTMUCH_DUMP_VERSION);\r
++\r
++ if (include & DUMP_INCLUDE_CONFIG) {\r
++ gzputs (output, "config");\r
++ sep = ",";\r
++ }\r
++ if (include & DUMP_INCLUDE_PROPERTIES) {\r
++ gzprintf (output, "%sproperties",sep);\r
++ sep = ",";\r
++ }\r
++ if (include & DUMP_INCLUDE_TAGS) {\r
++ gzprintf (output, "%sproperties",sep);\r
++ }\r
++ gzputs (output, "\n");\r
++}\r
++\r
++static int\r
++dump_properties_message (void *ctx,\r
++ notmuch_message_t *message,\r
++ gzFile output,\r
++ char **buffer_p, size_t *size_p)\r
++{\r
++ const char *message_id;\r
++ notmuch_message_properties_t *list;\r
++ notmuch_bool_t first = TRUE;\r
++\r
++ message_id = notmuch_message_get_message_id (message);\r
++\r
++ if (strchr (message_id, '\n')) {\r
++ fprintf (stderr, "Warning: skipping message id containing line break: \"%s\"\n", message_id);\r
++ return 0;\r
++ }\r
++\r
++ for (list = notmuch_message_get_properties (message, "", FALSE);\r
++ notmuch_message_properties_valid (list); notmuch_message_properties_move_to_next (list)) {\r
++ const char *key, *val;\r
++\r
++ if (first) {\r
++ if (hex_encode (ctx, message_id, buffer_p, size_p) != HEX_SUCCESS) {\r
++ fprintf (stderr, "Error: failed to hex-encode message-id %s\n", message_id);\r
++ return 1;\r
++ }\r
++ gzprintf (output, "#= %s", *buffer_p);\r
++ first = FALSE;\r
++ }\r
++\r
++ key = notmuch_message_properties_key (list);\r
++ val = notmuch_message_properties_value (list);\r
++\r
++ if (hex_encode (ctx, key, buffer_p, size_p) != HEX_SUCCESS) {\r
++ fprintf (stderr, "Error: failed to hex-encode key %s\n", key);\r
++ return 1;\r
++ }\r
++ gzprintf (output, " %s", *buffer_p);\r
++\r
++ if (hex_encode (ctx, val, buffer_p, size_p) != HEX_SUCCESS) {\r
++ fprintf (stderr, "Error: failed to hex-encode value %s\n", val);\r
++ return 1;\r
++ }\r
++ gzprintf (output, "=%s", *buffer_p);\r
++}\r
++ notmuch_message_properties_destroy (list);\r
++\r
++ if (!first)\r
++ gzprintf (output, "\n", *buffer_p);\r
++\r
++ return 0;\r
+ }\r
+ \r
+ static int\r
+@@ -159,7 +224,7 @@ database_dump_file (notmuch_database_t *notmuch, gzFile output,\r
+ return EXIT_FAILURE;\r
+ }\r
+ \r
+- if (! (include & DUMP_INCLUDE_TAGS))\r
++ if (! (include & (DUMP_INCLUDE_TAGS | DUMP_INCLUDE_PROPERTIES)))\r
+ return EXIT_SUCCESS;\r
+ \r
+ if (! query_str)\r
+@@ -189,6 +254,11 @@ database_dump_file (notmuch_database_t *notmuch, gzFile output,\r
+ &buffer, &buffer_size))\r
+ return EXIT_FAILURE;\r
+ \r
++ if ((include & DUMP_INCLUDE_PROPERTIES) &&\r
++ dump_properties_message (notmuch, message, output,\r
++ &buffer, &buffer_size))\r
++ return EXIT_FAILURE;\r
++\r
+ notmuch_message_destroy (message);\r
+ }\r
+ \r
+@@ -312,6 +382,7 @@ notmuch_dump_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
+ { NOTMUCH_OPT_STRING, &output_file_name, "output", 'o', 0 },\r
+ { NOTMUCH_OPT_BOOLEAN, &gzip_output, "gzip", 'z', 0 },\r
+@@ -326,7 +397,7 @@ notmuch_dump_command (notmuch_config_t *config, int argc, char *argv[])\r
+ notmuch_process_shared_options (argv[0]);\r
+ \r
+ if (include == 0)\r
+- include = DUMP_INCLUDE_CONFIG | DUMP_INCLUDE_TAGS;\r
++ include = DUMP_INCLUDE_CONFIG | DUMP_INCLUDE_TAGS | DUMP_INCLUDE_PROPERTIES;\r
+ \r
+ if (opt_index < argc) {\r
+ query_str = query_string_from_args (notmuch, argc - opt_index, argv + opt_index);\r
+diff --git a/notmuch-new.c b/notmuch-new.c\r
+index 799fec2..c55dea7 100644\r
+--- a/notmuch-new.c\r
++++ b/notmuch-new.c\r
+@@ -1042,7 +1042,7 @@ notmuch_new_command (notmuch_config_t *config, int argc, char *argv[])\r
+ }\r
+ \r
+ if (notmuch_database_dump (notmuch, backup_name, "",\r
+- DUMP_FORMAT_BATCH_TAG, DUMP_INCLUDE_CONFIG | DUMP_INCLUDE_TAGS, TRUE)) {\r
++ DUMP_FORMAT_BATCH_TAG, DUMP_INCLUDE_DEFAULT, TRUE)) {\r
+ fprintf (stderr, "Backup failed. Aborting upgrade.");\r
+ return EXIT_FAILURE;\r
+ }\r
+diff --git a/test/T610-message-property.sh b/test/T610-message-property.sh\r
+index b5ddb7a..e594979 100755\r
+--- a/test/T610-message-property.sh\r
++++ b/test/T610-message-property.sh\r
+@@ -89,6 +89,17 @@ testkey2 = NULL\r
+ EOF\r
+ test_expect_equal_file EXPECTED OUTPUT\r
+ \r
++test_begin_subtest "notmuch_message_remove_all_properties"\r
++cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR}\r
++EXPECT0(notmuch_message_remove_all_properties (message));\r
++print_properties (message, "", FALSE);\r
++EOF\r
++cat <<'EOF' >EXPECTED\r
++== stdout ==\r
++== stderr ==\r
++EOF\r
++test_expect_equal_file EXPECTED OUTPUT\r
++\r
+ test_begin_subtest "notmuch_message_get_properties: empty list"\r
+ cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR}\r
+ {\r
+@@ -188,4 +199,14 @@ cat <<'EOF' >EXPECTED\r
+ EOF\r
+ test_expect_equal_file EXPECTED OUTPUT\r
+ \r
++test_begin_subtest "dump message properties"\r
++cat <<EOF > PROPERTIES\r
++#= 4EFC743A.3060609@april.org fancy%20key%20with%20%c3%a1cc%c3%a8nts=import%20value%20with%20= testkey1=alice testkey1=bob testkey1=testvalue1 testkey1=testvalue2 testkey3=alice3 testkey3=bob3 testkey3=testvalue3\r
++EOF\r
++cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR}\r
++EXPECT0(notmuch_message_add_property (message, "fancy key with áccènts", "import value with ="));\r
++EOF\r
++notmuch dump | grep '^#=' > OUTPUT\r
++test_expect_equal_file PROPERTIES OUTPUT\r
++\r
+ test_done\r
+-- \r
+2.8.1\r
+\r