[PATCH v4 00/12] insert command
authorPeter Wang <novalazy@gmail.com>
Thu, 24 Jan 2013 12:07:56 +0000 (23:07 +1100)
committerW. Trevor King <wking@tremily.us>
Fri, 7 Nov 2014 17:53:21 +0000 (09:53 -0800)
d2/86c5b2e4a8da72519c085f75ed10f2cb41c70e [new file with mode: 0644]

diff --git a/d2/86c5b2e4a8da72519c085f75ed10f2cb41c70e b/d2/86c5b2e4a8da72519c085f75ed10f2cb41c70e
new file mode 100644 (file)
index 0000000..0e5e791
--- /dev/null
@@ -0,0 +1,424 @@
+Return-Path: <novalazy@gmail.com>\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 25FF0431FAF\r
+       for <notmuch@notmuchmail.org>; Thu, 24 Jan 2013 04:09:18 -0800 (PST)\r
+X-Virus-Scanned: Debian amavisd-new at olra.theworths.org\r
+X-Spam-Flag: NO\r
+X-Spam-Score: -0.799\r
+X-Spam-Level: \r
+X-Spam-Status: No, score=-0.799 tagged_above=-999 required=5\r
+       tests=[DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1,\r
+       FREEMAIL_FROM=0.001, RCVD_IN_DNSWL_LOW=-0.7] 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 TsCRlnDgBiCk for <notmuch@notmuchmail.org>;\r
+       Thu, 24 Jan 2013 04:09:16 -0800 (PST)\r
+Received: from mail-pb0-f43.google.com (mail-pb0-f43.google.com\r
+       [209.85.160.43]) (using TLSv1 with cipher RC4-SHA (128/128 bits))\r
+       (No client certificate requested)\r
+       by olra.theworths.org (Postfix) with ESMTPS id 06190431FAE\r
+       for <notmuch@notmuchmail.org>; Thu, 24 Jan 2013 04:09:15 -0800 (PST)\r
+Received: by mail-pb0-f43.google.com with SMTP id jt11so3997106pbb.2\r
+       for <notmuch@notmuchmail.org>; Thu, 24 Jan 2013 04:09:15 -0800 (PST)\r
+DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20120113;\r
+       h=x-received:from:to:cc:subject:date:message-id:x-mailer;\r
+       bh=pq+bVJLMrvoa1umMAPToGStcuLLinxwPlewyyNvRcZg=;\r
+       b=J9QAYybUCsclcIpETQaEK/WLjlVFqVnIkwRd8263Wsu2gNNJ/FpMpAC6os4fCiUwoo\r
+       5mzxgrqvFD06ExhIQ8+BPQFTdPhPJY5Zm/n4BGIXuvsdAQEwDHci0N46octc3iUq1qi9\r
+       FEL1LT/3htG7XNRsdIzcuzaXZ1Z8lEKGR/SNY5BWm+SM1ROkRi5jELa1zIYjrSeHZmCh\r
+       soU2/kKhGEpw8lWsGgtwhyVUGbg1X6Mseb9/dBswlF5sVcHSK56rEiQTEQ782qKnKIxC\r
+       LKuF49SqmLaXdFde7IyCHvFhYIz1sHf7NU5cdzBNxhrpI8dImr8RvzGoBfg5+eGXZPtg\r
+       MDzg==\r
+X-Received: by 10.68.223.35 with SMTP id qr3mr4330939pbc.27.1359029354173;\r
+       Thu, 24 Jan 2013 04:09:14 -0800 (PST)\r
+Received: from localhost (215.42.233.220.static.exetel.com.au.\r
+       [220.233.42.215])\r
+       by mx.google.com with ESMTPS id bj9sm2746834pab.22.2013.01.24.04.09.11\r
+       (version=TLSv1.2 cipher=RC4-SHA bits=128/128);\r
+       Thu, 24 Jan 2013 04:09:13 -0800 (PST)\r
+From: Peter Wang <novalazy@gmail.com>\r
+To: notmuch@notmuchmail.org\r
+Subject: [PATCH v4 00/12] insert command\r
+Date: Thu, 24 Jan 2013 23:07:56 +1100\r
+Message-Id: <1359029288-12132-1-git-send-email-novalazy@gmail.com>\r
+X-Mailer: git-send-email 1.7.12.1\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: Thu, 24 Jan 2013 12:09:18 -0000\r
+\r
+Differences from v3:\r
+\r
+ - squashed patches; take it up with Jani\r
+ - address some review comments (interdiff follows)\r
+ - some stylistic things I left for someone who cares\r
+   (either I tried it and didn't like it, or disagree with the premise)\r
+ - split doc and test patches so series can be partially applied\r
+   without --folder or --create-folder options\r
+\r
+Peter Wang (12):\r
+  tag-util: move out 'tag' command-line checks\r
+  tag-util: do not reset list in parse_tag_command_line\r
+  cli: add insert command\r
+  man: document 'insert' command\r
+  man: reference notmuch-insert.1\r
+  test: add tests for insert\r
+  insert: add --folder option\r
+  man: document insert --folder option\r
+  test: test insert --folder option\r
+  insert: add --create-folder option\r
+  man: document insert --create-folder\r
+  test: test insert --create-folder option\r
+\r
+ Makefile.local                  |   1 +\r
+ man/Makefile.local              |   1 +\r
+ man/man1/notmuch-config.1       |   4 +-\r
+ man/man1/notmuch-count.1        |   4 +-\r
+ man/man1/notmuch-dump.1         |   4 +-\r
+ man/man1/notmuch-insert.1       |  63 ++++++\r
+ man/man1/notmuch-new.1          |   4 +-\r
+ man/man1/notmuch-reply.1        |   3 +-\r
+ man/man1/notmuch-restore.1      |   3 +-\r
+ man/man1/notmuch-search.1       |   3 +-\r
+ man/man1/notmuch-show.1         |   3 +-\r
+ man/man1/notmuch-tag.1          |   3 +-\r
+ man/man1/notmuch.1              |   3 +-\r
+ man/man5/notmuch-hooks.5        |   4 +-\r
+ man/man7/notmuch-search-terms.7 |   3 +-\r
+ notmuch-client.h                |   3 +\r
+ notmuch-insert.c                | 484 ++++++++++++++++++++++++++++++++++++++++\r
+ notmuch-tag.c                   |  10 +\r
+ notmuch.c                       |   3 +\r
+ tag-util.c                      |  13 +-\r
+ tag-util.h                      |   2 +\r
+ test/insert                     | 110 +++++++++\r
+ test/notmuch-test               |   1 +\r
+ 23 files changed, 705 insertions(+), 27 deletions(-)\r
+ create mode 100644 man/man1/notmuch-insert.1\r
+ create mode 100644 notmuch-insert.c\r
+ create mode 100755 test/insert\r
+\r
+-- \r
+1.7.12.1\r
+\r
+\r
+diff --git a/man/man1/notmuch-insert.1 b/man/man1/notmuch-insert.1\r
+index 4a7cbeb..8ce634e 100644\r
+--- a/man/man1/notmuch-insert.1\r
++++ b/man/man1/notmuch-insert.1\r
+@@ -24,6 +24,10 @@ configuration option, then by operations specified on the command-line:\r
+ tags prefixed by '+' are added while\r
+ those prefixed by '\-' are removed.\r
\r
++If the new message is a duplicate of an existing message in the database\r
++(it has same Message-ID), it will be added to the maildir folder and\r
++notmuch database, but the tags will not be changed.\r
++\r
+ Option arguments must appear before any tag operation arguments.\r
+ Supported options for\r
+ .B insert\r
+diff --git a/notmuch-insert.c b/notmuch-insert.c\r
+index 6b3e380..69329ad 100644\r
+--- a/notmuch-insert.c\r
++++ b/notmuch-insert.c\r
+@@ -44,33 +44,22 @@ handle_sigint (unused (int sig))\r
+ }\r
\r
+ /* Like gethostname but guarantees that a null-terminated hostname is\r
+- * returned, even if it has to make one up.\r
+- * Returns true unless hostname contains a slash. */\r
+-static notmuch_bool_t\r
++ * returned, even if it has to make one up. Invalid characters are\r
++ * substituted such that the hostname can be used within a filename.\r
++ */\r
++static void\r
+ safe_gethostname (char *hostname, size_t len)\r
+ {\r
++    char *p;\r
++\r
+     if (gethostname (hostname, len) == -1) {\r
+       strncpy (hostname, "unknown", len);\r
+     }\r
+     hostname[len - 1] = '\0';\r
\r
+-    return (strchr (hostname, '/') == NULL);\r
+-}\r
+-\r
+-/* Check the specified folder name does not contain a directory\r
+- * component ".." to prevent writes outside of the Maildir hierarchy. */\r
+-static notmuch_bool_t\r
+-check_folder_name (const char *folder)\r
+-{\r
+-    const char *p = folder;\r
+-\r
+-    for (;;) {\r
+-      if ((p[0] == '.') && (p[1] == '.') && (p[2] == '\0' || p[2] == '/'))\r
+-          return FALSE;\r
+-      p = strchr (p, '/');\r
+-      if (!p)\r
+-          return TRUE;\r
+-      p++;\r
++    for (p = hostname; *p != '\0'; p++) {\r
++      if (*p == '/' || *p == ':')\r
++          *p = '_';\r
+     }\r
+ }\r
\r
+@@ -96,6 +85,23 @@ sync_dir (const char *dir)\r
+     return ret;\r
+ }\r
\r
++/* Check the specified folder name does not contain a directory\r
++ * component ".." to prevent writes outside of the Maildir hierarchy. */\r
++static notmuch_bool_t\r
++check_folder_name (const char *folder)\r
++{\r
++    const char *p = folder;\r
++\r
++    for (;;) {\r
++      if ((p[0] == '.') && (p[1] == '.') && (p[2] == '\0' || p[2] == '/'))\r
++          return FALSE;\r
++      p = strchr (p, '/');\r
++      if (!p)\r
++          return TRUE;\r
++      p++;\r
++    }\r
++}\r
++\r
+ /* Make the given directory, succeeding if it already exists. */\r
+ static notmuch_bool_t\r
+ make_directory (char *path, int mode)\r
+@@ -206,10 +212,7 @@ maildir_open_tmp_file (void *ctx, const char *dir,\r
\r
+     /* We follow the Dovecot file name generation algorithm. */\r
+     pid = getpid ();\r
+-    if (! safe_gethostname (hostname, sizeof (hostname))) {\r
+-      fprintf (stderr, "Error: invalid host name.\n");\r
+-      return -1;\r
+-    }\r
++    safe_gethostname (hostname, sizeof (hostname));\r
+     do {\r
+       gettimeofday (&tv, NULL);\r
+       filename = talloc_asprintf (ctx, "%ld.M%ldP%d.%s",\r
+@@ -247,26 +250,6 @@ maildir_open_tmp_file (void *ctx, const char *dir,\r
+     return fd;\r
+ }\r
\r
+-/* Atomically move the new message file from the Maildir 'tmp' directory\r
+- * to the 'new' directory.\r
+- *\r
+- * We follow the Dovecot recommendation to simply use rename()\r
+- * instead of link() and unlink().  See also:\r
+- * http://wiki.dovecot.org/MailboxFormat/Maildir#Mail_delivery\r
+- */\r
+-static notmuch_bool_t\r
+-maildir_move_tmp_to_new (const char *tmppath, const char *newpath,\r
+-                       const char *newdir)\r
+-{\r
+-    if (rename (tmppath, newpath) != 0) {\r
+-      fprintf (stderr, "Error: rename() failed: %s\n", strerror (errno));\r
+-      return FALSE;\r
+-    }\r
+-\r
+-    /* Sync the 'new' directory after rename for durability. */\r
+-    return sync_dir (newdir);\r
+-}\r
+-\r
+ /* Copy the contents of standard input (fdin) into fdout. */\r
+ static notmuch_bool_t\r
+ copy_stdin (int fdin, int fdout)\r
+@@ -291,11 +274,9 @@ copy_stdin (int fdin, int fdout)\r
+       p = buf;\r
+       do {\r
+           written = write (fdout, p, remain);\r
+-          if (written == 0)\r
+-              return FALSE;\r
+-          if (written < 0) {\r
+-              if (errno == EINTR)\r
+-                  continue;\r
++          if (written < 0 && errno == EINTR)\r
++              continue;\r
++          if (written <= 0) {\r
+               fprintf (stderr, "Error: writing to temporary file: %s",\r
+                        strerror (errno));\r
+               return FALSE;\r
+@@ -320,9 +301,7 @@ add_file_to_database (notmuch_database_t *notmuch, const char *path,\r
+     status = notmuch_database_add_message (notmuch, path, &message);\r
+     switch (status) {\r
+     case NOTMUCH_STATUS_SUCCESS:\r
+-      break;\r
+     case NOTMUCH_STATUS_DUPLICATE_MESSAGE_ID:\r
+-      fprintf (stderr, "Warning: duplicate message.\n");\r
+       break;\r
+     default:\r
+     case NOTMUCH_STATUS_FILE_NOT_EMAIL:\r
+@@ -340,11 +319,18 @@ add_file_to_database (notmuch_database_t *notmuch, const char *path,\r
+       return FALSE;\r
+     }\r
\r
+-    tag_op_list_apply (message, tag_ops, TAG_FLAG_MAILDIR_SYNC);\r
++    if (status == NOTMUCH_STATUS_DUPLICATE_MESSAGE_ID) {\r
++      /* Don't change tags of an existing message. */\r
++      status = notmuch_message_tags_to_maildir_flags (message);\r
++      if (status != NOTMUCH_STATUS_SUCCESS)\r
++          fprintf (stderr, "Error: failed to sync tags to maildir flags\n");\r
++    } else {\r
++      status = tag_op_list_apply (message, tag_ops, TAG_FLAG_MAILDIR_SYNC);\r
++    }\r
\r
+     notmuch_message_destroy (message);\r
\r
+-    return TRUE;\r
++    return (status == NOTMUCH_STATUS_SUCCESS) ? TRUE : FALSE;\r
+ }\r
\r
+ static notmuch_bool_t\r
+@@ -355,29 +341,45 @@ insert_message (void *ctx, notmuch_database_t *notmuch, int fdin,\r
+     char *newpath;\r
+     char *newdir;\r
+     int fdout;\r
+-    notmuch_bool_t ret;\r
\r
+     fdout = maildir_open_tmp_file (ctx, dir, &tmppath, &newpath, &newdir);\r
+     if (fdout < 0) {\r
+       return FALSE;\r
+     }\r
+-    ret = copy_stdin (fdin, fdout);\r
+-    if (ret && fsync (fdout) != 0) {\r
++\r
++    if (! copy_stdin (fdin, fdout)) {\r
++      close (fdout);\r
++      unlink (tmppath);\r
++      return FALSE;\r
++    }\r
++\r
++    if (fsync (fdout) != 0) {\r
+       fprintf (stderr, "Error: fsync failed: %s\n", strerror (errno));\r
+-      ret = FALSE;\r
++      close (fdout);\r
++      unlink (tmppath);\r
++      return FALSE;\r
+     }\r
++\r
+     close (fdout);\r
+-    if (ret) {\r
+-      ret = maildir_move_tmp_to_new (tmppath, newpath, newdir);\r
+-    }\r
+-    if (!ret) {\r
++\r
++    /* Atomically move the new message file from the Maildir 'tmp' directory\r
++     * to the 'new' directory.  We follow the Dovecot recommendation to\r
++     * simply use rename() instead of link() and unlink().\r
++     * See also: http://wiki.dovecot.org/MailboxFormat/Maildir#Mail_delivery\r
++     */\r
++    if (rename (tmppath, newpath) != 0) {\r
++      fprintf (stderr, "Error: rename() failed: %s\n", strerror (errno));\r
+       unlink (tmppath);\r
+       return FALSE;\r
+     }\r
\r
+-    ret = add_file_to_database (notmuch, newpath, tag_ops);\r
+-    if (!ret) {\r
+-      /* XXX maybe there should be an option to keep the file in maildir? */\r
++    if (! add_file_to_database (notmuch, newpath, tag_ops)) {\r
++      /* XXX add an option to keep the file in maildir? */\r
++      unlink (newpath);\r
++      return FALSE;\r
++    }\r
++\r
++    if (! sync_dir (newdir)) {\r
+       unlink (newpath);\r
+       return FALSE;\r
+     }\r
+@@ -398,7 +400,7 @@ notmuch_insert_command (void *ctx, int argc, char *argv[])\r
+     char *query_string = NULL;\r
+     const char *folder = NULL;\r
+     notmuch_bool_t create_folder = FALSE;\r
+-    char *maildir;\r
++    const char *maildir;\r
+     int opt_index;\r
+     unsigned int i;\r
+     notmuch_bool_t ret;\r
+@@ -443,23 +445,23 @@ notmuch_insert_command (void *ctx, int argc, char *argv[])\r
+       return 1;\r
+     }\r
\r
+-    if (folder != NULL) {\r
++    if (folder == NULL) {\r
++      maildir = db_path;\r
++    } else {\r
+       if (! check_folder_name (folder)) {\r
+           fprintf (stderr, "Error: bad folder name: %s\n", folder);\r
+           return 1;\r
+       }\r
+       maildir = talloc_asprintf (ctx, "%s/%s", db_path, folder);\r
+-    } else {\r
+-      maildir = talloc_asprintf (ctx, "%s", db_path);\r
+-    }\r
+-    if (! maildir) {\r
+-      fprintf (stderr, "Out of memory\n");\r
+-      return 1;\r
+-    }\r
+-    if (create_folder && ! maildir_create_folder (ctx, maildir)) {\r
+-      fprintf (stderr, "Error: creating maildir %s: %s\n",\r
+-               maildir, strerror (errno));\r
+-      return 1;\r
++      if (! maildir) {\r
++          fprintf (stderr, "Out of memory\n");\r
++          return 1;\r
++      }\r
++      if (create_folder && ! maildir_create_folder (ctx, maildir)) {\r
++          fprintf (stderr, "Error: creating maildir %s: %s\n",\r
++                   maildir, strerror (errno));\r
++          return 1;\r
++      }\r
+     }\r
\r
+     /* Setup our handler for SIGINT. We do not set SA_RESTART so that copying\r
+diff --git a/tag-util.c b/tag-util.c\r
+index 41f2c09..b57ee32 100644\r
+--- a/tag-util.c\r
++++ b/tag-util.c\r
+@@ -188,6 +188,11 @@ parse_tag_command_line (void *ctx, int argc, char **argv,\r
\r
+     *query_str = query_string_from_args (ctx, argc - i, &argv[i]);\r
\r
++    if (*query_str == NULL) {\r
++       fprintf (stderr, "Out of memory.\n");\r
++       return TAG_PARSE_OUT_OF_MEMORY;\r
++    }\r
++\r
+     return TAG_PARSE_SUCCESS;\r
+ }\r
\r
+diff --git a/test/insert b/test/insert\r
+index a3b6283..24a61e1 100755\r
+--- a/test/insert\r
++++ b/test/insert\r
+@@ -46,10 +46,14 @@ expected='[[[{\r
+ test_expect_equal_json "$output" "$expected"\r
\r
+ test_begin_subtest "Insert message, duplicate message"\r
+-notmuch insert < "$gen_msg_filename"\r
++notmuch insert +duptag -unread < "$gen_msg_filename"\r
+ output=$(notmuch search --output=files "subject:insert-subject" | wc -l)\r
+ test_expect_equal "$output" 2\r
\r
++test_begin_subtest "Insert message, duplicate message does not change tags"\r
++output=$(notmuch search --format=json --output=tags "subject:insert-subject")\r
++test_expect_equal_json "$output" '["inbox", "unread"]'\r
++\r
+ test_begin_subtest "Insert message, add tag"\r
+ gen_insert_msg\r
+ notmuch insert +custom < "$gen_msg_filename"\r