archive, the recorded Subject: of may change upon reindexing,
depending on the order in which the variants are indexed.
+Improved error reporting in notmuch new
+
+ Give more details when reporting certain Xapian exceptions.
+
+Support maildir synced tags in `new.tags`
+
+ Tags `draft`, `flagged`, `passed`, and `replied` are now supported
+ in `new.tags`. The tag `unread` is still special in the presence of
+ maildir syncing, and will be added for files in `new/` regardless of
+ the setting of `new.tags`.
+
Encrypted Mail
--------------
that the notmuch index itself is adequately protected. DO NOT USE
this feature without considering the security of your index.
+Library Changes
+---------------
+
+Indexing files with duplicate message-id
+
+ Files with duplicate message-id's are now indexed, and searchable
+ via terms and phrases. There are known issues related to
+ presentation of results and regular-expression search, but in
+ principle no mail file should be completely unsearchable now.
+
+New functions to count files
+
+ Two new functions in the libnotmuch API:
+ `notmuch_message_count_files`, and `notmuch_thread_get_total_files`.
+
+Change of return value of `notmuch_thread_get_authors`
+
+ In certain corner cases, `notmuch_thread_get_authors` previously
+ returned NULL. This has been replaced by an empty string, since the
+ possibility of NULL was not documented.
+
+Python Bindings
+---------------
+
+Python bindings specific Debian packaging is removed
+
+ The bindings have been build by the top level Debian packaging for a
+ long time, and `bindings/python/debian` has bit-rotted.
+
+Open mail files in binary mode when using Python 3
+
+ This avoids certain encoding related crashes under Python 3.
+
+Add python bindings for notmuch_database_{get,set}_config*
+
+nmbug
+-----
+
+nmbug's internal version increases to 0.3 in this notmuch release.
+User-facing changes with this notmuch release:
+
+* Accept failures to unset `core.worktree` in `clone`, which allows
+ nmbug to be used with Git 2.11.0 and later.
+* Auto-checkout in `clone` if it wouldn't clobber existing content,
+ which makes the initial clone more convenient.
+* Only error for invalid diff lines in `tags/`, which allows for
+ `README`s and similar in nmbug repositories.
+
Notmuch 0.25.3 (2017-12-08)
===========================
# this file should be kept in sync with ../../../version
-__VERSION__ = '0.26~rc0'
+__VERSION__ = '0.26~rc1'
SOVERSION = '5'
#include "error_util.h"
#include "command-line-arguments.h"
+typedef enum {
+ OPT_FAILED, /* false */
+ OPT_OK, /* good */
+ OPT_GIVEBACK, /* pop one of the arguments you thought you were getting off the stack */
+} opt_handled;
+
/*
Search the array of keywords for a given argument, assigning the
output variable to the corresponding value. Return false if nothing
matches.
*/
-static bool
+static opt_handled
_process_keyword_arg (const notmuch_opt_desc_t *arg_desc, char next,
const char *arg_str, bool negate)
{
else
*arg_desc->opt_keyword = keywords->value;
- return true;
+ return OPT_OK;
+ }
+
+ if (arg_desc->opt_keyword && arg_desc->keyword_no_arg_value && next != ':' && next != '=') {
+ for (keywords = arg_desc->keywords; keywords->name; keywords++) {
+ if (strcmp (arg_desc->keyword_no_arg_value, keywords->name) != 0)
+ continue;
+
+ *arg_desc->opt_keyword = keywords->value;
+ fprintf (stderr, "Warning: No known keyword option given for \"%s\", choosing value \"%s\"."
+ " Please specify the argument explicitly!\n", arg_desc->name, arg_desc->keyword_no_arg_value);
+
+ return OPT_GIVEBACK;
+ }
+ fprintf (stderr, "No matching keyword for option \"%s\" and default value \"%s\" is invalid.\n", arg_str, arg_desc->name);
+ return OPT_FAILED;
}
+
if (next != '\0')
fprintf (stderr, "Unknown keyword argument \"%s\" for option \"%s\".\n", arg_str, arg_desc->name);
else
fprintf (stderr, "Option \"%s\" needs a keyword argument.\n", arg_desc->name);
- return false;
+ return OPT_FAILED;
}
-static bool
+static opt_handled
_process_boolean_arg (const notmuch_opt_desc_t *arg_desc, char next,
const char *arg_str, bool negate)
{
value = false;
} else {
fprintf (stderr, "Unknown argument \"%s\" for (boolean) option \"%s\".\n", arg_str, arg_desc->name);
- return false;
+ return OPT_FAILED;
}
*arg_desc->opt_bool = negate ? !value : value;
- return true;
+ return OPT_OK;
}
-static bool
+static opt_handled
_process_int_arg (const notmuch_opt_desc_t *arg_desc, char next, const char *arg_str) {
char *endptr;
if (next == '\0' || arg_str[0] == '\0') {
fprintf (stderr, "Option \"%s\" needs an integer argument.\n", arg_desc->name);
- return false;
+ return OPT_FAILED;
}
*arg_desc->opt_int = strtol (arg_str, &endptr, 10);
if (*endptr == '\0')
- return true;
+ return OPT_OK;
fprintf (stderr, "Unable to parse argument \"%s\" for option \"%s\" as an integer.\n",
arg_str, arg_desc->name);
- return false;
+ return OPT_FAILED;
}
-static bool
+static opt_handled
_process_string_arg (const notmuch_opt_desc_t *arg_desc, char next, const char *arg_str) {
if (next == '\0') {
fprintf (stderr, "Option \"%s\" needs a string argument.\n", arg_desc->name);
- return false;
+ return OPT_FAILED;
}
if (arg_str[0] == '\0' && ! arg_desc->allow_empty) {
fprintf (stderr, "String argument for option \"%s\" must be non-empty.\n", arg_desc->name);
- return false;
+ return OPT_FAILED;
}
*arg_desc->opt_string = arg_str;
- return true;
+ return OPT_OK;
}
/* Return number of non-NULL opt_* fields in opt_desc. */
if (next != '=' && next != ':' && next != '\0')
continue;
- if (next == '\0' && next_arg != NULL && ! try->opt_bool) {
+ bool lookahead = (next == '\0' && next_arg != NULL && ! try->opt_bool);
+
+ if (lookahead) {
next = ' ';
value = next_arg;
opt_index ++;
}
- bool opt_status = false;
+ opt_handled opt_status = OPT_FAILED;
if (try->opt_keyword || try->opt_flags)
opt_status = _process_keyword_arg (try, next, value, negate);
else if (try->opt_bool)
else
INTERNAL_ERROR ("unknown or unhandled option \"%s\"", try->name);
- if (! opt_status)
+ if (opt_status == OPT_FAILED)
return -1;
+ if (lookahead && opt_status == OPT_GIVEBACK)
+ opt_index --;
+
if (try->present)
*try->present = true;
const char **opt_string;
const char **opt_position;
+ /* for opt_keyword only: if no matching arguments were found, and
+ * keyword_no_arg_value is set, then use keyword_no_arg_value instead. */
+ const char *keyword_no_arg_value;
+
/* Must be set except for opt_inherit and opt_position. */
const char *name;
return
;;
--decrypt)
- COMPREPLY=( $( compgen -W "true false" -- "${cur}" ) )
+ COMPREPLY=( $( compgen -W "true auto false" -- "${cur}" ) )
return
;;
esac
COMPREPLY=( $( compgen -W "text json sexp mbox raw" -- "${cur}" ) )
return
;;
- --exclude|--body|--decrypt)
+ --exclude|--body)
COMPREPLY=( $( compgen -W "true false" -- "${cur}" ) )
return
;;
+ --decrypt)
+ COMPREPLY=( $( compgen -W "true auto false" -- "${cur}" ) )
+ return
+ ;;
esac
! $split &&
+notmuch (0.26~rc1-1) experimental; urgency=medium
+
+ * Second upstream release candidate
+
+ -- David Bremner <bremner@debian.org> Fri, 29 Dec 2017 16:49:37 -0400
+
notmuch (0.26~rc0-1) experimental; urgency=medium
* Upstream release candidate
from urllib import unquote as _unquote
-__version__ = '0.2'
+__version__ = '0.3'
_LOG = _logging.getLogger('nmbug')
_LOG.setLevel(_logging.WARNING)
order, and copy values from the first that contains something
other than only the user's addresses.
-``--decrypt``
- Decrypt any MIME encrypted parts found in the selected content
- (ie. "multipart/encrypted" parts). Status of the decryption will
- be reported (currently only supported with --format=json and
- --format=sexp) and on successful decryption the
- multipart/encrypted part will be replaced by the decrypted
- content.
-
- If a session key is already known for the message, then it will be
- decrypted automatically unless the user explicitly sets
- ``--decrypt=false``.
-
- Decryption expects a functioning **gpg-agent(1)** to provide any
- needed credentials. Without one, the decryption will likely fail.
+ ``--decrypt=(false|auto|true)``
+
+ If ``true``, decrypt any MIME encrypted parts found in the
+ selected content (i.e., "multipart/encrypted" parts). Status
+ of the decryption will be reported (currently only supported
+ with --format=json and --format=sexp), and on successful
+ decryption the multipart/encrypted part will be replaced by
+ the decrypted content.
+
+ If ``auto``, and a session key is already known for the
+ message, then it will be decrypted, but notmuch will not try
+ to access the user's secret keys.
+
+ Use ``false`` to avoid even automatic decryption.
+
+ Non-automatic decryption expects a functioning
+ **gpg-agent(1)** to provide any needed credentials. Without
+ one, the decryption will likely fail.
+
+ Default: ``auto``
See **notmuch-search-terms(7)** for details of the supported syntax for
<search-terms>.
supported with --format=json and --format=sexp), and the
multipart/signed part will be replaced by the signed data.
-``--decrypt``
- Decrypt any MIME encrypted parts found in the selected content
- (ie. "multipart/encrypted" parts). Status of the decryption will
- be reported (currently only supported with --format=json and
- --format=sexp) and on successful decryption the
- multipart/encrypted part will be replaced by the decrypted
- content.
-
- If a session key is already known for the message, then it will be
- decrypted automatically unless the user explicitly sets
- ``--decrypt=false``.
-
- Decryption expects a functioning **gpg-agent(1)** to provide any
- needed credentials. Without one, the decryption will fail.
-
- Implies --verify.
+ ``--decrypt=(false|auto|true)``
+ If ``true``, decrypt any MIME encrypted parts found in the
+ selected content (i.e. "multipart/encrypted" parts). Status of
+ the decryption will be reported (currently only supported
+ with --format=json and --format=sexp) and on successful
+ decryption the multipart/encrypted part will be replaced by
+ the decrypted content.
+
+ If ``auto``, and a session key is already known for the
+ message, then it will be decrypted, but notmuch will not try
+ to access the user's keys.
+
+ Use ``false`` to avoid even automatic decryption.
+
+ Non-automatic decryption expects a functioning
+ **gpg-agent(1)** to provide any needed credentials. Without
+ one, the decryption will fail.
+
+ Note: ``true`` implies --verify.
+
+ Default: ``auto``
``--exclude=(true|false)``
Specify whether to omit threads only matching search.tag\_exclude
(set-buffer-multibyte nil))
(let ((args `("show" "--format=raw"
,(format "--part=%s" (plist-get part :id))
- ,@(when process-crypto '("--decrypt"))
+ ,@(when process-crypto '("--decrypt=true"))
,(notmuch-id-to-query (plist-get msg :id))))
(coding-system-for-read
(if binaryp 'no-conversion
reply
original)
(when process-crypto
- (setq args (append args '("--decrypt"))))
+ (setq args (append args '("--decrypt=true"))))
(if reply-all
(setq args (append args '("--reply-to=all")))
"
(let ((args '("show" "--format=sexp" "--format-version=4")))
(if notmuch-show-process-crypto
- (setq args (append args '("--decrypt"))))
+ (setq args (append args '("--decrypt=true"))))
(setq args (append args search-terms))
(apply #'notmuch-call-notmuch-sexp args)))
};
int format = FORMAT_DEFAULT;
int reply_all = true;
- bool decrypt = false;
- bool decrypt_set = false;
notmuch_opt_desc_t options[] = {
{ .opt_keyword = &format, .name = "format", .keywords =
(notmuch_keyword_t []){ { "all", true },
{ "sender", false },
{ 0, 0 } } },
- { .opt_bool = &decrypt, .name = "decrypt", .present = &decrypt_set },
+ { .opt_keyword = (int*)(¶ms.crypto.decrypt), .name = "decrypt",
+ .keyword_no_arg_value = "true", .keywords =
+ (notmuch_keyword_t []){ { "false", NOTMUCH_DECRYPT_FALSE },
+ { "auto", NOTMUCH_DECRYPT_AUTO },
+ { "true", NOTMUCH_DECRYPT_NOSTASH },
+ { 0, 0 } } },
{ .opt_inherit = notmuch_shared_options },
{ }
};
return EXIT_FAILURE;
notmuch_process_shared_options (argv[0]);
- if (decrypt_set)
- params.crypto.decrypt = decrypt ? NOTMUCH_DECRYPT_NOSTASH : NOTMUCH_DECRYPT_FALSE;
notmuch_exit_if_unsupported_format ();
bool exclude = true;
bool entire_thread_set = false;
bool single_message;
- bool decrypt = false;
- bool decrypt_set = false;
notmuch_opt_desc_t options[] = {
{ .opt_keyword = &format, .name = "format", .keywords =
{ .opt_bool = ¶ms.entire_thread, .name = "entire-thread",
.present = &entire_thread_set },
{ .opt_int = ¶ms.part, .name = "part" },
- { .opt_bool = &decrypt, .name = "decrypt", .present = &decrypt_set },
+ { .opt_keyword = (int*)(¶ms.crypto.decrypt), .name = "decrypt",
+ .keyword_no_arg_value = "true", .keywords =
+ (notmuch_keyword_t []){ { "false", NOTMUCH_DECRYPT_FALSE },
+ { "auto", NOTMUCH_DECRYPT_AUTO },
+ { "true", NOTMUCH_DECRYPT_NOSTASH },
+ { 0, 0 } } },
{ .opt_bool = ¶ms.crypto.verify, .name = "verify" },
{ .opt_bool = ¶ms.output_body, .name = "body" },
{ .opt_bool = ¶ms.include_html, .name = "include-html" },
notmuch_process_shared_options (argv[0]);
- if (decrypt_set) {
- if (decrypt) {
- /* we do not need or want to ask for session keys */
- params.crypto.decrypt = NOTMUCH_DECRYPT_NOSTASH;
- /* decryption implies verification */
- params.crypto.verify = true;
- } else {
- params.crypto.decrypt = NOTMUCH_DECRYPT_FALSE;
- }
- }
+ /* explicit decryption implies verification */
+ if (params.crypto.decrypt == NOTMUCH_DECRYPT_NOSTASH)
+ params.crypto.verify = true;
/* specifying a part implies single message display */
single_message = params.part >= 0;
test_expect_equal "$output" "thread:XXX 2000-01-01 [1/1] Notmuch Test Suite; test encrypted message 001 (encrypted inbox)"
test_begin_subtest "decryption, --format=text"
-output=$(notmuch show --format=text --decrypt subject:"test encrypted message 001" \
+output=$(notmuch show --format=text --decrypt=true subject:"test encrypted message 001" \
| notmuch_show_sanitize_all \
| sed -e 's|"created": [1234567890]*|"created": 946728000|')
expected='\fmessage{ id:XXXXX depth:0 match:1 excluded:0 filename:XXXXX
"$expected"
test_begin_subtest "decryption, --format=json"
-output=$(notmuch show --format=json --decrypt subject:"test encrypted message 001" \
+output=$(notmuch show --format=json --decrypt=true subject:"test encrypted message 001" \
| notmuch_json_show_sanitize \
| sed -e 's|"created": [1234567890]*|"created": 946728000|')
expected='[[[{"id": "XXXXX",
"$expected"
test_begin_subtest "decryption, --format=json, --part=4"
-output=$(notmuch show --format=json --part=4 --decrypt subject:"test encrypted message 001" \
+output=$(notmuch show --format=json --part=4 --decrypt=true subject:"test encrypted message 001" \
| notmuch_json_show_sanitize \
| sed -e 's|"created": [1234567890]*|"created": 946728000|')
expected='{"id": 4,
notmuch show \
--format=raw \
--part=5 \
- --decrypt \
+ --decrypt=true \
subject:"test encrypted message 001" >OUTPUT
test_expect_equal_file TESTATTACHMENT OUTPUT
test_begin_subtest "decryption failure with missing key"
mv "${GNUPGHOME}"{,.bak}
-output=$(notmuch show --format=json --decrypt subject:"test encrypted message 001" \
+output=$(notmuch show --format=json --decrypt=true subject:"test encrypted message 001" \
| notmuch_json_show_sanitize \
| sed -e 's|"created": [1234567890]*|"created": 946728000|')
expected='[[[{"id": "XXXXX",
test_begin_subtest "decryption + signature verification"
test_subtest_broken_gmime_2
-output=$(notmuch show --format=json --decrypt subject:"test encrypted message 002" \
+output=$(notmuch show --format=json --decrypt=true subject:"test encrypted message 002" \
| notmuch_json_show_sanitize \
| sed -e 's|"created": [1234567890]*|"created": 946728000|')
expected='[[[{"id": "XXXXX",
"$expected"
test_begin_subtest "reply to encrypted message"
-output=$(notmuch reply --decrypt subject:"test encrypted message 002" \
+output=$(notmuch reply --decrypt=true subject:"test encrypted message 002" \
| notmuch_drop_mail_headers In-Reply-To References)
expected='From: Notmuch Test Suite <test_suite@notmuchmail.org>
Subject: Re: test encrypted message 002
"$output" \
"$expected"
-test_begin_subtest "show one of the messages with --decrypt"
-output=$(notmuch show --decrypt thread:0000000000000001 | awk '/^\014part}/{ f=0 }; { if (f) { print $0 } } /^\014part{ ID: 3/{ f=1 }')
+test_begin_subtest "show one of the messages with --decrypt=true"
+output=$(notmuch show --decrypt=true thread:0000000000000001 | awk '/^\014part}/{ f=0 }; { if (f) { print $0 } } /^\014part{ ID: 3/{ f=1 }')
expected='This is a test encrypted message with a wumpus.'
test_expect_equal \
"$output" \
"$expected"
-test_begin_subtest "Ensure that we cannot show the message without --decrypt"
+test_begin_subtest "Ensure that we cannot show the message with --decrypt=auto"
output=$(notmuch show thread:0000000000000001 | awk '/^\014part}/{ f=0 }; { if (f) { print $0 } } /^\014part{ ID: 3/{ f=1 }')
expected='Non-text part: application/octet-stream'
test_expect_equal \
EOF
test_expect_equal_file EXPECTED OUTPUT
+test_begin_subtest "test keyword arguments without value"
+$TEST_DIRECTORY/arg-test --boolkeyword bananas > OUTPUT
+cat <<EOF > EXPECTED
+boolkeyword 1
+positional arg 1 bananas
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "test keyword arguments with non-default value separted by a space"
+$TEST_DIRECTORY/arg-test --boolkeyword false bananas > OUTPUT
+cat <<EOF > EXPECTED
+boolkeyword 0
+positional arg 1 bananas
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "test keyword arguments without value at the end"
+$TEST_DIRECTORY/arg-test bananas --boolkeyword > OUTPUT
+cat <<EOF > EXPECTED
+boolkeyword 1
+positional arg 1 bananas
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "test keyword arguments without value but with = (should be an error)"
+$TEST_DIRECTORY/arg-test bananas --boolkeyword= > OUTPUT 2>&1
+cat <<EOF > EXPECTED
+Unknown keyword argument "" for option "boolkeyword".
+Unrecognized option: --boolkeyword=
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
test_done
=== ERROR ===
[XXX]
This is an error
-command: YYY/notmuch_fail show --format\\=sexp --format-version\\=4 --decrypt --exclude\\=false \\' \\* \\'
+command: YYY/notmuch_fail show --format\\=sexp --format-version\\=4 --decrypt\\=true --exclude\\=false \\' \\* \\'
exit status: 1
stderr:
This is an error
int opt_index=1;
int kw_val=0;
+ int kwb_val=0;
int fl_val=0;
int int_val=0;
const char *pos_arg1=NULL;
const char *pos_arg2=NULL;
const char *string_val=NULL;
bool bool_val = false;
- bool fl_set = false, int_set = false, bool_set = false,
+ bool fl_set = false, int_set = false, bool_set = false, kwb_set = false,
kw_set = false, string_set = false, pos1_set = false, pos2_set = false;
notmuch_opt_desc_t parent_options[] = {
{ "one", 1 },
{ "two", 2 },
{ 0, 0 } } },
+ { .opt_keyword = &kwb_val, .name = "boolkeyword", .present = &kwb_set,
+ .keyword_no_arg_value = "true", .keywords =
+ (notmuch_keyword_t []){ { "false", 0 },
+ { "true", 1 },
+ { "auto", 2 },
+ { 0, 0 } } },
{ .opt_inherit = parent_options },
{ .opt_string = &string_val, .name = "string", .present = &string_set },
{ .opt_position = &pos_arg1, .present = &pos1_set },
if (kw_set)
printf("keyword %d\n", kw_val);
+ if (kwb_set)
+ printf("boolkeyword %d\n", kwb_val);
+
if (fl_set)
printf("flags %d\n", fl_val);