Re: [PATCH 9/9] add has: query prefix to search for specific properties
[notmuch-archives.git] / e9 / 61bb8bba57e03aad7c8d0f5b05b435fe72500b
1 Return-Path: <dkg@fifthhorseman.net>\r
2 X-Original-To: notmuch@notmuchmail.org\r
3 Delivered-To: notmuch@notmuchmail.org\r
4 Received: from localhost (localhost [127.0.0.1])\r
5  by arlo.cworth.org (Postfix) with ESMTP id 733586DE1BB0\r
6  for <notmuch@notmuchmail.org>; Sun, 31 Jan 2016 12:40:25 -0800 (PST)\r
7 X-Virus-Scanned: Debian amavisd-new at cworth.org\r
8 X-Spam-Flag: NO\r
9 X-Spam-Score: 0\r
10 X-Spam-Level: \r
11 X-Spam-Status: No, score=0 tagged_above=-999 required=5 tests=[none]\r
12  autolearn=disabled\r
13 Received: from arlo.cworth.org ([127.0.0.1])\r
14  by localhost (arlo.cworth.org [127.0.0.1]) (amavisd-new, port 10024)\r
15  with ESMTP id tShFG0lZnqD3 for <notmuch@notmuchmail.org>;\r
16  Sun, 31 Jan 2016 12:40:23 -0800 (PST)\r
17 Received: from che.mayfirst.org (che.mayfirst.org [209.234.253.108])\r
18  by arlo.cworth.org (Postfix) with ESMTP id E71716DE1ADE\r
19  for <notmuch@notmuchmail.org>; Sun, 31 Jan 2016 12:40:09 -0800 (PST)\r
20 Received: from fifthhorseman.net (ip-64-134-185-108.public.wayport.net\r
21  [64.134.185.108])\r
22  by che.mayfirst.org (Postfix) with ESMTPSA id 09222F9A6\r
23  for <notmuch@notmuchmail.org>; Sun, 31 Jan 2016 15:40:07 -0500 (EST)\r
24 Received: by fifthhorseman.net (Postfix, from userid 1000)\r
25  id 683E0211EE; Sun, 31 Jan 2016 15:40:06 -0500 (EST)\r
26 From: Daniel Kahn Gillmor <dkg@fifthhorseman.net>\r
27 To: Notmuch Mail <notmuch@notmuchmail.org>\r
28 Subject: [PATCH v3 16/16] add "notmuch reindex" subcommand\r
29 Date: Sun, 31 Jan 2016 15:40:01 -0500\r
30 Message-Id: <1454272801-23623-17-git-send-email-dkg@fifthhorseman.net>\r
31 X-Mailer: git-send-email 2.7.0.rc3\r
32 In-Reply-To: <1454272801-23623-1-git-send-email-dkg@fifthhorseman.net>\r
33 References: <1454272801-23623-1-git-send-email-dkg@fifthhorseman.net>\r
34 MIME-Version: 1.0\r
35 Content-Type: text/plain; charset=UTF-8\r
36 Content-Transfer-Encoding: 8bit\r
37 X-BeenThere: notmuch@notmuchmail.org\r
38 X-Mailman-Version: 2.1.20\r
39 Precedence: list\r
40 List-Id: "Use and development of the notmuch mail system."\r
41  <notmuch.notmuchmail.org>\r
42 List-Unsubscribe: <https://notmuchmail.org/mailman/options/notmuch>,\r
43  <mailto:notmuch-request@notmuchmail.org?subject=unsubscribe>\r
44 List-Archive: <http://notmuchmail.org/pipermail/notmuch/>\r
45 List-Post: <mailto:notmuch@notmuchmail.org>\r
46 List-Help: <mailto:notmuch-request@notmuchmail.org?subject=help>\r
47 List-Subscribe: <https://notmuchmail.org/mailman/listinfo/notmuch>,\r
48  <mailto:notmuch-request@notmuchmail.org?subject=subscribe>\r
49 X-List-Received-Date: Sun, 31 Jan 2016 20:40:25 -0000\r
50 \r
51 This new subcommand takes a set of search terms, and re-indexes the\r
52 list of matching messages using the supplied options.\r
53 \r
54 This can be used to index the cleartext of encrypted messages with\r
55 something like:\r
56 \r
57  notmuch reindex --try-decrypt \\r
58     tag:encrypted and not tag:index-decrypted\r
59 ---\r
60  Makefile.local                    |   1 +\r
61  doc/conf.py                       |   7 ++\r
62  doc/man1/notmuch-reindex.rst      |  41 ++++++++++\r
63  doc/man1/notmuch.rst              |   1 +\r
64  doc/man7/notmuch-search-terms.rst |   7 +-\r
65  notmuch-client.h                  |   3 +\r
66  notmuch-reindex.c                 | 152 ++++++++++++++++++++++++++++++++++++++\r
67  notmuch.c                         |   2 +\r
68  test/T357-index-decryption.sh     |  53 +++++++++++++\r
69  9 files changed, 265 insertions(+), 2 deletions(-)\r
70  create mode 100644 doc/man1/notmuch-reindex.rst\r
71  create mode 100644 notmuch-reindex.c\r
72 \r
73 diff --git a/Makefile.local b/Makefile.local\r
74 index 6206771..e03a83d 100644\r
75 --- a/Makefile.local\r
76 +++ b/Makefile.local\r
77 @@ -281,6 +281,7 @@ notmuch_client_srcs =               \\r
78         notmuch-dump.c          \\r
79         notmuch-insert.c        \\r
80         notmuch-new.c           \\r
81 +       notmuch-reindex.c       \\r
82         notmuch-reply.c         \\r
83         notmuch-restore.c       \\r
84         notmuch-search.c        \\r
85 diff --git a/doc/conf.py b/doc/conf.py\r
86 index 65adafe..f98d67a 100644\r
87 --- a/doc/conf.py\r
88 +++ b/doc/conf.py\r
89 @@ -94,6 +94,10 @@ man_pages = [\r
90          u'incorporate new mail into the notmuch database',\r
91          [u'Carl Worth and many others'], 1),\r
92  \r
93 +('man1/notmuch-reindex','notmuch-reindex',\r
94 +        u're-index matching messages',\r
95 +        [u'Carl Worth and many others'], 1),\r
96 +\r
97  ('man1/notmuch-reply','notmuch-reply',\r
98          u'constructs a reply template for a set of messages',\r
99          [u'Carl Worth and many others'], 1),\r
100 @@ -162,6 +166,9 @@ texinfo_documents = [\r
101  ('man1/notmuch-new','notmuch-new',u'notmuch Documentation',\r
102        u'Carl Worth and many others', 'notmuch-new',\r
103        'incorporate new mail into the notmuch database','Miscellaneous'),\r
104 +('man1/notmuch-reindex','notmuch-reindex',u'notmuch Documentation',\r
105 +      u'Carl Worth and many others', 'notmuch-reindex',\r
106 +      're-index matching messages','Miscellaneous'),\r
107  ('man1/notmuch-reply','notmuch-reply',u'notmuch Documentation',\r
108        u'Carl Worth and many others', 'notmuch-reply',\r
109        'constructs a reply template for a set of messages','Miscellaneous'),\r
110 diff --git a/doc/man1/notmuch-reindex.rst b/doc/man1/notmuch-reindex.rst\r
111 new file mode 100644\r
112 index 0000000..7ccc947\r
113 --- /dev/null\r
114 +++ b/doc/man1/notmuch-reindex.rst\r
115 @@ -0,0 +1,41 @@\r
116 +===========\r
117 +notmuch-reindex\r
118 +===========\r
119 +\r
120 +SYNOPSIS\r
121 +========\r
122 +\r
123 +**notmuch** **reindex** [*option* ...] <*search-term*> ...\r
124 +\r
125 +DESCRIPTION\r
126 +===========\r
127 +\r
128 +Re-index all messages matching the search terms.\r
129 +\r
130 +See **notmuch-search-terms(7)** for details of the supported syntax for\r
131 +<*search-term*\ >.\r
132 +\r
133 +The **reindex** command searches for all messages matching the\r
134 +supplied search terms, and re-creates the full-text index on these\r
135 +messages using the supplied options.\r
136 +\r
137 +Supported options for **reindex** include\r
138 +\r
139 +    ``--try-decrypt``\r
140 +\r
141 +        For each message, if it is encrypted, try to decrypt it while\r
142 +        indexing.  If decryption is successful, index the cleartext\r
143 +        itself.  Be aware that the index is likely sufficient to\r
144 +        reconstruct the cleartext of the message itself, so please\r
145 +        ensure that the notmuch message index is adequately\r
146 +        protected. DO NOT USE THIS FLAG without considering the\r
147 +        security of your index.\r
148 +\r
149 +SEE ALSO\r
150 +========\r
151 +\r
152 +**notmuch(1)**, **notmuch-config(1)**, **notmuch-count(1)**,\r
153 +**notmuch-dump(1)**, **notmuch-hooks(5)**, **notmuch-insert(1)**,\r
154 +**notmuch-new(1)**,\r
155 +**notmuch-reply(1)**, **notmuch-restore(1)**, **notmuch-search(1)**,\r
156 +**notmuch-search-terms(7)**, **notmuch-show(1)**, **notmuch-tag(1)**\r
157 diff --git a/doc/man1/notmuch.rst b/doc/man1/notmuch.rst\r
158 index 3acfbdb..d9ba146 100644\r
159 --- a/doc/man1/notmuch.rst\r
160 +++ b/doc/man1/notmuch.rst\r
161 @@ -140,6 +140,7 @@ SEE ALSO\r
162  \r
163  **notmuch-config(1)**, **notmuch-count(1)**, **notmuch-dump(1)**,\r
164  **notmuch-hooks(5)**, **notmuch-insert(1)**, **notmuch-new(1)**,\r
165 +**notmuch-reindex(1)**,\r
166  **notmuch-reply(1)**, **notmuch-restore(1)**, **notmuch-search(1)**,\r
167  **notmuch-search-terms(7)**, **notmuch-show(1)**, **notmuch-tag(1)**,\r
168  **notmuch-address(1)**\r
169 diff --git a/doc/man7/notmuch-search-terms.rst b/doc/man7/notmuch-search-terms.rst\r
170 index 2fbc16d..f9c4676 100644\r
171 --- a/doc/man7/notmuch-search-terms.rst\r
172 +++ b/doc/man7/notmuch-search-terms.rst\r
173 @@ -9,6 +9,8 @@ SYNOPSIS\r
174  \r
175  **notmuch** **dump** [--format=(batch-tag|sup)] [--] [--output=<*file*>] [--] [<*search-term*> ...]\r
176  \r
177 +**notmuch** **reindex** [option ...] <*search-term*> ...\r
178 +\r
179  **notmuch** **search** [option ...] <*search-term*> ...\r
180  \r
181  **notmuch** **show** [option ...] <*search-term*> ...\r
182 @@ -375,5 +377,6 @@ SEE ALSO\r
183  \r
184  **notmuch(1)**, **notmuch-config(1)**, **notmuch-count(1)**,\r
185  **notmuch-dump(1)**, **notmuch-hooks(5)**, **notmuch-insert(1)**,\r
186 -**notmuch-new(1)**, **notmuch-reply(1)**, **notmuch-restore(1)**,\r
187 -**notmuch-search(1)**, **notmuch-show(1)**, **notmuch-tag(1)**\r
188 +**notmuch-new(1)**, **notmuch-reindex(1)**, **notmuch-reply(1)**,\r
189 +**notmuch-restore(1)**, **notmuch-search(1)**, **notmuch-show(1)**,\r
190 +**notmuch-tag(1)**\r
191 diff --git a/notmuch-client.h b/notmuch-client.h\r
192 index a41e90a..89b1180 100644\r
193 --- a/notmuch-client.h\r
194 +++ b/notmuch-client.h\r
195 @@ -169,6 +169,9 @@ int\r
196  notmuch_insert_command (notmuch_config_t *config, int argc, char *argv[]);\r
197  \r
198  int\r
199 +notmuch_reindex_command (notmuch_config_t *config, int argc, char *argv[]);\r
200 +\r
201 +int\r
202  notmuch_reply_command (notmuch_config_t *config, int argc, char *argv[]);\r
203  \r
204  int\r
205 diff --git a/notmuch-reindex.c b/notmuch-reindex.c\r
206 new file mode 100644\r
207 index 0000000..6fc88c5\r
208 --- /dev/null\r
209 +++ b/notmuch-reindex.c\r
210 @@ -0,0 +1,152 @@\r
211 +/* notmuch - Not much of an email program, (just index and search)\r
212 + *\r
213 + * Copyright Â© 2016 Daniel Kahn Gillmor\r
214 + *\r
215 + * This program is free software: you can redistribute it and/or modify\r
216 + * it under the terms of the GNU General Public License as published by\r
217 + * the Free Software Foundation, either version 3 of the License, or\r
218 + * (at your option) any later version.\r
219 + *\r
220 + * This program is distributed in the hope that it will be useful,\r
221 + * but WITHOUT ANY WARRANTY; without even the implied warranty of\r
222 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r
223 + * GNU General Public License for more details.\r
224 + *\r
225 + * You should have received a copy of the GNU General Public License\r
226 + * along with this program.  If not, see http://www.gnu.org/licenses/ .\r
227 + *\r
228 + * Author: Daniel Kahn Gillmor <dkg@fifthhorseman.net>\r
229 + */\r
230 +\r
231 +#include "notmuch-client.h"\r
232 +#include "string-util.h"\r
233 +\r
234 +static volatile sig_atomic_t interrupted;\r
235 +\r
236 +static void\r
237 +handle_sigint (unused (int sig))\r
238 +{\r
239 +    static char msg[] = "Stopping...         \n";\r
240 +\r
241 +    /* This write is "opportunistic", so it's okay to ignore the\r
242 +     * result.  It is not required for correctness, and if it does\r
243 +     * fail or produce a short write, we want to get out of the signal\r
244 +     * handler as quickly as possible, not retry it. */\r
245 +    IGNORE_RESULT (write (2, msg, sizeof (msg) - 1));\r
246 +    interrupted = 1;\r
247 +}\r
248 +\r
249 +/* reindex all messages matching 'query_string' using the passed-in indexopts\r
250 + */\r
251 +static int\r
252 +reindex_query (notmuch_database_t *notmuch, const char *query_string,\r
253 +              notmuch_indexopts_t *indexopts)\r
254 +{\r
255 +    notmuch_query_t *query;\r
256 +    notmuch_messages_t *messages;\r
257 +    notmuch_message_t *message;\r
258 +    notmuch_status_t status;\r
259 +\r
260 +    int ret = NOTMUCH_STATUS_SUCCESS;\r
261 +\r
262 +    query = notmuch_query_create (notmuch, query_string);\r
263 +    if (query == NULL) {\r
264 +       fprintf (stderr, "Out of memory.\n");\r
265 +       return 1;\r
266 +    }\r
267 +\r
268 +    /* reindexing is not interested in any special sort order */\r
269 +    notmuch_query_set_sort (query, NOTMUCH_SORT_UNSORTED);\r
270 +\r
271 +    status = notmuch_query_search_messages_st (query, &messages);\r
272 +    if (print_status_query ("notmuch reindex", query, status))\r
273 +       return status;\r
274 +\r
275 +    for (;\r
276 +        notmuch_messages_valid (messages) && ! interrupted;\r
277 +        notmuch_messages_move_to_next (messages)) {\r
278 +       message = notmuch_messages_get (messages);\r
279 +\r
280 +       notmuch_message_reindex(message, indexopts);\r
281 +       notmuch_message_destroy (message);\r
282 +       if (ret != NOTMUCH_STATUS_SUCCESS)\r
283 +           break;\r
284 +    }\r
285 +\r
286 +    notmuch_query_destroy (query);\r
287 +\r
288 +    return ret || interrupted;\r
289 +}\r
290 +\r
291 +int\r
292 +notmuch_reindex_command (notmuch_config_t *config, int argc, char *argv[])\r
293 +{\r
294 +    char *query_string = NULL;\r
295 +    notmuch_database_t *notmuch;\r
296 +    struct sigaction action;\r
297 +    notmuch_bool_t try_decrypt = FALSE;\r
298 +    int opt_index;\r
299 +    int ret;\r
300 +    notmuch_status_t status;\r
301 +    notmuch_indexopts_t *indexopts = NULL;\r
302 +\r
303 +    /* Set up our handler for SIGINT */\r
304 +    memset (&action, 0, sizeof (struct sigaction));\r
305 +    action.sa_handler = handle_sigint;\r
306 +    sigemptyset (&action.sa_mask);\r
307 +    action.sa_flags = SA_RESTART;\r
308 +    sigaction (SIGINT, &action, NULL);\r
309 +\r
310 +    notmuch_opt_desc_t options[] = {\r
311 +       { NOTMUCH_OPT_BOOLEAN, &try_decrypt, "try-decrypt", 0, 0 },\r
312 +       { NOTMUCH_OPT_INHERIT, (void *) &notmuch_shared_options, NULL, 0, 0 },\r
313 +       { 0, 0, 0, 0, 0 }\r
314 +    };\r
315 +\r
316 +    opt_index = parse_arguments (argc, argv, options, 1);\r
317 +    if (opt_index < 0)\r
318 +       return EXIT_FAILURE;\r
319 +\r
320 +    notmuch_process_shared_options (argv[0]);\r
321 +\r
322 +    if (notmuch_database_open (notmuch_config_get_database_path (config),\r
323 +                              NOTMUCH_DATABASE_MODE_READ_WRITE, &notmuch))\r
324 +       return EXIT_FAILURE;\r
325 +\r
326 +    notmuch_exit_if_unmatched_db_uuid (notmuch);\r
327 +\r
328 +    indexopts = notmuch_indexopts_create();\r
329 +    if (!indexopts)\r
330 +       return EXIT_FAILURE;\r
331 +\r
332 +    status = notmuch_indexopts_set_try_decrypt (indexopts, try_decrypt);\r
333 +    if (status)\r
334 +       fprintf (stderr, "Warning: failed to set --try-decrypt to %d (%s)\n",\r
335 +                try_decrypt, notmuch_status_to_string (status));\r
336 +\r
337 +    if (try_decrypt) {\r
338 +       const char* gpg_path = notmuch_config_get_crypto_gpg_path (config);\r
339 +       status = notmuch_indexopts_set_gpg_path (indexopts, gpg_path);\r
340 +       if (status)\r
341 +           fprintf (stderr, "Warning: failed to set gpg_path for reindexing to '%s' (%s)\n",\r
342 +                    gpg_path ? gpg_path : "(NULL)",\r
343 +                    notmuch_status_to_string (status));\r
344 +    }\r
345 +\r
346 +    query_string = query_string_from_args (config, argc-opt_index, argv+opt_index);\r
347 +    if (query_string == NULL) {\r
348 +       fprintf (stderr, "Out of memory\n");\r
349 +       return EXIT_FAILURE;\r
350 +    }\r
351 +\r
352 +    if (*query_string == '\0') {\r
353 +       fprintf (stderr, "Error: notmuch reindex requires at least one search term.\n");\r
354 +       return EXIT_FAILURE;\r
355 +    }\r
356 +    \r
357 +    ret = reindex_query (notmuch, query_string, indexopts);\r
358 +\r
359 +    notmuch_database_destroy (notmuch);\r
360 +\r
361 +    return ret || interrupted ? EXIT_FAILURE : EXIT_SUCCESS;\r
362 +}\r
363 diff --git a/notmuch.c b/notmuch.c\r
364 index ce6c575..df9cf4e 100644\r
365 --- a/notmuch.c\r
366 +++ b/notmuch.c\r
367 @@ -123,6 +123,8 @@ static command_t commands[] = {\r
368        "Restore the tags from the given dump file (see 'dump')." },\r
369      { "compact", notmuch_compact_command, FALSE,\r
370        "Compact the notmuch database." },\r
371 +    { "reindex", notmuch_reindex_command, FALSE,\r
372 +      "Re-index all messages matching the search terms." },\r
373      { "config", notmuch_config_command, FALSE,\r
374        "Get or set settings in the notmuch configuration file." },\r
375      { "help", notmuch_help_command, TRUE, /* create but don't save config */\r
376 diff --git a/test/T357-index-decryption.sh b/test/T357-index-decryption.sh\r
377 index 03e49cc..c60e152 100755\r
378 --- a/test/T357-index-decryption.sh\r
379 +++ b/test/T357-index-decryption.sh\r
380 @@ -39,4 +39,57 @@ test_expect_equal \\r
381      "$output" \\r
382      "$expected"\r
383  \r
384 +# add a tag to all messages to ensure that it stays after reindexing\r
385 +test_expect_success 'tagging all messages' \\r
386 +                    'notmuch tag +blarney "encrypted message"'\r
387 +test_begin_subtest "verify that tags are all present"\r
388 +output=$(notmuch search tag:blarney)\r
389 +expected='thread:0000000000000001   2000-01-01 [1/1] Notmuch Test Suite; test encrypted message for cleartext index 001 (blarney encrypted inbox)\r
390 +thread:0000000000000002   2000-01-01 [1/1] Notmuch Test Suite; test encrypted message for cleartext index 002 (blarney encrypted inbox index-decrypted)'\r
391 +test_expect_equal \\r
392 +    "$output" \\r
393 +    "$expected"\r
394 +\r
395 +# see if first message shows up after reindexing with --try-decrypt\r
396 +test_expect_success 'reindex old messages' \\r
397 +                    'notmuch reindex --try-decrypt tag:encrypted and not tag:index-decrypted'\r
398 +test_begin_subtest "reindexed encrypted message, including cleartext"\r
399 +output=$(notmuch search wumpus)\r
400 +expected='thread:0000000000000002   2000-01-01 [1/1] Notmuch Test Suite; test encrypted message for cleartext index 002 (blarney encrypted inbox index-decrypted)\r
401 +thread:0000000000000003   2000-01-01 [1/1] Notmuch Test Suite; test encrypted message for cleartext index 001 (blarney encrypted inbox index-decrypted)'\r
402 +test_expect_equal \\r
403 +    "$output" \\r
404 +    "$expected"\r
405 +\r
406 +\r
407 +# try to remove cleartext indexing\r
408 +test_expect_success 'reindex without cleartext' \\r
409 +                    'notmuch reindex tag:encrypted and tag:index-decrypted'\r
410 +test_begin_subtest "reindexed encrypted messages, without cleartext"\r
411 +output=$(notmuch search wumpus)\r
412 +expected=''\r
413 +test_expect_equal \\r
414 +    "$output" \\r
415 +    "$expected"\r
416 +\r
417 +# ensure that the tags remain even when we are dropping the cleartext.\r
418 +test_begin_subtest "verify that tags remain without cleartext"\r
419 +output=$(notmuch search tag:blarney)\r
420 +expected='thread:0000000000000004   2000-01-01 [1/1] Notmuch Test Suite; test encrypted message for cleartext index 002 (blarney encrypted inbox)\r
421 +thread:0000000000000005   2000-01-01 [1/1] Notmuch Test Suite; test encrypted message for cleartext index 001 (blarney encrypted inbox)'\r
422 +test_expect_equal \\r
423 +    "$output" \\r
424 +    "$expected"\r
425 +\r
426 +\r
427 +# TODO: test removal of a message from the message store between\r
428 +# indexing and reindexing.\r
429 +\r
430 +# TODO: insert the same message into the message store twice, index,\r
431 +# remove one of them from the message store, and then reindex.\r
432 +# reindexing should return a failure but the message should still be\r
433 +# present? -- or what should the semantics be if you ask to reindex a\r
434 +# message whose underlying files have been renamed or moved or\r
435 +# removed?\r
436 +\r
437  test_done\r
438 -- \r
439 2.7.0.rc3\r
440 \r