From 5f22d16273ef0d42fc42303087a588efeeb6d7c1 Mon Sep 17 00:00:00 2001 From: William Casarin Date: Fri, 31 May 2019 11:32:50 -0700 Subject: [PATCH 001/427] perf-tests: #!/bin/bash -> #!/usr/bin/env bash I couldn't run the performance tests on my machines due to a hardcoded bash path. Use env for finding bash in weird systems like NixOS. Signed-off-by: William Casarin --- performance-test/M00-new.sh | 2 +- performance-test/M01-dump-restore.sh | 2 +- performance-test/M02-show.sh | 2 +- performance-test/M03-search.sh | 2 +- performance-test/M04-reply.sh | 2 +- performance-test/M05-reindex.sh | 2 +- performance-test/M06-insert.sh | 2 +- performance-test/T00-new.sh | 2 +- performance-test/T01-dump-restore.sh | 2 +- performance-test/T02-tag.sh | 2 +- performance-test/T03-reindex.sh | 2 +- performance-test/T04-thread-subquery.sh | 2 +- 12 files changed, 12 insertions(+), 12 deletions(-) diff --git a/performance-test/M00-new.sh b/performance-test/M00-new.sh index aab36e69..5858ab33 100755 --- a/performance-test/M00-new.sh +++ b/performance-test/M00-new.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash test_description='notmuch new' diff --git a/performance-test/M01-dump-restore.sh b/performance-test/M01-dump-restore.sh index 32ab8dc9..7850b411 100755 --- a/performance-test/M01-dump-restore.sh +++ b/performance-test/M01-dump-restore.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash test_description='dump and restore' diff --git a/performance-test/M02-show.sh b/performance-test/M02-show.sh index 2e218fd3..40c5d4d7 100755 --- a/performance-test/M02-show.sh +++ b/performance-test/M02-show.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash test_description='show' diff --git a/performance-test/M03-search.sh b/performance-test/M03-search.sh index 343f5c7c..a73a36ab 100755 --- a/performance-test/M03-search.sh +++ b/performance-test/M03-search.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash test_description='search' diff --git a/performance-test/M04-reply.sh b/performance-test/M04-reply.sh index 3c1205db..3b0f9e78 100755 --- a/performance-test/M04-reply.sh +++ b/performance-test/M04-reply.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash test_description='search' diff --git a/performance-test/M05-reindex.sh b/performance-test/M05-reindex.sh index 17e2c824..8ea7e7ee 100755 --- a/performance-test/M05-reindex.sh +++ b/performance-test/M05-reindex.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash test_description='reindex' diff --git a/performance-test/M06-insert.sh b/performance-test/M06-insert.sh index 5ae0656a..12330c76 100755 --- a/performance-test/M06-insert.sh +++ b/performance-test/M06-insert.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash test_description='search' diff --git a/performance-test/T00-new.sh b/performance-test/T00-new.sh index 25391136..a14dd13f 100755 --- a/performance-test/T00-new.sh +++ b/performance-test/T00-new.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash test_description='notmuch new' diff --git a/performance-test/T01-dump-restore.sh b/performance-test/T01-dump-restore.sh index 12f12e66..2a53e3b8 100755 --- a/performance-test/T01-dump-restore.sh +++ b/performance-test/T01-dump-restore.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash test_description='dump and restore' diff --git a/performance-test/T02-tag.sh b/performance-test/T02-tag.sh index 8c5dfd68..9c895d6a 100755 --- a/performance-test/T02-tag.sh +++ b/performance-test/T02-tag.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash test_description='tagging' diff --git a/performance-test/T03-reindex.sh b/performance-test/T03-reindex.sh index 8e0a77f4..8db52a33 100755 --- a/performance-test/T03-reindex.sh +++ b/performance-test/T03-reindex.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash test_description='reindexing' diff --git a/performance-test/T04-thread-subquery.sh b/performance-test/T04-thread-subquery.sh index 665d5a64..ba81f383 100755 --- a/performance-test/T04-thread-subquery.sh +++ b/performance-test/T04-thread-subquery.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash test_description='thread subqueries' From bc396c967c7cd8e7a109858e428d7bf97173f7a7 Mon Sep 17 00:00:00 2001 From: Daniel Kahn Gillmor Date: Wed, 29 May 2019 20:09:44 -0400 Subject: [PATCH 002/427] test: signature verification during decryption (session keys) When the user knows the signer's key, we want "notmuch show" to be able to verify the signature of an encrypted and signed message regardless of whether we are using a stashed session key or not. I wrote this test because I was surprised to see signature verification failing when viewing some encrypted messages after upgrading to GPGME 1.13.0-1 in debian experimental. The added tests here all pass with GPGME 1.12.0, but the final test fails with 1.13.0, due to some buggy updates to GPGME upstream: see https://dev.gnupg.org/T3464 for more details. While the bug needs to be fixed in GPGME, notmuch's test suite needs to make sure that GMime is doing what we expect it to do; i was a bit surprised that it hadn't caught the problem, hence this patch. I've fixed this bug in debian experimental with gpgme 1.13.0-2, so the tests should pass on any debian system. I've also fixed it in the gpgme packages (1.13.0-2~ppa1) in the ubuntu xenial PPA (ppa:notmuch/notmuch) that notmuch uses for Travis CI. Signed-off-by: Daniel Kahn Gillmor --- test/T357-index-decryption.sh | 19 +++++++++++++ test/corpora/crypto/encrypted-signed.eml | 35 ++++++++++++++++++++++++ 2 files changed, 54 insertions(+) create mode 100644 test/corpora/crypto/encrypted-signed.eml diff --git a/test/T357-index-decryption.sh b/test/T357-index-decryption.sh index 8a2d4c02..1ac2836a 100755 --- a/test/T357-index-decryption.sh +++ b/test/T357-index-decryption.sh @@ -226,6 +226,7 @@ output=$(notmuch dump | LC_ALL=C sort) expected='#= simple-encrypted@crypto.notmuchmail.org index.decryption=failure #notmuch-dump batch-tag:3 config,properties,tags +encrypted +inbox +unread -- id:basic-encrypted@crypto.notmuchmail.org ++encrypted +inbox +unread -- id:encrypted-signed@crypto.notmuchmail.org +encrypted +inbox +unread -- id:simple-encrypted@crypto.notmuchmail.org' test_expect_equal \ "$output" \ @@ -288,6 +289,24 @@ test_expect_equal \ "$output" \ "$expected" +goodsig='good_sig:[0][0][0]["crypto"]["signed"]["status"][0]["status"]="good"' +nosig='no_sig:[0][0][0]["crypto"]!"signed"' + +test_begin_subtest "verify signature without a session key stashed when --decrypt=true" +output=$(notmuch show --format=json --decrypt=true id:encrypted-signed@crypto.notmuchmail.org) +test_json_nodes <<<"$output" "$goodsig" + +test_begin_subtest "do not verify sig without a session key stashed if --decrypt=auto" +output=$(notmuch show --format=json id:encrypted-signed@crypto.notmuchmail.org) +test_json_nodes <<<"$output" "$nosig" + +test_begin_subtest "verify signature when --decrypt=stash" +output=$(notmuch show --format=json --decrypt=stash id:encrypted-signed@crypto.notmuchmail.org) +test_json_nodes <<<"$output" "$goodsig" + +test_begin_subtest "verify signature with stashed session key" +output=$(notmuch show --format=json id:encrypted-signed@crypto.notmuchmail.org) +test_json_nodes <<<"$output" "$goodsig" # TODO: test removal of a message from the message store between # indexing and reindexing. diff --git a/test/corpora/crypto/encrypted-signed.eml b/test/corpora/crypto/encrypted-signed.eml new file mode 100644 index 00000000..0345e3e9 --- /dev/null +++ b/test/corpora/crypto/encrypted-signed.eml @@ -0,0 +1,35 @@ +From: test_suite@notmuchmail.org +To: test_suite@notmuchmail.org +Subject: Lyrics +Date: Wed 29 May 2019 06:09:22 PM EDT +Message-ID: +MIME-Version: 1.0 +Content-Type: multipart/encrypted; boundary="=-=-="; + protocol="application/pgp-encrypted" + +--=-=-= +Content-Type: application/pgp-encrypted + +Version: 1 + +--=-=-= +Content-Type: application/octet-stream + +-----BEGIN PGP MESSAGE----- + +hIwDxE023q1UqxYBBAC9z781zV7QAInGMKHX6TKU5Xw/OkoWXahpDL88F6Ocm5R9 +7M9z2ocvlyrbgRhqE+nvFeGH/K7rVkBBT6TAcdIe/C8Qzbd3stPPcx1PlunGROj7 +H/WAcmDksK3HkXpHwmInUtzNw1pkhOoLy/sFSbPvtyg8GCUzXbafHAIIo0rB2tLB +DwGWD3l4WdcyQWuYD9QJKuDIqdWo8E3TTcKkiOAt/6liwPNZ0jGzDeCuSTnWFj6Z +AiXGeNtD3I1tCN/8T3NjEKOCQ+bdT5Y06dDaL61FpQ23eIuSUgksVxjnkEAb6iPe +07gjzcyNuGP3WPI/0qu0wtZwpAQxvaNygDsQj/OjR5kn9luBd/VqodM3TWWS8miV +m0z1tYbqYAQWW6TS7fXlsyXoOxTLW5MCfe3D36VSErL/NJItETklVKzNfKjMmRKx +CI2ZUzugxPWSLQzOp5yl7iICk8e+vS9TkQw2j0nXAQYLYgmqZMhf4av5GlFv3tQu +heO4XLT6NBDTHMFTDbgW42kE0N4MDPc29AqVFGImcTHvflF4Vp0qIbSJdIcHwKkU +5LKqvicAa0lsIoJbsW3lHrzowyjov2vLH/VGd/wIX+MS3KT7cySdyp8HVMcwwyZu +Y9nrTN/7G1FwKWlcGa4uJNcFFkYlcEymZj1EX2cyrdezPtX7K5vhwBYddptFD+Bn +IVkghRut3UDeXe83F8OutWiZfK5EVYABq/aP3//hIbQl2o4Dkd3z9m+8LobrIV5s +NXjAjU5WQOjRLoHBebG2HkMpFsWhXD/Fb/Bb58VOpdI= +=x12v +-----END PGP MESSAGE----- +--=-=-=-- From 00c63bf7364778a75591fe494e029233736af04d Mon Sep 17 00:00:00 2001 From: Tomi Ollila Date: Mon, 10 Jun 2019 21:39:23 +0300 Subject: [PATCH 003/427] test: aggregate-results.sh: consistent style. zero forks. - all variables in $((...)) without leading $ - all comparisons use -gt, -eq or -ne - no -a nor -o inside [ ... ] expressions - all indentation levels using one tab Dropped unnecessary empty string check when reading results files. Replaced pluralize() which was executed in subshell with pluralize_s(). pluralize_s sets $s to 's' or '' based on value of $1. Calls to pluralize_s are done in context of current shell, so no forks to subshells executed. --- test/aggregate-results.sh | 92 +++++++++++++++------------------------ 1 file changed, 36 insertions(+), 56 deletions(-) diff --git a/test/aggregate-results.sh b/test/aggregate-results.sh index 63228546..05fb0a92 100755 --- a/test/aggregate-results.sh +++ b/test/aggregate-results.sh @@ -13,81 +13,61 @@ do while read type value do case $type in - '') - continue ;; fixed) - fixed=$(($fixed + $value)) ;; + fixed=$((fixed + value)) ;; success) - success=$(($success + $value)) ;; + success=$((success + value)) ;; failed) - failed=$(($failed + $value)) ;; + failed=$((failed + value)) ;; broken) - broken=$(($broken + $value)) ;; + broken=$((broken + value)) ;; total) - total=$(($total + $value)) ;; + total=$((total + value)) ;; esac done <"$file" done -pluralize () { - case $2 in - 1) - case $1 in - test) - echo test ;; - failure) - echo failure ;; - esac - ;; - *) - case $1 in - test) - echo tests ;; - failure) - echo failures ;; - esac - ;; - esac -} +pluralize_s () { [ "$1" -eq 1 ] && s='' || s='s'; } echo "Notmuch test suite complete." -if [ "$fixed" = "0" ] && [ "$failed" = "0" ]; then - tests=$(pluralize "test" $total) - printf "All $total $tests " - if [ "$broken" = "0" ]; then - echo "passed." - else - failures=$(pluralize "failure" $broken) - echo "behaved as expected ($broken expected $failures)." - fi; + +if [ "$fixed" -eq 0 ] && [ "$failed" -eq 0 ]; then + pluralize_s "$total" + printf "All $total test$s " + if [ "$broken" -eq 0 ]; then + echo "passed." + else + pluralize_s "$broken" + echo "behaved as expected ($broken expected failure$s)." + fi else - echo "$success/$total tests passed." - if [ "$broken" != "0" ]; then - tests=$(pluralize "test" $broken) - echo "$broken broken $tests failed as expected." - fi - if [ "$fixed" != "0" ]; then - tests=$(pluralize "test" $fixed) - echo "$fixed broken $tests now fixed." - fi - if [ "$failed" != "0" ]; then - tests=$(pluralize "test" $failed) - echo "$failed $tests failed." - fi + echo "$success/$total tests passed." + if [ "$broken" -ne 0 ]; then + pluralize_s "$broken" + echo "$broken broken test$s failed as expected." + fi + if [ "$fixed" -ne 0 ]; then + pluralize_s "$fixed" + echo "$fixed broken test$s now fixed." + fi + if [ "$failed" -ne 0 ]; then + pluralize_s "$failed" + echo "$failed test$s failed." + fi fi -skipped=$(($total - $fixed - $success - $failed - $broken)) -if [ "$skipped" != "0" ]; then - tests=$(pluralize "test" $skipped) - echo "$skipped $tests skipped." +skipped=$((total - fixed - success - failed - broken)) +if [ "$skipped" -ne 0 ]; then + pluralize_s "$skipped" + echo "$skipped test$s skipped." fi # Note that we currently do not consider skipped tests as failing the # build. -if [ $success -gt 0 -a $fixed -eq 0 -a $failed -eq 0 ] +if [ "$success" -gt 0 ] && [ "$fixed" -eq 0 ] && [ "$failed" -eq 0 ] then - exit 0 + exit 0 else - exit 1 + exit 1 fi From cd733b079f7038d73cbaa88fa5ade40794f670bd Mon Sep 17 00:00:00 2001 From: Daniel Kahn Gillmor Date: Mon, 10 Jun 2019 04:35:03 +0300 Subject: [PATCH 004/427] debian: enable build hardening features Debian's build hardening toolchain options produce binary artifacts that are more resistant to compromise. The most visible change for notmuch today is likely to be the addition of the "bindnow" linker flag, which contributes to making the "Global Offset Table" fully read-only. See https://wiki.debian.org/Hardening for more details. Signed-off-by: Daniel Kahn Gillmor --- debian/rules | 2 ++ 1 file changed, 2 insertions(+) diff --git a/debian/rules b/debian/rules index d056edb6..ebd10481 100755 --- a/debian/rules +++ b/debian/rules @@ -2,6 +2,8 @@ python3_all = py3versions -s | xargs -n1 | xargs -t -I {} env {} +export DEB_BUILD_MAINT_OPTIONS = hardening=+all + %: dh $@ --with python2,python3,elpa From bcee87082694f7e7d7ef51064073bacfe856d23f Mon Sep 17 00:00:00 2001 From: Daniel Kahn Gillmor Date: Mon, 10 Jun 2019 04:25:26 +0300 Subject: [PATCH 005/427] fix misspelling Signed-off-by: Daniel Kahn Gillmor --- lib/messages.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/messages.c b/lib/messages.c index 04fa19f8..7ddfaf26 100644 --- a/lib/messages.c +++ b/lib/messages.c @@ -117,7 +117,7 @@ _notmuch_messages_has_next (notmuch_messages_t *messages) return false; if (! messages->is_of_list_type) - INTERNAL_ERROR("_notmuch_messages_has_next not implimented for msets"); + INTERNAL_ERROR("_notmuch_messages_has_next not implemented for msets"); return (messages->iterator->next != NULL); } From e9206b5ed659baa4438e3be1e30c9e806450b59a Mon Sep 17 00:00:00 2001 From: Daniel Kahn Gillmor Date: Mon, 10 Jun 2019 04:22:50 +0300 Subject: [PATCH 006/427] debian: bump Standards-Version to 4.3.0 (no changes needed) /usr/share/doc/debian-policy/upgrading-checklist.txt.gz suggests that notmuch is already compliant with debian-policy 4.3.0. Signed-off-by: Daniel Kahn Gillmor --- debian/control | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/debian/control b/debian/control index 31d6471c..56849500 100644 --- a/debian/control +++ b/debian/control @@ -29,7 +29,7 @@ Build-Depends: gnupg , bash-completion (>=1.9.0~), texinfo -Standards-Version: 4.1.3 +Standards-Version: 4.3.0 Homepage: https://notmuchmail.org/ Vcs-Git: https://git.notmuchmail.org/git/notmuch -b release Vcs-Browser: https://git.notmuchmail.org/git/notmuch From eea09ee15348992685e4c5b329303b6bae3b0cac Mon Sep 17 00:00:00 2001 From: Daniel Kahn Gillmor Date: Mon, 10 Jun 2019 04:22:51 +0300 Subject: [PATCH 007/427] debian: Add appropriate substitution variables to debian/control Without this change, dh_gencontrol emits: dpkg-gencontrol: warning: package python-notmuch: substitution variable ${python:Provides} unused, but is defined dpkg-gencontrol: warning: package python-notmuch: substitution variable ${python:Versions} unused, but is defined dpkg-gencontrol: warning: package notmuch-mutt: substitution variable ${perl:Depends} unused, but is defined Signed-off-by: Daniel Kahn Gillmor --- debian/control | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/debian/control b/debian/control index 56849500..ff646c6b 100644 --- a/debian/control +++ b/debian/control @@ -77,6 +77,8 @@ Package: python-notmuch Architecture: all Section: python Depends: ${misc:Depends}, ${python:Depends}, libnotmuch5 (>= ${source:Version}) +Provides: ${python:Provides} +XB-Python-Version: ${python:Versions} Description: Python interface to the notmuch mail search and index library Notmuch is a system for indexing, searching, reading, and tagging large collections of email messages in maildir or mh format. It uses @@ -152,7 +154,8 @@ Depends: notmuch (>= 0.4), libmail-box-perl, libmailtools-perl, libstring-shellquote-perl, libterm-readline-gnu-perl, - ${misc:Depends} + ${misc:Depends}, + ${perl:Depends}, Recommends: mutt Enhances: notmuch, mutt Description: thread-based email index, search and tagging (Mutt interface) From 38dacf009c4feb5d4820bbdd9222d7e7be067903 Mon Sep 17 00:00:00 2001 From: Tomi Ollila Date: Thu, 30 May 2019 22:56:14 +0300 Subject: [PATCH 008/427] append _unused to the expression defined using unused() macro This way if variables defined using unused() macro are actually used then code will not compile... - removed unused usage around one argc and one argv since those were used - changed one unused (char *argv[]) to unused (char **argv) to work with modified unused() macro definition --- notmuch-client.h | 2 +- notmuch-setup.c | 2 +- notmuch.c | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/notmuch-client.h b/notmuch-client.h index 39e26f2e..b3a501a9 100644 --- a/notmuch-client.h +++ b/notmuch-client.h @@ -53,7 +53,7 @@ #include "talloc-extra.h" #include "crypto.h" -#define unused(x) x __attribute__ ((unused)) +#define unused(x) x ## _unused __attribute__ ((unused)) #define STRINGIFY(s) STRINGIFY_(s) #define STRINGIFY_(s) #s diff --git a/notmuch-setup.c b/notmuch-setup.c index 53048005..81419ccf 100644 --- a/notmuch-setup.c +++ b/notmuch-setup.c @@ -121,7 +121,7 @@ parse_tag_list (void *ctx, char *response) int notmuch_setup_command (notmuch_config_t *config, - unused (int argc), unused (char *argv[])) + int argc, char *argv[]) { char *response = NULL; size_t response_size = 0; diff --git a/notmuch.c b/notmuch.c index eeb794e8..2ddc8fbc 100644 --- a/notmuch.c +++ b/notmuch.c @@ -358,7 +358,7 @@ notmuch_help_command (unused (notmuch_config_t * config), int argc, char *argv[] */ static int notmuch_command (notmuch_config_t *config, - unused(int argc), unused(char *argv[])) + unused(int argc), unused(char **argv)) { char *db_path; struct stat st; From a6a8df7e0379bfb6e69b9541d7813ac71f624f2e Mon Sep 17 00:00:00 2001 From: David Bremner Date: Tue, 11 Jun 2019 21:12:38 -0300 Subject: [PATCH 009/427] build: drop variable HAVE_EMACS. use WITH_EMACS instead The extra flexibility of having both HAVE_EMACS (for yes, there is an emacs we can use) and WITH_EMACS (the user wants emacs support) lead to confusion and bugs. We now just force WITH_EMACS to 0 if no suitable emacs is detected. --- configure | 18 ++++++++---------- doc/Makefile.local | 6 +++--- doc/conf.py | 2 +- emacs/Makefile.local | 10 ++++------ 4 files changed, 16 insertions(+), 20 deletions(-) diff --git a/configure b/configure index 8b80f0e0..554e10c9 100755 --- a/configure +++ b/configure @@ -677,13 +677,14 @@ if [ -z "${EMACSETCDIR-}" ]; then EMACSETCDIR="\$(prefix)/share/emacs/site-lisp" fi -printf "Checking if emacs (>= 24) is available... " -if emacs --quick --batch --eval '(if (< emacs-major-version 24) (kill-emacs 1))' > /dev/null 2>&1; then - printf "Yes.\n" - have_emacs=1 -else - printf "No (so will not byte-compile emacs code)\n" - have_emacs=0 +if [ $WITH_EMACS = "1" ]; then + printf "Checking if emacs (>= 24) is available... " + if emacs --quick --batch --eval '(if (< emacs-major-version 24) (kill-emacs 1))' > /dev/null 2>&1; then + printf "Yes.\n" + else + printf "No (disabling emacs related parts of build)\n" + WITH_EMACS=0 + fi fi have_doxygen=0 @@ -1165,9 +1166,6 @@ BASH_ABSOLUTE = ${bash_absolute} HAVE_PERL = ${have_perl} PERL_ABSOLUTE = ${perl_absolute} -# Whether there's an emacs binary available for byte-compiling -HAVE_EMACS = ${have_emacs} - # Whether there's a sphinx-build binary available for building documentation HAVE_SPHINX=${have_sphinx} diff --git a/doc/Makefile.local b/doc/Makefile.local index d733b51e..b4e0c955 100644 --- a/doc/Makefile.local +++ b/doc/Makefile.local @@ -4,7 +4,7 @@ dir := doc # You can set these variables from the command line. SPHINXOPTS := -q -SPHINXBUILD = HAVE_EMACS=${HAVE_EMACS} WITH_EMACS=${WITH_EMACS} sphinx-build +SPHINXBUILD = WITH_EMACS=${WITH_EMACS} sphinx-build DOCBUILDDIR := $(dir)/_build # Internal variables. @@ -29,8 +29,8 @@ MAN1_TEXI := $(patsubst $(srcdir)/doc/man1/%.rst,$(DOCBUILDDIR)/texinfo/%.texi,$ MAN5_TEXI := $(patsubst $(srcdir)/doc/man5/%.rst,$(DOCBUILDDIR)/texinfo/%.texi,$(MAN5_RST)) MAN7_TEXI := $(patsubst $(srcdir)/doc/man7/%.rst,$(DOCBUILDDIR)/texinfo/%.texi,$(MAN7_RST)) INFO_TEXI_FILES := $(MAN1_TEXI) $(MAN5_TEXI) $(MAN7_TEXI) -ifeq ($(HAVE_EMACS)$(WITH_EMACS),11) - INFO_TEXI_FILES := $(INFO_TEXI_FILES) $(DOCBUILDDIR)/texinfo/notmuch-emacs.texi +ifeq ($(WITH_EMACS),1) + INFO_TEXI_FILES += $(DOCBUILDDIR)/texinfo/notmuch-emacs.texi endif INFO_INFO_FILES := $(INFO_TEXI_FILES:.texi=.info) diff --git a/doc/conf.py b/doc/conf.py index 8afff929..fc9738ff 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -32,7 +32,7 @@ exclude_patterns = ['_build'] # If we don't have emacs (or the user configured --without-emacs), # don't build the notmuch-emacs docs, as they need emacs to generate # the docstring include files -if os.environ.get('HAVE_EMACS') != '1' or os.environ.get('WITH_EMACS') != '1': +if os.environ.get('WITH_EMACS') != '1': exclude_patterns.append('notmuch-emacs.rst') # The name of the Pygments (syntax highlighting) style to use. diff --git a/emacs/Makefile.local b/emacs/Makefile.local index 40b7c14f..141f5868 100644 --- a/emacs/Makefile.local +++ b/emacs/Makefile.local @@ -47,7 +47,7 @@ emacs_images := \ emacs_bytecode = $(emacs_sources:.el=.elc) emacs_docstrings = $(emacs_sources:.el=.rsti) -ifneq ($(HAVE_SPHINX)$(HAVE_EMACS),11) +ifneq ($(HAVE_SPHINX)$(WITH_EMACS),11) docstring.stamp: @echo "Missing prerequisites, not collecting docstrings" else @@ -60,7 +60,7 @@ endif # the byte compiler may load an old .elc file when processing a # "require" or we may fail to rebuild a .elc that depended on a macro # from an updated file. -ifeq ($(HAVE_EMACS),1) +ifeq ($(WITH_EMACS),1) $(dir)/.eldeps: $(dir)/Makefile.local $(dir)/make-deps.el $(emacs_sources) $(call quiet,EMACS) --directory emacs -batch -l make-deps.el \ -f batch-make-deps $(emacs_sources) > $@.tmp && \ @@ -82,7 +82,7 @@ $(dir)/notmuch-lib.elc: $(dir)/notmuch-version.elc endif CLEAN+=$(dir)/.eldeps $(dir)/.eldeps.tmp $(dir)/.eldeps.x -ifeq ($(HAVE_EMACS),1) +ifeq ($(WITH_EMACS),1) %.elc: %.el $(global_deps) $(call quiet,EMACS) --directory emacs -batch -f batch-byte-compile $< %.rsti: %.el @@ -103,10 +103,8 @@ endif rm -r .elpa-build ifeq ($(WITH_EMACS),1) -ifeq ($(HAVE_EMACS),1) all: $(emacs_bytecode) $(emacs_docstrings) install-emacs: $(emacs_bytecode) -endif install: install-emacs endif @@ -115,7 +113,7 @@ endif install-emacs: $(emacs_sources) $(emacs_images) mkdir -p "$(DESTDIR)$(emacslispdir)" install -m0644 $(emacs_sources) "$(DESTDIR)$(emacslispdir)" -ifeq ($(HAVE_EMACS),1) +ifeq ($(WITH_EMACS),1) install -m0644 $(emacs_bytecode) "$(DESTDIR)$(emacslispdir)" endif mkdir -p "$(DESTDIR)$(emacsetcdir)" From 2c4384a5763bdab308b8c417afa8d5f8e393b9a5 Mon Sep 17 00:00:00 2001 From: David Bremner Date: Fri, 14 Jun 2019 07:32:22 -0300 Subject: [PATCH 010/427] cli: partial whitespace cleanup in notmuch-config.c This avoids spurious tab to space conversion by uncrustify --- notmuch-config.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/notmuch-config.c b/notmuch-config.c index b7f0784f..91a24a76 100644 --- a/notmuch-config.c +++ b/notmuch-config.c @@ -305,15 +305,15 @@ out: * returned and *is_new_ret will be set to 1 on return so that * the caller can recognize this case. * - * These default configuration settings are determined as - * follows: + * These default configuration settings are determined as + * follows: * * database_path: $MAILDIR, otherwise $HOME/mail * * user_name: $NAME variable if set, otherwise * read from /etc/passwd * - * user_primary_mail: $EMAIL variable if set, otherwise + * user_primary_mail: $EMAIL variable if set, otherwise * constructed from the username and * hostname of the current machine. * From 87df05d181976638e0b3d44d586ce4d5cfe2bd0f Mon Sep 17 00:00:00 2001 From: David Bremner Date: Wed, 12 Jun 2019 19:49:13 -0300 Subject: [PATCH 011/427] STYLE: document rules for calls, block comments, ternary ops --- devel/STYLE | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/devel/STYLE b/devel/STYLE index da653124..b18a9573 100644 --- a/devel/STYLE +++ b/devel/STYLE @@ -53,11 +53,19 @@ function (param_type param, param_type param) if/for/while test) and are preceded by a space. The opening brace of functions is the exception, and starts on a new line. -* Comments are always C-style /* */ block comments. They should start - with a capital letter and generally be written in complete - sentences. Public library functions are documented immediately - before their prototype in lib/notmuch.h. Internal functions are - typically documented immediately before their definition. +* Opening parens also cuddle, even if the first argument does not fit + on the same line. + +* Ternary operators that span a line should be parenthesized like as + "a ? (\n b ) : c". This is mainly to keep the indentation tools + happy. + +* Comments are always C-style /* */ block comments, with a leading * + each line. They should start with a capital letter and generally be + written in complete sentences. Public library functions are + documented immediately before their prototype in lib/notmuch.h. + Internal functions are typically documented immediately before their + definition. * Code lines should be less than 80 columns and comments should be wrapped at 70 columns. From bcfd3e7542b3a004caba16b723c6663c7fd4b015 Mon Sep 17 00:00:00 2001 From: David Bremner Date: Thu, 13 Jun 2019 07:50:44 -0300 Subject: [PATCH 012/427] uncrustify: indent classes With previous settings member functions / variables are moved to column 0. --- devel/uncrustify.cfg | 2 ++ 1 file changed, 2 insertions(+) diff --git a/devel/uncrustify.cfg b/devel/uncrustify.cfg index 6a8769c6..c36c33d6 100644 --- a/devel/uncrustify.cfg +++ b/devel/uncrustify.cfg @@ -117,3 +117,5 @@ align_right_cmt_span = 8 # align comments span this much in func cmt_star_cont = true # indent_brace = 0 + +indent_class = true From be8f0ba92a302798b21cf02ef73c4ad783b66cba Mon Sep 17 00:00:00 2001 From: David Bremner Date: Wed, 12 Jun 2019 19:47:20 -0300 Subject: [PATCH 013/427] CLI: replace some constructs with more uncrustify friendly ones In particular - use (bool) instead of !! - cuddle the opening parens of function calls - add parens in some ternery operators --- command-line-arguments.c | 16 ++++++++-------- mime-node.c | 8 ++++---- notmuch-search.c | 4 ++-- notmuch-show.c | 16 ++++++++-------- 4 files changed, 22 insertions(+), 22 deletions(-) diff --git a/command-line-arguments.c b/command-line-arguments.c index d64aa85b..6699c521 100644 --- a/command-line-arguments.c +++ b/command-line-arguments.c @@ -78,7 +78,7 @@ _process_boolean_arg (const notmuch_opt_desc_t *arg_desc, char next, return OPT_FAILED; } - *arg_desc->opt_bool = negate ? !value : value; + *arg_desc->opt_bool = negate ? (! value) : value; return OPT_OK; } @@ -120,13 +120,13 @@ _process_string_arg (const notmuch_opt_desc_t *arg_desc, char next, const char * static int _opt_set_count (const notmuch_opt_desc_t *opt_desc) { return - !!opt_desc->opt_inherit + - !!opt_desc->opt_bool + - !!opt_desc->opt_int + - !!opt_desc->opt_keyword + - !!opt_desc->opt_flags + - !!opt_desc->opt_string + - !!opt_desc->opt_position; + (bool) opt_desc->opt_inherit + + (bool) opt_desc->opt_bool + + (bool) opt_desc->opt_int + + (bool) opt_desc->opt_keyword + + (bool) opt_desc->opt_flags + + (bool) opt_desc->opt_string + + (bool) opt_desc->opt_position; } /* Return true if opt_desc is valid. */ diff --git a/mime-node.c b/mime-node.c index a93cbb31..4ca51fe9 100644 --- a/mime-node.c +++ b/mime-node.c @@ -185,8 +185,8 @@ node_verify (mime_node_t *node, GMimeObject *part) notmuch_status_t status; node->verify_attempted = true; - node->sig_list = g_mime_multipart_signed_verify - (GMIME_MULTIPART_SIGNED (part), GMIME_ENCRYPT_NONE, &err); + node->sig_list = g_mime_multipart_signed_verify ( + GMIME_MULTIPART_SIGNED (part), GMIME_ENCRYPT_NONE, &err); if (node->sig_list) set_signature_list_destructor (node); @@ -342,8 +342,8 @@ mime_node_child (mime_node_t *parent, int child) if (child == GMIME_MULTIPART_ENCRYPTED_CONTENT && parent->decrypted_child) sub = parent->decrypted_child; else - sub = g_mime_multipart_get_part - (GMIME_MULTIPART (parent->part), child); + sub = g_mime_multipart_get_part ( + GMIME_MULTIPART (parent->part), child); } else if (GMIME_IS_MESSAGE (parent->part)) { sub = g_mime_message_get_mime_part (GMIME_MESSAGE (parent->part)); } else { diff --git a/notmuch-search.c b/notmuch-search.c index e2dee418..e3a85617 100644 --- a/notmuch-search.c +++ b/notmuch-search.c @@ -752,8 +752,8 @@ _notmuch_search_prepare (search_context_t *ctx, notmuch_config_t *config, int ar size_t search_exclude_tags_length; notmuch_status_t status; - search_exclude_tags = notmuch_config_get_search_exclude_tags - (config, &search_exclude_tags_length); + search_exclude_tags = notmuch_config_get_search_exclude_tags ( + config, &search_exclude_tags_length); for (i = 0; i < search_exclude_tags_length; i++) { status = notmuch_query_add_tag_exclude (ctx->query, search_exclude_tags[i]); diff --git a/notmuch-show.c b/notmuch-show.c index 4dfe9c1d..bce7d827 100644 --- a/notmuch-show.c +++ b/notmuch-show.c @@ -490,8 +490,8 @@ format_part_text (const void *ctx, sprinter_t *sp, mime_node_t *node, { /* The disposition and content-type metadata are associated with * the envelope for message parts */ - GMimeObject *meta = node->envelope_part ? - GMIME_OBJECT (node->envelope_part) : node->part; + GMimeObject *meta = node->envelope_part ? ( + GMIME_OBJECT (node->envelope_part) ) : node->part ; GMimeContentType *content_type = g_mime_object_get_content_type (meta); const bool leaf = GMIME_IS_PART (node->part); GMimeStream *stream = params->out_stream; @@ -513,8 +513,8 @@ format_part_text (const void *ctx, sprinter_t *sp, mime_node_t *node, char *content_string; const char *disposition = _get_disposition (meta); const char *cid = g_mime_object_get_content_id (meta); - const char *filename = leaf ? - g_mime_part_get_filename (GMIME_PART (node->part)) : NULL; + const char *filename = leaf ? ( + g_mime_part_get_filename (GMIME_PART (node->part)) ) : NULL ; if (disposition && strcasecmp (disposition, GMIME_DISPOSITION_ATTACHMENT) == 0) @@ -688,14 +688,14 @@ format_part_sprinter (const void *ctx, sprinter_t *sp, mime_node_t *node, /* The disposition and content-type metadata are associated with * the envelope for message parts */ - GMimeObject *meta = node->envelope_part ? - GMIME_OBJECT (node->envelope_part) : node->part; + GMimeObject *meta = node->envelope_part ? ( + GMIME_OBJECT (node->envelope_part) ): node->part; GMimeContentType *content_type = g_mime_object_get_content_type (meta); char *content_string; const char *disposition = _get_disposition (meta); const char *cid = g_mime_object_get_content_id (meta); - const char *filename = GMIME_IS_PART (node->part) ? - g_mime_part_get_filename (GMIME_PART (node->part)) : NULL; + const char *filename = GMIME_IS_PART (node->part) ? ( + g_mime_part_get_filename (GMIME_PART (node->part) ) ) : NULL; int nclose = 0; int i; From 33382c2b5ba2537952a60ea378feff36961e4713 Mon Sep 17 00:00:00 2001 From: uncrustify Date: Thu, 13 Jun 2019 07:31:01 -0300 Subject: [PATCH 014/427] cli: run uncrustify This is the result of running $ uncrustify --replace --config devel/uncrustify.cfg *.c *.h in the top level source directory --- command-line-arguments.c | 56 ++++++------ command-line-arguments.h | 32 +++---- debugger.c | 3 +- gmime-filter-reply.c | 180 +++++++++++++++++++-------------------- gmime-filter-reply.h | 10 +-- hooks.c | 4 +- mime-node.c | 26 +++--- notmuch-client.h | 60 ++++++------- notmuch-config.c | 120 +++++++++++++------------- notmuch-count.c | 10 +-- notmuch-dump.c | 20 ++--- notmuch-insert.c | 10 +-- notmuch-new.c | 86 +++++++++---------- notmuch-reindex.c | 8 +- notmuch-reply.c | 93 ++++++++++---------- notmuch-restore.c | 39 +++++---- notmuch-search.c | 144 +++++++++++++++---------------- notmuch-setup.c | 79 ++++++++--------- notmuch-show.c | 128 ++++++++++++++-------------- notmuch-time.c | 17 ++-- notmuch.c | 49 ++++++----- sprinter.h | 22 ++--- status.c | 4 +- tag-util.h | 18 ++-- 24 files changed, 609 insertions(+), 609 deletions(-) diff --git a/command-line-arguments.c b/command-line-arguments.c index 6699c521..169b12a3 100644 --- a/command-line-arguments.c +++ b/command-line-arguments.c @@ -5,16 +5,16 @@ #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_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. -*/ + * Search the array of keywords for a given argument, assigning the + * output variable to the corresponding value. Return false if nothing + * matches. + */ static opt_handled _process_keyword_arg (const notmuch_opt_desc_t *arg_desc, char next, @@ -84,9 +84,11 @@ _process_boolean_arg (const notmuch_opt_desc_t *arg_desc, char next, } static opt_handled -_process_int_arg (const notmuch_opt_desc_t *arg_desc, char next, const char *arg_str) { +_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 OPT_FAILED; @@ -102,7 +104,8 @@ _process_int_arg (const notmuch_opt_desc_t *arg_desc, char next, const char *arg } static opt_handled -_process_string_arg (const notmuch_opt_desc_t *arg_desc, char next, const char *arg_str) { +_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); @@ -117,7 +120,8 @@ _process_string_arg (const notmuch_opt_desc_t *arg_desc, char next, const char * } /* Return number of non-NULL opt_* fields in opt_desc. */ -static int _opt_set_count (const notmuch_opt_desc_t *opt_desc) +static int +_opt_set_count (const notmuch_opt_desc_t *opt_desc) { return (bool) opt_desc->opt_inherit + @@ -130,7 +134,8 @@ static int _opt_set_count (const notmuch_opt_desc_t *opt_desc) } /* Return true if opt_desc is valid. */ -static bool _opt_valid (const notmuch_opt_desc_t *opt_desc) +static bool +_opt_valid (const notmuch_opt_desc_t *opt_desc) { int n = _opt_set_count (opt_desc); @@ -142,15 +147,17 @@ static bool _opt_valid (const notmuch_opt_desc_t *opt_desc) } /* - Search for the {pos_arg_index}th position argument, return false if - that does not exist. -*/ + * Search for the {pos_arg_index}th position argument, return false if + * that does not exist. + */ bool parse_position_arg (const char *arg_str, int pos_arg_index, - const notmuch_opt_desc_t *arg_desc) { + const notmuch_opt_desc_t *arg_desc) +{ int pos_arg_counter = 0; + while (_opt_valid (arg_desc)) { if (arg_desc->opt_position) { if (pos_arg_counter == pos_arg_index) { @@ -176,12 +183,12 @@ parse_position_arg (const char *arg_str, int pos_arg_index, int parse_option (int argc, char **argv, const notmuch_opt_desc_t *options, int opt_index) { - assert(argv); + assert (argv); const char *_arg = argv[opt_index]; - assert(_arg); - assert(options); + assert (_arg); + assert (options); const char *arg = _arg + 2; /* _arg starts with -- */ const char *negative_arg = NULL; @@ -239,7 +246,7 @@ parse_option (int argc, char **argv, const notmuch_opt_desc_t *options, int opt_ if (lookahead) { next = ' '; value = next_arg; - opt_index ++; + opt_index++; } opt_handled opt_status = OPT_FAILED; @@ -258,12 +265,12 @@ parse_option (int argc, char **argv, const notmuch_opt_desc_t *options, int opt_ return -1; if (lookahead && opt_status == OPT_GIVEBACK) - opt_index --; + opt_index--; if (try->present) *try->present = true; - return opt_index+1; + return opt_index + 1; } return -1; } @@ -271,13 +278,14 @@ parse_option (int argc, char **argv, const notmuch_opt_desc_t *options, int opt_ /* See command-line-arguments.h for description */ int parse_arguments (int argc, char **argv, - const notmuch_opt_desc_t *options, int opt_index) { + const notmuch_opt_desc_t *options, int opt_index) +{ int pos_arg_index = 0; bool more_args = true; while (more_args && opt_index < argc) { - if (strncmp (argv[opt_index],"--",2) != 0) { + if (strncmp (argv[opt_index], "--", 2) != 0) { more_args = parse_position_arg (argv[opt_index], pos_arg_index, options); @@ -290,7 +298,7 @@ parse_arguments (int argc, char **argv, int prev_opt_index = opt_index; if (strlen (argv[opt_index]) == 2) - return opt_index+1; + return opt_index + 1; opt_index = parse_option (argc, argv, options, opt_index); if (opt_index < 0) { diff --git a/command-line-arguments.h b/command-line-arguments.h index f722f97d..606e5cd0 100644 --- a/command-line-arguments.h +++ b/command-line-arguments.h @@ -45,20 +45,20 @@ typedef struct notmuch_opt_desc { /* - This is the main entry point for command line argument parsing. - - Parse command line arguments according to structure options, - starting at position opt_index. - - All output of parsed values is via pointers in options. - - Parsing stops at -- (consumed) or at the (k+1)st argument - not starting with -- (a "positional argument") if options contains - k positional argument descriptors. - - Returns the index of first non-parsed argument, or -1 in case of error. - -*/ + * This is the main entry point for command line argument parsing. + * + * Parse command line arguments according to structure options, + * starting at position opt_index. + * + * All output of parsed values is via pointers in options. + * + * Parsing stops at -- (consumed) or at the (k+1)st argument + * not starting with -- (a "positional argument") if options contains + * k positional argument descriptors. + * + * Returns the index of first non-parsed argument, or -1 in case of error. + * + */ int parse_arguments (int argc, char **argv, const notmuch_opt_desc_t *options, int opt_index); @@ -71,12 +71,12 @@ parse_arguments (int argc, char **argv, const notmuch_opt_desc_t *options, int o */ int -parse_option (int argc, char **argv, const notmuch_opt_desc_t* options, int opt_index); +parse_option (int argc, char **argv, const notmuch_opt_desc_t *options, int opt_index); bool parse_position_arg (const char *arg, int position_arg_index, - const notmuch_opt_desc_t* options); + const notmuch_opt_desc_t *options); #endif diff --git a/debugger.c b/debugger.c index 0febf170..5f47a1d7 100644 --- a/debugger.c +++ b/debugger.c @@ -39,8 +39,7 @@ debugger_is_active (void) sprintf (buf, "/proc/%d/exe", getppid ()); if (readlink (buf, buf2, sizeof (buf2)) != -1 && - strncmp (basename (buf2), "gdb", 3) == 0) - { + strncmp (basename (buf2), "gdb", 3) == 0) { return true; } diff --git a/gmime-filter-reply.c b/gmime-filter-reply.c index 480d9381..2b067669 100644 --- a/gmime-filter-reply.c +++ b/gmime-filter-reply.c @@ -47,143 +47,143 @@ static GMimeFilterClass *parent_class = NULL; GType g_mime_filter_reply_get_type (void) { - static GType type = 0; + static GType type = 0; - if (!type) { - static const GTypeInfo info = { - .class_size = sizeof (GMimeFilterReplyClass), - .base_init = NULL, - .base_finalize = NULL, - .class_init = (GClassInitFunc) g_mime_filter_reply_class_init, - .class_finalize = NULL, - .class_data = NULL, - .instance_size = sizeof (GMimeFilterReply), - .n_preallocs = 0, - .instance_init = (GInstanceInitFunc) g_mime_filter_reply_init, - .value_table = NULL, - }; + if (! type) { + static const GTypeInfo info = { + .class_size = sizeof (GMimeFilterReplyClass), + .base_init = NULL, + .base_finalize = NULL, + .class_init = (GClassInitFunc) g_mime_filter_reply_class_init, + .class_finalize = NULL, + .class_data = NULL, + .instance_size = sizeof (GMimeFilterReply), + .n_preallocs = 0, + .instance_init = (GInstanceInitFunc) g_mime_filter_reply_init, + .value_table = NULL, + }; - type = g_type_register_static (GMIME_TYPE_FILTER, "GMimeFilterReply", &info, (GTypeFlags) 0); - } + type = g_type_register_static (GMIME_TYPE_FILTER, "GMimeFilterReply", &info, (GTypeFlags) 0); + } - return type; + return type; } static void g_mime_filter_reply_class_init (GMimeFilterReplyClass *klass, unused (void *class_data)) { - GObjectClass *object_class = G_OBJECT_CLASS (klass); - GMimeFilterClass *filter_class = GMIME_FILTER_CLASS (klass); + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GMimeFilterClass *filter_class = GMIME_FILTER_CLASS (klass); - parent_class = (GMimeFilterClass *) g_type_class_ref (GMIME_TYPE_FILTER); + parent_class = (GMimeFilterClass *) g_type_class_ref (GMIME_TYPE_FILTER); - object_class->finalize = g_mime_filter_reply_finalize; + object_class->finalize = g_mime_filter_reply_finalize; - filter_class->copy = filter_copy; - filter_class->filter = filter_filter; - filter_class->complete = filter_complete; - filter_class->reset = filter_reset; + filter_class->copy = filter_copy; + filter_class->filter = filter_filter; + filter_class->complete = filter_complete; + filter_class->reset = filter_reset; } static void g_mime_filter_reply_init (GMimeFilterReply *filter, GMimeFilterReplyClass *klass) { - (void) klass; - filter->saw_nl = true; - filter->saw_angle = false; + (void) klass; + filter->saw_nl = true; + filter->saw_angle = false; } static void g_mime_filter_reply_finalize (GObject *object) { - G_OBJECT_CLASS (parent_class)->finalize (object); + G_OBJECT_CLASS (parent_class)->finalize (object); } static GMimeFilter * filter_copy (GMimeFilter *filter) { - GMimeFilterReply *reply = (GMimeFilterReply *) filter; + GMimeFilterReply *reply = (GMimeFilterReply *) filter; - return g_mime_filter_reply_new (reply->encode); + return g_mime_filter_reply_new (reply->encode); } static void filter_filter (GMimeFilter *filter, char *inbuf, size_t inlen, size_t prespace, char **outbuf, size_t *outlen, size_t *outprespace) { - GMimeFilterReply *reply = (GMimeFilterReply *) filter; - const char *inptr = inbuf; - const char *inend = inbuf + inlen; - char *outptr; + GMimeFilterReply *reply = (GMimeFilterReply *) filter; + const char *inptr = inbuf; + const char *inend = inbuf + inlen; + char *outptr; - (void) prespace; - if (reply->encode) { - g_mime_filter_set_size (filter, 3 * inlen, false); + (void) prespace; + if (reply->encode) { + g_mime_filter_set_size (filter, 3 * inlen, false); - outptr = filter->outbuf; - while (inptr < inend) { - if (reply->saw_nl) { - *outptr++ = '>'; - *outptr++ = ' '; - reply->saw_nl = false; - } - if (*inptr == '\n') - reply->saw_nl = true; - else - reply->saw_nl = false; - if (*inptr != '\r') - *outptr++ = *inptr; - inptr++; - } - } else { - g_mime_filter_set_size (filter, inlen + 1, false); - - outptr = filter->outbuf; - while (inptr < inend) { - if (reply->saw_nl) { - if (*inptr == '>') - reply->saw_angle = true; - else - *outptr++ = *inptr; - reply->saw_nl = false; - } else if (reply->saw_angle) { - if (*inptr == ' ') - ; - else - *outptr++ = *inptr; - reply->saw_angle = false; - } else if (*inptr != '\r') { - if (*inptr == '\n') - reply->saw_nl = true; - *outptr++ = *inptr; - } - - inptr++; - } + outptr = filter->outbuf; + while (inptr < inend) { + if (reply->saw_nl) { + *outptr++ = '>'; + *outptr++ = ' '; + reply->saw_nl = false; + } + if (*inptr == '\n') + reply->saw_nl = true; + else + reply->saw_nl = false; + if (*inptr != '\r') + *outptr++ = *inptr; + inptr++; } + } else { + g_mime_filter_set_size (filter, inlen + 1, false); - *outlen = outptr - filter->outbuf; - *outprespace = filter->outpre; - *outbuf = filter->outbuf; + outptr = filter->outbuf; + while (inptr < inend) { + if (reply->saw_nl) { + if (*inptr == '>') + reply->saw_angle = true; + else + *outptr++ = *inptr; + reply->saw_nl = false; + } else if (reply->saw_angle) { + if (*inptr == ' ') + ; + else + *outptr++ = *inptr; + reply->saw_angle = false; + } else if (*inptr != '\r') { + if (*inptr == '\n') + reply->saw_nl = true; + *outptr++ = *inptr; + } + + inptr++; + } + } + + *outlen = outptr - filter->outbuf; + *outprespace = filter->outpre; + *outbuf = filter->outbuf; } static void filter_complete (GMimeFilter *filter, char *inbuf, size_t inlen, size_t prespace, char **outbuf, size_t *outlen, size_t *outprespace) { - if (inbuf && inlen) - filter_filter (filter, inbuf, inlen, prespace, outbuf, outlen, outprespace); + if (inbuf && inlen) + filter_filter (filter, inbuf, inlen, prespace, outbuf, outlen, outprespace); } static void filter_reset (GMimeFilter *filter) { - GMimeFilterReply *reply = (GMimeFilterReply *) filter; + GMimeFilterReply *reply = (GMimeFilterReply *) filter; - reply->saw_nl = true; - reply->saw_angle = false; + reply->saw_nl = true; + reply->saw_angle = false; } @@ -202,11 +202,11 @@ filter_reset (GMimeFilter *filter) GMimeFilter * g_mime_filter_reply_new (gboolean encode) { - GMimeFilterReply *new_reply; + GMimeFilterReply *new_reply; - new_reply = (GMimeFilterReply *) g_object_new (GMIME_TYPE_FILTER_REPLY, NULL); - new_reply->encode = encode; + new_reply = (GMimeFilterReply *) g_object_new (GMIME_TYPE_FILTER_REPLY, NULL); + new_reply->encode = encode; - return (GMimeFilter *) new_reply; + return (GMimeFilter *) new_reply; } diff --git a/gmime-filter-reply.h b/gmime-filter-reply.h index b7cbc6b1..5a1e606e 100644 --- a/gmime-filter-reply.h +++ b/gmime-filter-reply.h @@ -43,15 +43,15 @@ typedef struct _GMimeFilterReplyClass GMimeFilterReplyClass; * A filter to insert/remove reply markers (lines beginning with >) **/ struct _GMimeFilterReply { - GMimeFilter parent_object; + GMimeFilter parent_object; - gboolean encode; - gboolean saw_nl; - gboolean saw_angle; + gboolean encode; + gboolean saw_nl; + gboolean saw_angle; }; struct _GMimeFilterReplyClass { - GMimeFilterClass parent_class; + GMimeFilterClass parent_class; }; diff --git a/hooks.c b/hooks.c index 7348d322..59c58070 100644 --- a/hooks.c +++ b/hooks.c @@ -53,7 +53,7 @@ notmuch_run_hook (const char *db_path, const char *hook) /* Flush any buffered output before forking. */ fflush (stdout); - pid = fork(); + pid = fork (); if (pid == -1) { fprintf (stderr, "Error: %s hook fork failed: %s\n", hook, strerror (errno)); @@ -78,7 +78,7 @@ notmuch_run_hook (const char *db_path, const char *hook) goto DONE; } - if (!WIFEXITED (status) || WEXITSTATUS (status)) { + if (! WIFEXITED (status) || WEXITSTATUS (status)) { if (WIFEXITED (status)) { fprintf (stderr, "Error: %s hook failed with status %d\n", hook, WEXITSTATUS (status)); diff --git a/mime-node.c b/mime-node.c index 4ca51fe9..3133ca44 100644 --- a/mime-node.c +++ b/mime-node.c @@ -55,7 +55,7 @@ _mime_node_context_free (mime_node_context_t *res) return 0; } -const _notmuch_message_crypto_t* +const _notmuch_message_crypto_t * mime_node_get_message_crypto_status (mime_node_t *node) { return node->ctx->msg_crypto; @@ -97,8 +97,7 @@ mime_node_open (const void *ctx, notmuch_message_t *message, notmuch_filenames_t *filenames; for (filenames = notmuch_message_get_filenames (message); notmuch_filenames_valid (filenames); - notmuch_filenames_move_to_next (filenames)) - { + notmuch_filenames_move_to_next (filenames)) { filename = notmuch_filenames_get (filenames); fd = open (filename, O_RDONLY); if (fd != -1) @@ -109,27 +108,27 @@ mime_node_open (const void *ctx, notmuch_message_t *message, if (fd == -1) { /* Give up */ fprintf (stderr, "Error opening %s: %s\n", filename, strerror (errno)); - status = NOTMUCH_STATUS_FILE_ERROR; - goto DONE; - } + status = NOTMUCH_STATUS_FILE_ERROR; + goto DONE; } + } mctx->stream = g_mime_stream_gzfile_new (fd); - if (!mctx->stream) { + if (! mctx->stream) { fprintf (stderr, "Out of memory.\n"); status = NOTMUCH_STATUS_OUT_OF_MEMORY; goto DONE; } mctx->parser = g_mime_parser_new_with_stream (mctx->stream); - if (!mctx->parser) { + if (! mctx->parser) { fprintf (stderr, "Out of memory.\n"); status = NOTMUCH_STATUS_OUT_OF_MEMORY; goto DONE; } mctx->mime_message = g_mime_parser_construct_message (mctx->parser, NULL); - if (!mctx->mime_message) { + if (! mctx->mime_message) { fprintf (stderr, "Failed to parse %s\n", filename); status = NOTMUCH_STATUS_FILE_ERROR; goto DONE; @@ -153,7 +152,7 @@ mime_node_open (const void *ctx, notmuch_message_t *message, *root_out = root; return NOTMUCH_STATUS_SUCCESS; -DONE: + DONE: talloc_free (root); return status; } @@ -171,6 +170,7 @@ static void set_signature_list_destructor (mime_node_t *node) { GMimeSignatureList **proxy = talloc (node, GMimeSignatureList *); + if (proxy) { *proxy = node->sig_list; talloc_set_destructor (proxy, _signature_list_free); @@ -259,7 +259,7 @@ node_decrypt_and_verify (mime_node_t *node, GMimeObject *part) g_object_unref (decrypt_result); } - DONE: + DONE: if (err) g_error_free (err); } @@ -273,7 +273,7 @@ _mime_node_create (mime_node_t *parent, GMimeObject *part, int numchild) /* Set basic node properties */ node->part = part; node->ctx = parent->ctx; - if (!talloc_reference (node, node->ctx)) { + if (! talloc_reference (node, node->ctx)) { fprintf (stderr, "Out of memory.\n"); talloc_free (node); return NULL; @@ -335,7 +335,7 @@ mime_node_child (mime_node_t *parent, int child) GMimeObject *sub; mime_node_t *node; - if (!parent || !parent->part || child < 0 || child >= parent->nchildren) + if (! parent || ! parent->part || child < 0 || child >= parent->nchildren) return NULL; if (GMIME_IS_MULTIPART (parent->part)) { diff --git a/notmuch-client.h b/notmuch-client.h index b3a501a9..d1b78017 100644 --- a/notmuch-client.h +++ b/notmuch-client.h @@ -55,7 +55,7 @@ #define unused(x) x ## _unused __attribute__ ((unused)) -#define STRINGIFY(s) STRINGIFY_(s) +#define STRINGIFY(s) STRINGIFY_ (s) #define STRINGIFY_(s) #s typedef struct mime_node mime_node_t; @@ -63,10 +63,10 @@ struct sprinter; struct notmuch_show_params; typedef struct notmuch_show_format { - struct sprinter *(*new_sprinter) (const void *ctx, FILE *stream); - notmuch_status_t (*part) (const void *ctx, struct sprinter *sprinter, - struct mime_node *node, int indent, - const struct notmuch_show_params *params); + struct sprinter *(*new_sprinter)(const void *ctx, FILE *stream); + notmuch_status_t (*part)(const void *ctx, struct sprinter *sprinter, + struct mime_node *node, int indent, + const struct notmuch_show_params *params); } notmuch_show_format_t; typedef struct notmuch_show_params { @@ -85,12 +85,12 @@ typedef struct notmuch_show_params { * * Note that __location__ comes from talloc.h. */ -#define INTERNAL_ERROR(format, ...) \ - do { \ - fprintf(stderr, \ - "Internal error: " format " (%s)\n", \ - ##__VA_ARGS__, __location__); \ - exit (1); \ +#define INTERNAL_ERROR(format, ...) \ + do { \ + fprintf (stderr, \ + "Internal error: " format " (%s)\n", \ + ##__VA_ARGS__, __location__); \ + exit (1); \ } while (0) #define ARRAY_SIZE(arr) (sizeof (arr) / sizeof (arr[0])) @@ -101,8 +101,8 @@ typedef struct notmuch_show_params { static inline void chomp_newline (char *str) { - if (str && str[strlen(str)-1] == '\n') - str[strlen(str)-1] = '\0'; + if (str && str[strlen (str) - 1] == '\n') + str[strlen (str) - 1] = '\0'; } /* Exit status code indicating temporary failure; user is invited to @@ -251,8 +251,8 @@ json_quote_str (const void *ctx, const char *str); /* notmuch-config.c */ typedef enum { - NOTMUCH_CONFIG_OPEN = 1 << 0, - NOTMUCH_CONFIG_CREATE = 1 << 1, + NOTMUCH_CONFIG_OPEN = 1 << 0, + NOTMUCH_CONFIG_CREATE = 1 << 1, } notmuch_config_mode_t; notmuch_config_t * @@ -328,8 +328,8 @@ notmuch_config_get_search_exclude_tags (notmuch_config_t *config, size_t *length void notmuch_config_set_search_exclude_tags (notmuch_config_t *config, - const char *list[], - size_t length); + const char *list[], + size_t length); int notmuch_run_hook (const char *db_path, const char *hook); @@ -388,7 +388,7 @@ struct mime_node { /* The list of signatures for signed or encrypted containers. If * there are no signatures, this will be NULL. */ - GMimeSignatureList* sig_list; + GMimeSignatureList *sig_list; /* Internal: Context inherited from the root iterator. */ struct mime_node_context *ctx; @@ -435,11 +435,11 @@ mime_node_t * mime_node_child (mime_node_t *parent, int child); /* Return the nth child of node in a depth-first traversal. If n is - * 0, returns node itself. Returns NULL if there is no such part. */ +* 0, returns node itself. Returns NULL if there is no such part. */ mime_node_t * mime_node_seek_dfs (mime_node_t *node, int n); -const _notmuch_message_crypto_t* +const _notmuch_message_crypto_t * mime_node_get_message_crypto_status (mime_node_t *node); typedef enum dump_formats { @@ -449,9 +449,9 @@ typedef enum dump_formats { } dump_format_t; typedef enum dump_includes { - DUMP_INCLUDE_TAGS = 1, - DUMP_INCLUDE_CONFIG = 2, - DUMP_INCLUDE_PROPERTIES = 4 + DUMP_INCLUDE_TAGS = 1, + DUMP_INCLUDE_CONFIG = 2, + DUMP_INCLUDE_PROPERTIES = 4 } dump_include_t; #define DUMP_INCLUDE_DEFAULT (DUMP_INCLUDE_TAGS | DUMP_INCLUDE_CONFIG | DUMP_INCLUDE_PROPERTIES) @@ -467,8 +467,8 @@ notmuch_database_dump (notmuch_database_t *notmuch, bool gzip_output); /* If status is non-zero (i.e. error) print appropriate - messages to stderr. -*/ + * messages to stderr. + */ notmuch_status_t print_status_query (const char *loc, @@ -491,11 +491,11 @@ status_to_exit (notmuch_status_t status); #include "command-line-arguments.h" extern const char *notmuch_requested_db_uuid; -extern const notmuch_opt_desc_t notmuch_shared_options []; +extern const notmuch_opt_desc_t notmuch_shared_options []; void notmuch_exit_if_unmatched_db_uuid (notmuch_database_t *notmuch); -void notmuch_process_shared_options (const char* subcommand_name); -int notmuch_minimal_options (const char* subcommand_name, +void notmuch_process_shared_options (const char *subcommand_name); +int notmuch_minimal_options (const char *subcommand_name, int argc, char **argv); @@ -504,10 +504,10 @@ int notmuch_minimal_options (const char* subcommand_name, struct _notmuch_client_indexing_cli_choices { int decrypt_policy; bool decrypt_policy_set; - notmuch_indexopts_t * opts; + notmuch_indexopts_t *opts; }; extern struct _notmuch_client_indexing_cli_choices indexing_cli_choices; -extern const notmuch_opt_desc_t notmuch_shared_indexing_options []; +extern const notmuch_opt_desc_t notmuch_shared_indexing_options []; notmuch_status_t notmuch_process_shared_indexing_options (notmuch_database_t *notmuch); diff --git a/notmuch-config.c b/notmuch-config.c index 91a24a76..1b079e85 100644 --- a/notmuch-config.c +++ b/notmuch-config.c @@ -151,14 +151,14 @@ get_name_from_passwd_file (void *ctx) char *name; int e; - pw_buf_size = sysconf(_SC_GETPW_R_SIZE_MAX); + pw_buf_size = sysconf (_SC_GETPW_R_SIZE_MAX); if (pw_buf_size == -1) pw_buf_size = 64; pw_buf = talloc_size (ctx, pw_buf_size); while ((e = getpwuid_r (getuid (), &passwd, pw_buf, - pw_buf_size, &ignored)) == ERANGE) { - pw_buf_size = pw_buf_size * 2; - pw_buf = talloc_zero_size(ctx, pw_buf_size); + pw_buf_size, &ignored)) == ERANGE) { + pw_buf_size = pw_buf_size * 2; + pw_buf = talloc_zero_size (ctx, pw_buf_size); } if (e == 0) { @@ -186,14 +186,14 @@ get_username_from_passwd_file (void *ctx) char *name; int e; - pw_buf_size = sysconf(_SC_GETPW_R_SIZE_MAX); + pw_buf_size = sysconf (_SC_GETPW_R_SIZE_MAX); if (pw_buf_size == -1) pw_buf_size = 64; pw_buf = talloc_zero_size (ctx, pw_buf_size); while ((e = getpwuid_r (getuid (), &passwd, pw_buf, - pw_buf_size, &ignored)) == ERANGE) { - pw_buf_size = pw_buf_size * 2; - pw_buf = talloc_zero_size(ctx, pw_buf_size); + pw_buf_size, &ignored)) == ERANGE) { + pw_buf_size = pw_buf_size * 2; + pw_buf = talloc_zero_size (ctx, pw_buf_size); } if (e == 0) @@ -217,7 +217,7 @@ get_config_from_file (notmuch_config_t *config, bool create_new) GError *error = NULL; bool ret = false; - FILE *fp = fopen(config->filename, "r"); + FILE *fp = fopen (config->filename, "r"); if (fp == NULL) { if (errno == ENOENT) { /* If create_new is true, then the caller is prepared for a @@ -233,7 +233,7 @@ get_config_from_file (notmuch_config_t *config, bool create_new) } } else { fprintf (stderr, "Error opening config file '%s': %s\n", - config->filename, strerror(errno)); + config->filename, strerror (errno)); } goto out; } @@ -274,12 +274,12 @@ get_config_from_file (notmuch_config_t *config, bool create_new) g_error_free (error); -out: + out: if (fp) - fclose(fp); + fclose (fp); if (config_str) - talloc_free(config_str); + talloc_free (config_str); return ret; } @@ -300,7 +300,7 @@ out: * * If is_new_ret is NULL, then a "file not found" message will be * printed to stderr and NULL will be returned. - + * * If is_new_ret is non-NULL then a default configuration will be * returned and *is_new_ret will be set to 1 on return so that * the caller can recognize this case. @@ -338,11 +338,12 @@ notmuch_config_open (void *ctx, int file_had_crypto_group; notmuch_config_t *config = talloc_zero (ctx, notmuch_config_t); + if (config == NULL) { fprintf (stderr, "Out of memory.\n"); return NULL; } - + talloc_set_destructor (config, notmuch_config_destructor); /* non-zero defaults */ @@ -438,7 +439,7 @@ notmuch_config_open (void *ctx, } if (notmuch_config_get_new_tags (config, &tmp) == NULL) { - const char *tags[] = { "unread", "inbox" }; + const char *tags[] = { "unread", "inbox" }; notmuch_config_set_new_tags (config, tags, 2); } @@ -499,11 +500,11 @@ notmuch_config_open (void *ctx, } /* Close the given notmuch_config_t object, freeing all resources. - * + * * Note: Any changes made to the configuration are *not* saved by this * function. To save changes, call notmuch_config_save before * notmuch_config_close. -*/ + */ void notmuch_config_close (notmuch_config_t *config) { @@ -605,13 +606,13 @@ _config_get_list (notmuch_config_t *config, const char *section, const char *key, const char ***outlist, size_t *list_length, size_t *ret_length) { - assert(outlist); + assert (outlist); /* read from config file and cache value, if not cached already */ if (*outlist == NULL) { char **inlist = g_key_file_get_string_list (config->key_file, - section, key, list_length, NULL); + section, key, list_length, NULL); if (inlist) { unsigned int i; @@ -648,7 +649,7 @@ _config_set_list (notmuch_config_t *config, const char * notmuch_config_get_database_path (notmuch_config_t *config) { - char *db_path = (char *)_config_get (config, &config->database_path, "database", "path"); + char *db_path = (char *) _config_get (config, &config->database_path, "database", "path"); if (db_path && *db_path != '/') { /* If the path in the configuration file begins with any @@ -726,16 +727,16 @@ notmuch_config_set_user_other_email (notmuch_config_t *config, size_t length) { _config_set_list (config, "user", "other_email", list, length, - &(config->user_other_email)); + &(config->user_other_email)); } void notmuch_config_set_new_tags (notmuch_config_t *config, - const char *list[], - size_t length) + const char *list[], + size_t length) { _config_set_list (config, "new", "tags", list, length, - &(config->new_tags)); + &(config->new_tags)); } void @@ -744,7 +745,7 @@ notmuch_config_set_new_ignore (notmuch_config_t *config, size_t length) { _config_set_list (config, "new", "ignore", list, length, - &(config->new_ignore)); + &(config->new_ignore)); } const char ** @@ -757,8 +758,8 @@ notmuch_config_get_search_exclude_tags (notmuch_config_t *config, size_t *length void notmuch_config_set_search_exclude_tags (notmuch_config_t *config, - const char *list[], - size_t length) + const char *list[], + size_t length) { _config_set_list (config, "search", "exclude_tags", list, length, &(config->search_exclude_tags)); @@ -779,7 +780,7 @@ _item_split (char *item, char **group, char **key) *group = item; period = strchr (item, '.'); - if (period == NULL || *(period+1) == '\0') { + if (period == NULL || *(period + 1) == '\0') { fprintf (stderr, "Invalid configuration name: %s\n" "(Should be of the form
.)\n", item); @@ -793,7 +794,7 @@ _item_split (char *item, char **group, char **key) } /* These are more properly called Xapian fields, but the user facing - docs call them prefixes, so make the error message match */ + * docs call them prefixes, so make the error message match */ static bool validate_field_name (const char *str) { @@ -839,10 +840,10 @@ typedef struct config_key { } config_key_info_t; static struct config_key -config_key_table[] = { - {"index.decrypt", true, false, NULL}, - {"index.header.", true, true, validate_field_name}, - {"query.", true, true, NULL}, + config_key_table[] = { + { "index.decrypt", true, false, NULL }, + { "index.header.", true, true, validate_field_name }, + { "query.", true, true, NULL }, }; static config_key_info_t * @@ -851,10 +852,10 @@ _config_key_info (const char *item) for (size_t i = 0; i < ARRAY_SIZE (config_key_table); i++) { if (config_key_table[i].prefix && strncmp (item, config_key_table[i].name, - strlen(config_key_table[i].name)) == 0) - return config_key_table+i; + strlen (config_key_table[i].name)) == 0) + return config_key_table + i; if (strcmp (item, config_key_table[i].name) == 0) - return config_key_table+i; + return config_key_table + i; } return NULL; } @@ -863,13 +864,14 @@ static bool _stored_in_db (const char *item) { config_key_info_t *info; + info = _config_key_info (item); return (info && info->in_db); } static int -_print_db_config(notmuch_config_t *config, const char *name) +_print_db_config (notmuch_config_t *config, const char *name) { notmuch_database_t *notmuch; char *val; @@ -884,7 +886,7 @@ _print_db_config(notmuch_config_t *config, const char *name) notmuch_database_get_config (notmuch, name, &val))) return EXIT_FAILURE; - puts (val); + puts (val); return EXIT_SUCCESS; } @@ -892,20 +894,20 @@ _print_db_config(notmuch_config_t *config, const char *name) static int notmuch_config_command_get (notmuch_config_t *config, char *item) { - if (strcmp(item, "database.path") == 0) { + if (strcmp (item, "database.path") == 0) { printf ("%s\n", notmuch_config_get_database_path (config)); - } else if (strcmp(item, "user.name") == 0) { + } else if (strcmp (item, "user.name") == 0) { printf ("%s\n", notmuch_config_get_user_name (config)); - } else if (strcmp(item, "user.primary_email") == 0) { + } else if (strcmp (item, "user.primary_email") == 0) { printf ("%s\n", notmuch_config_get_user_primary_email (config)); - } else if (strcmp(item, "user.other_email") == 0) { + } else if (strcmp (item, "user.other_email") == 0) { const char **other_email; size_t i, length; - + other_email = notmuch_config_get_user_other_email (config, &length); for (i = 0; i < length; i++) printf ("%s\n", other_email[i]); - } else if (strcmp(item, "new.tags") == 0) { + } else if (strcmp (item, "new.tags") == 0) { const char **tags; size_t i, length; @@ -944,7 +946,7 @@ notmuch_config_command_get (notmuch_config_t *config, char *item) } static int -_set_db_config(notmuch_config_t *config, const char *key, int argc, char **argv) +_set_db_config (notmuch_config_t *config, const char *key, int argc, char **argv) { notmuch_database_t *notmuch; const char *val = ""; @@ -1025,15 +1027,15 @@ static void _notmuch_config_list_built_with () { - printf("%scompact=%s\n", - BUILT_WITH_PREFIX, - notmuch_built_with ("compact") ? "true" : "false"); - printf("%sfield_processor=%s\n", - BUILT_WITH_PREFIX, - notmuch_built_with ("field_processor") ? "true" : "false"); - printf("%sretry_lock=%s\n", - BUILT_WITH_PREFIX, - notmuch_built_with ("retry_lock") ? "true" : "false"); + printf ("%scompact=%s\n", + BUILT_WITH_PREFIX, + notmuch_built_with ("compact") ? "true" : "false"); + printf ("%sfield_processor=%s\n", + BUILT_WITH_PREFIX, + notmuch_built_with ("field_processor") ? "true" : "false"); + printf ("%sretry_lock=%s\n", + BUILT_WITH_PREFIX, + notmuch_built_with ("retry_lock") ? "true" : "false"); } static int @@ -1054,11 +1056,11 @@ _list_db_config (notmuch_config_t *config) return EXIT_FAILURE; for (; notmuch_config_list_valid (list); notmuch_config_list_move_to_next (list)) { - printf("%s=%s\n", notmuch_config_list_key (list), notmuch_config_list_value(list)); + printf ("%s=%s\n", notmuch_config_list_key (list), notmuch_config_list_value (list)); } notmuch_config_list_destroy (list); - return EXIT_SUCCESS; + return EXIT_SUCCESS; } static int @@ -1115,8 +1117,8 @@ notmuch_config_command (notmuch_config_t *config, int argc, char *argv[]) notmuch_requested_db_uuid); /* skip at least subcommand argument */ - argc-= opt_index; - argv+= opt_index; + argc -= opt_index; + argv += opt_index; if (argc < 1) { fprintf (stderr, "Error: notmuch config requires at least one argument.\n"); diff --git a/notmuch-count.c b/notmuch-count.c index ca05c979..d8ad7d6d 100644 --- a/notmuch-count.c +++ b/notmuch-count.c @@ -165,10 +165,10 @@ notmuch_count_command (notmuch_config_t *config, int argc, char *argv[]) notmuch_opt_desc_t options[] = { { .opt_keyword = &output, .name = "output", .keywords = - (notmuch_keyword_t []){ { "threads", OUTPUT_THREADS }, - { "messages", OUTPUT_MESSAGES }, - { "files", OUTPUT_FILES }, - { 0, 0 } } }, + (notmuch_keyword_t []){ { "threads", OUTPUT_THREADS }, + { "messages", OUTPUT_MESSAGES }, + { "files", OUTPUT_FILES }, + { 0, 0 } } }, { .opt_bool = &exclude, .name = "exclude" }, { .opt_bool = &print_lastmod, .name = "lastmod" }, { .opt_bool = &batch, .name = "batch" }, @@ -214,7 +214,7 @@ notmuch_count_command (notmuch_config_t *config, int argc, char *argv[]) if (exclude) { search_exclude_tags = notmuch_config_get_search_exclude_tags - (config, &search_exclude_tags_length); + (config, &search_exclude_tags_length); } if (batch) diff --git a/notmuch-dump.c b/notmuch-dump.c index ef2f02df..505c1469 100644 --- a/notmuch-dump.c +++ b/notmuch-dump.c @@ -56,7 +56,7 @@ database_dump_config (notmuch_database_t *notmuch, gzFile output) ret = EXIT_SUCCESS; - DONE: + DONE: if (list) notmuch_config_list_destroy (list); @@ -220,7 +220,7 @@ database_dump_file (notmuch_database_t *notmuch, gzFile output, if (include & DUMP_INCLUDE_CONFIG) { if (print_status_database ("notmuch dump", notmuch, - database_dump_config(notmuch,output))) + database_dump_config (notmuch, output))) return EXIT_FAILURE; } @@ -307,7 +307,7 @@ notmuch_database_dump (notmuch_database_t *notmuch, name_for_error, strerror (errno)); if (close (outfd)) fprintf (stderr, "Error closing %s during shutdown: %s\n", - name_for_error, strerror (errno)); + name_for_error, strerror (errno)); goto DONE; } @@ -346,7 +346,7 @@ notmuch_database_dump (notmuch_database_t *notmuch, } } - DONE: + DONE: if (ret != EXIT_SUCCESS && output) (void) gzclose_w (output); @@ -378,13 +378,13 @@ notmuch_dump_command (notmuch_config_t *config, int argc, char *argv[]) notmuch_opt_desc_t options[] = { { .opt_keyword = &output_format, .name = "format", .keywords = - (notmuch_keyword_t []){ { "sup", DUMP_FORMAT_SUP }, - { "batch-tag", DUMP_FORMAT_BATCH_TAG }, - { 0, 0 } } }, + (notmuch_keyword_t []){ { "sup", DUMP_FORMAT_SUP }, + { "batch-tag", DUMP_FORMAT_BATCH_TAG }, + { 0, 0 } } }, { .opt_flags = &include, .name = "include", .keywords = - (notmuch_keyword_t []){ { "config", DUMP_INCLUDE_CONFIG }, - { "properties", DUMP_INCLUDE_PROPERTIES }, - { "tags", DUMP_INCLUDE_TAGS} } }, + (notmuch_keyword_t []){ { "config", DUMP_INCLUDE_CONFIG }, + { "properties", DUMP_INCLUDE_PROPERTIES }, + { "tags", DUMP_INCLUDE_TAGS } } }, { .opt_string = &output_file_name, .name = "output" }, { .opt_bool = &gzip_output, .name = "gzip" }, { .opt_inherit = notmuch_shared_options }, diff --git a/notmuch-insert.c b/notmuch-insert.c index 327470d4..1d3b0150 100644 --- a/notmuch-insert.c +++ b/notmuch-insert.c @@ -99,7 +99,7 @@ is_valid_folder_name (const char *folder) if ((p[0] == '.') && (p[1] == '.') && (p[2] == '\0' || p[2] == '/')) return false; p = strchr (p, '/'); - if (!p) + if (! p) return true; p++; } @@ -120,7 +120,7 @@ mkdir_recursive (const void *ctx, const char *path, int mode) /* First check the common case: directory already exists. */ r = stat (path, &st); if (r == 0) { - if (! S_ISDIR (st.st_mode)) { + if (! S_ISDIR (st.st_mode)) { fprintf (stderr, "Error: '%s' is not a directory: %s\n", path, strerror (EEXIST)); return false; @@ -282,7 +282,7 @@ copy_fd (int fdout, int fdin) } while (remain > 0); } - return (!interrupted && !empty); + return (! interrupted && ! empty); } /* @@ -311,7 +311,7 @@ maildir_write_tmp (const void *ctx, int fdin, const char *maildir, bool world_re return path; -FAIL: + FAIL: close (fdout); unlink (path); @@ -360,7 +360,7 @@ maildir_write_new (const void *ctx, int fdin, const char *maildir, bool world_re return newpath; -FAIL: + FAIL: unlink (cleanpath); return NULL; diff --git a/notmuch-new.c b/notmuch-new.c index 184e9aa2..f079f62a 100644 --- a/notmuch-new.c +++ b/notmuch-new.c @@ -87,7 +87,7 @@ handle_sigint (unused (int sig)) * result. It is not required for correctness, and if it does * fail or produce a short write, we want to get out of the signal * handler as quickly as possible, not retry it. */ - IGNORE_RESULT (write (2, msg, sizeof(msg)-1)); + IGNORE_RESULT (write (2, msg, sizeof (msg) - 1)); interrupted = 1; } @@ -184,23 +184,23 @@ dirent_type (const char *path, const struct dirent *entry) /* Mapping from d_type to stat mode_t. We omit DT_LNK so that * we'll fall through to stat and get the real file type. */ static const mode_t modes[] = { - [DT_BLK] = S_IFBLK, - [DT_CHR] = S_IFCHR, - [DT_DIR] = S_IFDIR, + [DT_BLK] = S_IFBLK, + [DT_CHR] = S_IFCHR, + [DT_DIR] = S_IFDIR, [DT_FIFO] = S_IFIFO, - [DT_REG] = S_IFREG, + [DT_REG] = S_IFREG, [DT_SOCK] = S_IFSOCK }; - if (entry->d_type < ARRAY_SIZE(modes) && modes[entry->d_type]) + if (entry->d_type < ARRAY_SIZE (modes) && modes[entry->d_type]) return modes[entry->d_type]; #endif abspath = talloc_asprintf (NULL, "%s/%s", path, entry->d_name); - if (!abspath) { + if (! abspath) { errno = ENOMEM; return -1; } - err = stat(abspath, &statbuf); + err = stat (abspath, &statbuf); saved_errno = errno; talloc_free (abspath); if (err < 0) { @@ -226,10 +226,9 @@ _entries_resemble_maildir (const char *path, struct dirent **entries, int count) if (dirent_type (path, entries[i]) != S_IFDIR) continue; - if (strcmp(entries[i]->d_name, "new") == 0 || - strcmp(entries[i]->d_name, "cur") == 0 || - strcmp(entries[i]->d_name, "tmp") == 0) - { + if (strcmp (entries[i]->d_name, "new") == 0 || + strcmp (entries[i]->d_name, "cur") == 0 || + strcmp (entries[i]->d_name, "tmp") == 0) { found++; if (found == 3) return 1; @@ -389,8 +388,8 @@ add_file (notmuch_database_t *notmuch, const char *filename, notmuch_message_maildir_flags_to_tags (message); for (tag = state->new_tags; *tag != NULL; tag++) { - if (strcmp ("unread", *tag) !=0 || - !notmuch_message_has_maildir_flag (message, 'S')) { + if (strcmp ("unread", *tag) != 0 || + ! notmuch_message_has_maildir_flag (message, 'S')) { notmuch_message_add_tag (message, *tag); } } @@ -415,7 +414,7 @@ add_file (notmuch_database_t *notmuch, const char *filename, case NOTMUCH_STATUS_READ_ONLY_DATABASE: case NOTMUCH_STATUS_XAPIAN_EXCEPTION: case NOTMUCH_STATUS_OUT_OF_MEMORY: - (void) print_status_database("add_file", notmuch, status); + (void) print_status_database ("add_file", notmuch, status); goto DONE; default: INTERNAL_ERROR ("add_message returned unexpected value: %d", status); @@ -534,7 +533,7 @@ add_files (notmuch_database_t *notmuch, * file system link count. So, only bail early if the * database agrees that there are no sub-directories. */ db_subdirs = notmuch_directory_get_child_directories (directory); - if (!notmuch_filenames_valid (db_subdirs)) + if (! notmuch_filenames_valid (db_subdirs)) goto DONE; notmuch_filenames_destroy (db_subdirs); db_subdirs = NULL; @@ -631,7 +630,7 @@ add_files (notmuch_database_t *notmuch, /* Pass 2: Scan for new files, removed files, and removed directories. */ for (i = 0; i < num_fs_entries && ! interrupted; i++) { - entry = fs_entries[i]; + entry = fs_entries[i]; /* Ignore special directories early. */ if (_special_directory (entry->d_name)) @@ -648,8 +647,7 @@ add_files (notmuch_database_t *notmuch, /* Check if we've walked past any names in db_files or * db_subdirs. If so, these have been deleted. */ while (notmuch_filenames_valid (db_files) && - strcmp (notmuch_filenames_get (db_files), entry->d_name) < 0) - { + strcmp (notmuch_filenames_get (db_files), entry->d_name) < 0) { char *absolute = talloc_asprintf (state->removed_files, "%s/%s", path, notmuch_filenames_get (db_files)); @@ -664,17 +662,15 @@ add_files (notmuch_database_t *notmuch, } while (notmuch_filenames_valid (db_subdirs) && - strcmp (notmuch_filenames_get (db_subdirs), entry->d_name) <= 0) - { + strcmp (notmuch_filenames_get (db_subdirs), entry->d_name) <= 0) { const char *filename = notmuch_filenames_get (db_subdirs); - if (strcmp (filename, entry->d_name) < 0) - { + if (strcmp (filename, entry->d_name) < 0) { char *absolute = talloc_asprintf (state->removed_directories, "%s/%s", path, filename); if (state->debug) printf ("(D) add_files, pass 2: queuing passed directory %s for deletion from database\n", - absolute); + absolute); _filename_list_add (state->removed_directories, absolute); } @@ -694,8 +690,7 @@ add_files (notmuch_database_t *notmuch, /* Don't add a file that we've added before. */ if (notmuch_filenames_valid (db_files) && - strcmp (notmuch_filenames_get (db_files), entry->d_name) == 0) - { + strcmp (notmuch_filenames_get (db_files), entry->d_name) == 0) { notmuch_filenames_move_to_next (db_files); continue; } @@ -708,12 +703,12 @@ add_files (notmuch_database_t *notmuch, if (state->verbosity >= VERBOSITY_VERBOSE) { if (state->output_is_a_tty) - printf("\r\033[K"); + printf ("\r\033[K"); printf ("%i/%i: %s", state->processed_files, state->total_files, next); - putchar((state->output_is_a_tty) ? '\r' : '\n'); + putchar ((state->output_is_a_tty) ? '\r' : '\n'); fflush (stdout); } @@ -738,8 +733,7 @@ add_files (notmuch_database_t *notmuch, /* Now that we've walked the whole filesystem list, anything left * over in the database lists has been deleted. */ - while (notmuch_filenames_valid (db_files)) - { + while (notmuch_filenames_valid (db_files)) { char *absolute = talloc_asprintf (state->removed_files, "%s/%s", path, notmuch_filenames_get (db_files)); @@ -752,8 +746,7 @@ add_files (notmuch_database_t *notmuch, notmuch_filenames_move_to_next (db_files); } - while (notmuch_filenames_valid (db_subdirs)) - { + while (notmuch_filenames_valid (db_subdirs)) { char *absolute = talloc_asprintf (state->removed_directories, "%s/%s", path, notmuch_filenames_get (db_subdirs)); @@ -856,7 +849,7 @@ count_files (const char *path, int *count, add_files_state_t *state) } for (i = 0; i < num_fs_entries && ! interrupted; i++) { - entry = fs_entries[i]; + entry = fs_entries[i]; /* Ignore special directories to avoid infinite recursion. * Also ignore the .notmuch directory. @@ -901,7 +894,7 @@ count_files (const char *path, int *count, add_files_state_t *state) for (i = 0; i < num_fs_entries; i++) free (fs_entries[i]); - free (fs_entries); + free (fs_entries); } } @@ -939,6 +932,7 @@ remove_filename (notmuch_database_t *notmuch, { notmuch_status_t status; notmuch_message_t *message; + status = notmuch_database_begin_atomic (notmuch); if (status) return status; @@ -976,13 +970,12 @@ _remove_directory (void *ctx, char *absolute; status = notmuch_database_get_directory (notmuch, path, &directory); - if (status || !directory) + if (status || ! directory) return status; for (files = notmuch_directory_get_child_files (directory); notmuch_filenames_valid (files); - notmuch_filenames_move_to_next (files)) - { + notmuch_filenames_move_to_next (files)) { absolute = talloc_asprintf (ctx, "%s/%s", path, notmuch_filenames_get (files)); status = remove_filename (notmuch, absolute, add_files_state); @@ -993,8 +986,7 @@ _remove_directory (void *ctx, for (subdirs = notmuch_directory_get_child_directories (directory); notmuch_filenames_valid (subdirs); - notmuch_filenames_move_to_next (subdirs)) - { + notmuch_filenames_move_to_next (subdirs)) { absolute = talloc_asprintf (ctx, "%s/%s", path, notmuch_filenames_get (subdirs)); status = _remove_directory (ctx, notmuch, absolute, add_files_state); @@ -1234,32 +1226,32 @@ notmuch_new_command (notmuch_config_t *config, int argc, char *argv[]) goto DONE; gettimeofday (&tv_start, NULL); - for (f = add_files_state.removed_files->head; f && !interrupted; f = f->next) { + for (f = add_files_state.removed_files->head; f && ! interrupted; f = f->next) { ret = remove_filename (notmuch, f->filename, &add_files_state); if (ret) goto DONE; if (do_print_progress) { do_print_progress = 0; generic_print_progress ("Cleaned up", "messages", - tv_start, add_files_state.removed_messages + add_files_state.renamed_messages, - add_files_state.removed_files->count); + tv_start, add_files_state.removed_messages + add_files_state.renamed_messages, + add_files_state.removed_files->count); } } gettimeofday (&tv_start, NULL); - for (f = add_files_state.removed_directories->head, i = 0; f && !interrupted; f = f->next, i++) { + for (f = add_files_state.removed_directories->head, i = 0; f && ! interrupted; f = f->next, i++) { ret = _remove_directory (config, notmuch, f->filename, &add_files_state); if (ret) goto DONE; if (do_print_progress) { do_print_progress = 0; generic_print_progress ("Cleaned up", "directories", - tv_start, i, - add_files_state.removed_directories->count); + tv_start, i, + add_files_state.removed_directories->count); } } - for (f = add_files_state.directory_mtimes->head; f && !interrupted; f = f->next) { + for (f = add_files_state.directory_mtimes->head; f && ! interrupted; f = f->next) { notmuch_directory_t *directory; status = notmuch_database_get_directory (notmuch, f->filename, &directory); if (status == NOTMUCH_STATUS_SUCCESS && directory) { @@ -1285,7 +1277,7 @@ notmuch_new_command (notmuch_config_t *config, int argc, char *argv[]) notmuch_database_destroy (notmuch); - if (hooks && !ret && !interrupted) + if (hooks && ! ret && ! interrupted) ret = notmuch_run_hook (db_path, "post-new"); if (ret || interrupted) diff --git a/notmuch-reindex.c b/notmuch-reindex.c index 3139a8c6..5a39ade1 100644 --- a/notmuch-reindex.c +++ b/notmuch-reindex.c @@ -68,13 +68,13 @@ reindex_query (notmuch_database_t *notmuch, const char *query_string, notmuch_messages_move_to_next (messages)) { message = notmuch_messages_get (messages); - ret = notmuch_message_reindex(message, indexopts); + ret = notmuch_message_reindex (message, indexopts); if (ret != NOTMUCH_STATUS_SUCCESS) break; notmuch_message_destroy (message); } - if (!ret) + if (! ret) ret = notmuch_database_end_atomic (notmuch); notmuch_query_destroy (query); @@ -124,7 +124,7 @@ notmuch_reindex_command (notmuch_config_t *config, int argc, char *argv[]) return EXIT_FAILURE; } - query_string = query_string_from_args (config, argc-opt_index, argv+opt_index); + query_string = query_string_from_args (config, argc - opt_index, argv + opt_index); if (query_string == NULL) { fprintf (stderr, "Out of memory\n"); return EXIT_FAILURE; @@ -134,7 +134,7 @@ notmuch_reindex_command (notmuch_config_t *config, int argc, char *argv[]) fprintf (stderr, "Error: notmuch reindex requires at least one search term.\n"); return EXIT_FAILURE; } - + ret = reindex_query (notmuch, query_string, indexing_cli_choices.opts); notmuch_database_destroy (notmuch); diff --git a/notmuch-reply.c b/notmuch-reply.c index 46bab434..2c30f6f9 100644 --- a/notmuch-reply.c +++ b/notmuch-reply.c @@ -28,8 +28,8 @@ static void show_reply_headers (GMimeStream *stream, GMimeMessage *message) { /* Output RFC 2822 formatted (and RFC 2047 encoded) headers. */ - if (g_mime_object_write_to_stream (GMIME_OBJECT(message), NULL, stream) < 0) { - INTERNAL_ERROR("failed to write headers to stdout\n"); + if (g_mime_object_write_to_stream (GMIME_OBJECT (message), NULL, stream) < 0) { + INTERNAL_ERROR ("failed to write headers to stdout\n"); } } @@ -68,7 +68,7 @@ format_part_reply (GMimeStream *stream, mime_node_t *node) g_mime_content_type_is_type (content_type, "application", "pgp-signature")) { /* Ignore PGP/MIME cruft parts */ } else if (g_mime_content_type_is_type (content_type, "text", "*") && - !g_mime_content_type_is_type (content_type, "text", "html")) { + ! g_mime_content_type_is_type (content_type, "text", "html")) { show_text_part_content (node->part, stream, NOTMUCH_SHOW_TEXT_PART_REPLY); } else if (disposition && strcasecmp (g_mime_content_disposition_get_disposition (disposition), @@ -117,7 +117,7 @@ address_match (const char *str, notmuch_config_t *config, address_match_t mode) const char **other; size_t i, other_len; - if (!str || *str == '\0') + if (! str || *str == '\0') return NULL; primary = notmuch_config_get_user_primary_email (config); @@ -263,14 +263,15 @@ reply_to_header_is_redundant (GMimeMessage *message, return ret; } -static InternetAddressList *get_sender(GMimeMessage *message) +static InternetAddressList * +get_sender (GMimeMessage *message) { InternetAddressList *reply_to_list; reply_to_list = g_mime_message_get_reply_to_list (message); if (reply_to_list && internet_address_list_length (reply_to_list) > 0) { - /* + /* * Some mailing lists munge the Reply-To header despite it * being A Bad Thing, see * http://marc.merlins.org/netrants/reply-to-harmful.html @@ -290,17 +291,20 @@ static InternetAddressList *get_sender(GMimeMessage *message) return g_mime_message_get_from (message); } -static InternetAddressList *get_to(GMimeMessage *message) +static InternetAddressList * +get_to (GMimeMessage *message) { return g_mime_message_get_addresses (message, GMIME_ADDRESS_TYPE_TO); } -static InternetAddressList *get_cc(GMimeMessage *message) +static InternetAddressList * +get_cc (GMimeMessage *message) { return g_mime_message_get_addresses (message, GMIME_ADDRESS_TYPE_CC); } -static InternetAddressList *get_bcc(GMimeMessage *message) +static InternetAddressList * +get_bcc (GMimeMessage *message) { return g_mime_message_get_addresses (message, GMIME_ADDRESS_TYPE_BCC); } @@ -327,10 +331,10 @@ add_recipients_from_message (GMimeMessage *reply, InternetAddressList * (*get_header)(GMimeMessage *message); GMimeAddressType recipient_type; } reply_to_map[] = { - { get_sender, GMIME_ADDRESS_TYPE_TO }, - { get_to, GMIME_ADDRESS_TYPE_TO }, - { get_cc, GMIME_ADDRESS_TYPE_CC }, - { get_bcc, GMIME_ADDRESS_TYPE_BCC }, + { get_sender, GMIME_ADDRESS_TYPE_TO }, + { get_to, GMIME_ADDRESS_TYPE_TO }, + { get_cc, GMIME_ADDRESS_TYPE_CC }, + { get_bcc, GMIME_ADDRESS_TYPE_BCC }, }; const char *from_addr = NULL; unsigned int i; @@ -344,7 +348,7 @@ add_recipients_from_message (GMimeMessage *reply, n += scan_address_list (recipients, config, reply, reply_to_map[i].recipient_type, &from_addr); - if (!reply_all && n) { + if (! reply_all && n) { /* Stop adding new recipients in reply-to-sender mode if * we have added some recipient(s) above. * @@ -414,7 +418,7 @@ guess_from_in_received_by (notmuch_config_t *config, const char *received) if (*by == '\0') break; mta = xstrdup (by); - token = strtok(mta," \t"); + token = strtok (mta, " \t"); if (token == NULL) { free (mta); break; @@ -518,12 +522,12 @@ get_from_in_to_headers (notmuch_config_t *config, notmuch_message_t *message) } static GMimeMessage * -create_reply_message(void *ctx, - notmuch_config_t *config, - notmuch_message_t *message, - GMimeMessage *mime_message, - bool reply_all, - bool limited) +create_reply_message (void *ctx, + notmuch_config_t *config, + notmuch_message_t *message, + GMimeMessage *mime_message, + bool reply_all, + bool limited) { const char *subject, *from_addr = NULL; const char *in_reply_to, *orig_references, *references; @@ -533,6 +537,7 @@ create_reply_message(void *ctx, * otherwise. */ GMimeMessage *reply = g_mime_message_new (limited ? 0 : 1); + if (reply == NULL) { fprintf (stderr, "Out of memory\n"); return NULL; @@ -608,11 +613,12 @@ enum { FORMAT_HEADERS_ONLY, }; -static int do_reply(notmuch_config_t *config, - notmuch_query_t *query, - notmuch_show_params_t *params, - int format, - bool reply_all) +static int +do_reply (notmuch_config_t *config, + notmuch_query_t *query, + notmuch_show_params_t *params, + int format, + bool reply_all) { GMimeMessage *reply; mime_node_t *node; @@ -645,8 +651,7 @@ static int do_reply(notmuch_config_t *config, for (; notmuch_messages_valid (messages); - notmuch_messages_move_to_next (messages)) - { + notmuch_messages_move_to_next (messages)) { message = notmuch_messages_get (messages); if (mime_node_open (config, message, ¶ms->crypto, &node)) @@ -655,7 +660,7 @@ static int do_reply(notmuch_config_t *config, reply = create_reply_message (config, config, message, GMIME_MESSAGE (node->part), reply_all, format == FORMAT_HEADERS_ONLY); - if (!reply) + if (! reply) return 1; if (format == FORMAT_JSON || format == FORMAT_SEXP) { @@ -681,7 +686,7 @@ static int do_reply(notmuch_config_t *config, format_part_reply (stream_stdout, node); } g_mime_stream_flush (stream_stdout); - g_object_unref(stream_stdout); + g_object_unref (stream_stdout); } g_object_unref (G_OBJECT (reply)); @@ -709,22 +714,22 @@ notmuch_reply_command (notmuch_config_t *config, int argc, char *argv[]) notmuch_opt_desc_t options[] = { { .opt_keyword = &format, .name = "format", .keywords = - (notmuch_keyword_t []){ { "default", FORMAT_DEFAULT }, - { "json", FORMAT_JSON }, - { "sexp", FORMAT_SEXP }, - { "headers-only", FORMAT_HEADERS_ONLY }, - { 0, 0 } } }, + (notmuch_keyword_t []){ { "default", FORMAT_DEFAULT }, + { "json", FORMAT_JSON }, + { "sexp", FORMAT_SEXP }, + { "headers-only", FORMAT_HEADERS_ONLY }, + { 0, 0 } } }, { .opt_int = ¬much_format_version, .name = "format-version" }, { .opt_keyword = &reply_all, .name = "reply-to", .keywords = - (notmuch_keyword_t []){ { "all", true }, - { "sender", false }, - { 0, 0 } } }, - { .opt_keyword = (int*)(¶ms.crypto.decrypt), .name = "decrypt", + (notmuch_keyword_t []){ { "all", true }, + { "sender", false }, + { 0, 0 } } }, + { .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 } } }, + (notmuch_keyword_t []){ { "false", NOTMUCH_DECRYPT_FALSE }, + { "auto", NOTMUCH_DECRYPT_AUTO }, + { "true", NOTMUCH_DECRYPT_NOSTASH }, + { 0, 0 } } }, { .opt_inherit = notmuch_shared_options }, { } }; @@ -737,7 +742,7 @@ notmuch_reply_command (notmuch_config_t *config, int argc, char *argv[]) notmuch_exit_if_unsupported_format (); - query_string = query_string_from_args (config, argc-opt_index, argv+opt_index); + query_string = query_string_from_args (config, argc - opt_index, argv + opt_index); if (query_string == NULL) { fprintf (stderr, "Out of memory\n"); return EXIT_FAILURE; diff --git a/notmuch-restore.c b/notmuch-restore.c index dee19c20..4b509d95 100644 --- a/notmuch-restore.c +++ b/notmuch-restore.c @@ -25,18 +25,18 @@ #include "zlib-extra.h" static int -process_config_line (notmuch_database_t *notmuch, const char* line) +process_config_line (notmuch_database_t *notmuch, const char *line) { const char *key_p, *val_p; char *key, *val; - size_t key_len,val_len; + size_t key_len, val_len; const char *delim = " \t\n"; int ret = EXIT_FAILURE; - void *local = talloc_new(NULL); + void *local = talloc_new (NULL); key_p = strtok_len_c (line, delim, &key_len); - val_p = strtok_len_c (key_p+key_len, delim, &val_len); + val_p = strtok_len_c (key_p + key_len, delim, &val_len); key = talloc_strndup (local, key_p, key_len); val = talloc_strndup (local, val_p, val_len); @@ -52,14 +52,13 @@ process_config_line (notmuch_database_t *notmuch, const char* line) ret = EXIT_SUCCESS; - DONE: + DONE: talloc_free (local); return ret; } static int -process_properties_line (notmuch_database_t *notmuch, const char* line) - +process_properties_line (notmuch_database_t *notmuch, const char *line) { const char *id_p, *tok; size_t id_len = 0, tok_len = 0; @@ -248,14 +247,14 @@ notmuch_restore_command (notmuch_config_t *config, int argc, char *argv[]) notmuch_opt_desc_t options[] = { { .opt_keyword = &input_format, .name = "format", .keywords = - (notmuch_keyword_t []){ { "auto", DUMP_FORMAT_AUTO }, - { "batch-tag", DUMP_FORMAT_BATCH_TAG }, - { "sup", DUMP_FORMAT_SUP }, - { 0, 0 } } }, + (notmuch_keyword_t []){ { "auto", DUMP_FORMAT_AUTO }, + { "batch-tag", DUMP_FORMAT_BATCH_TAG }, + { "sup", DUMP_FORMAT_SUP }, + { 0, 0 } } }, { .opt_flags = &include, .name = "include", .keywords = - (notmuch_keyword_t []){ { "config", DUMP_INCLUDE_CONFIG }, - { "properties", DUMP_INCLUDE_PROPERTIES }, - { "tags", DUMP_INCLUDE_TAGS} } }, + (notmuch_keyword_t []){ { "config", DUMP_INCLUDE_CONFIG }, + { "properties", DUMP_INCLUDE_PROPERTIES }, + { "tags", DUMP_INCLUDE_TAGS } } }, { .opt_string = &input_file_name, .name = "input" }, { .opt_bool = &accumulate, .name = "accumulate" }, @@ -330,13 +329,13 @@ notmuch_restore_command (notmuch_config_t *config, int argc, char *argv[]) if (status) { fprintf (stderr, "Error reading (gzipped) input: %s\n", - gz_error_string(status, input)); + gz_error_string (status, input)); ret = EXIT_FAILURE; goto DONE; } if ((include & DUMP_INCLUDE_CONFIG) && line_len >= 2 && line[0] == '#' && line[1] == '@') { - ret = process_config_line(notmuch, line+2); + ret = process_config_line (notmuch, line + 2); if (ret) goto DONE; } @@ -348,8 +347,8 @@ notmuch_restore_command (notmuch_config_t *config, int argc, char *argv[]) } while ((line_len == 0) || (line[0] == '#') || - /* the cast is safe because we checked about for line_len < 0 */ - (strspn (line, " \t\n") == (unsigned)line_len)); + /* the cast is safe because we checked about for line_len < 0 */ + (strspn (line, " \t\n") == (unsigned) line_len)); if (! ((include & DUMP_INCLUDE_TAGS) || (include & DUMP_INCLUDE_PROPERTIES))) { ret = EXIT_SUCCESS; @@ -435,14 +434,14 @@ notmuch_restore_command (notmuch_config_t *config, int argc, char *argv[]) ret = EXIT_FAILURE; } - /* currently this should not be after DONE: since we don't + /* currently this should not be after DONE: since we don't * know if the xregcomp was reached */ if (input_format == DUMP_FORMAT_SUP) regfree (®ex); - DONE: + DONE: if (line_ctx != NULL) talloc_free (line_ctx); diff --git a/notmuch-search.c b/notmuch-search.c index e3a85617..fd0b58c5 100644 --- a/notmuch-search.c +++ b/notmuch-search.c @@ -87,8 +87,7 @@ get_thread_query (notmuch_thread_t *thread, for (messages = notmuch_thread_get_messages (thread); notmuch_messages_valid (messages); - notmuch_messages_move_to_next (messages)) - { + notmuch_messages_move_to_next (messages)) { notmuch_message_t *message = notmuch_messages_get (messages); const char *mid = notmuch_message_get_message_id (message); /* Determine which query buffer to extend */ @@ -103,7 +102,7 @@ get_thread_query (notmuch_thread_t *thread, *buf = talloc_asprintf_append_buffer (*buf, " %s", escaped); else *buf = talloc_strdup (thread, escaped); - if (!*buf) + if (! *buf) return -1; } talloc_free (escaped); @@ -134,15 +133,14 @@ do_search_threads (search_context_t *ctx) } status = notmuch_query_search_threads (ctx->query, &threads); - if (print_status_query("notmuch search", ctx->query, status)) + if (print_status_query ("notmuch search", ctx->query, status)) return 1; format->begin_list (format); for (i = 0; notmuch_threads_valid (threads) && (ctx->limit < 0 || i < ctx->offset + ctx->limit); - notmuch_threads_move_to_next (threads), i++) - { + notmuch_threads_move_to_next (threads), i++) { thread = notmuch_threads_get (threads); if (i < ctx->offset) { @@ -176,23 +174,23 @@ do_search_threads (search_context_t *ctx) relative_date = notmuch_time_relative_date (ctx_quote, date); if (format->is_text_printer) { - /* Special case for the text formatter */ + /* Special case for the text formatter */ printf ("thread:%s %12s ", thread_id, relative_date); if (total == files) printf ("[%d/%d] %s; %s (", - matched, - total, - sanitize_string (ctx_quote, authors), - sanitize_string (ctx_quote, subject)); + matched, + total, + sanitize_string (ctx_quote, authors), + sanitize_string (ctx_quote, subject)); else printf ("[%d/%d(%d)] %s; %s (", - matched, - total, - files, - sanitize_string (ctx_quote, authors), - sanitize_string (ctx_quote, subject)); + matched, + total, + files, + sanitize_string (ctx_quote, authors), + sanitize_string (ctx_quote, subject)); } else { /* Structured Output */ format->map_key (format, "thread"); @@ -237,12 +235,11 @@ do_search_threads (search_context_t *ctx) for (tags = notmuch_thread_get_tags (thread); notmuch_tags_valid (tags); - notmuch_tags_move_to_next (tags)) - { + notmuch_tags_move_to_next (tags)) { const char *tag = notmuch_tags_get (tags); if (format->is_text_printer) { - /* Special case for the text formatter */ + /* Special case for the text formatter */ if (first_tag) first_tag = false; else @@ -269,7 +266,8 @@ do_search_threads (search_context_t *ctx) return 0; } -static mailbox_t *new_mailbox (void *ctx, const char *name, const char *addr) +static mailbox_t * +new_mailbox (void *ctx, const char *name, const char *addr) { mailbox_t *mailbox; @@ -284,7 +282,8 @@ static mailbox_t *new_mailbox (void *ctx, const char *name, const char *addr) return mailbox; } -static int mailbox_compare (const void *v1, const void *v2) +static int +mailbox_compare (const void *v1, const void *v2) { const mailbox_t *m1 = v1, *m2 = v2; int ret; @@ -488,7 +487,7 @@ print_popular (const search_context_t *ctx, GList *list) } if (! mailbox) - INTERNAL_ERROR("Empty list in address hash table\n"); + INTERNAL_ERROR ("Empty list in address hash table\n"); /* The original count is no longer needed, so overwrite. */ mailbox->count = total; @@ -522,8 +521,8 @@ _count_filenames (notmuch_message_t *message) filenames = notmuch_message_get_filenames (message); while (notmuch_filenames_valid (filenames)) { - notmuch_filenames_move_to_next (filenames); - i++; + notmuch_filenames_move_to_next (filenames); + i++; } notmuch_filenames_destroy (filenames); @@ -561,8 +560,7 @@ do_search_messages (search_context_t *ctx) for (i = 0; notmuch_messages_valid (messages) && (ctx->limit < 0 || i < ctx->offset + ctx->limit); - notmuch_messages_move_to_next (messages), i++) - { + notmuch_messages_move_to_next (messages), i++) { if (i < ctx->offset) continue; @@ -574,24 +572,23 @@ do_search_messages (search_context_t *ctx) for (j = 1; notmuch_filenames_valid (filenames); - notmuch_filenames_move_to_next (filenames), j++) - { + notmuch_filenames_move_to_next (filenames), j++) { if (ctx->dupe < 0 || ctx->dupe == j) { format->string (format, notmuch_filenames_get (filenames)); format->separator (format); } } - - notmuch_filenames_destroy( filenames ); + + notmuch_filenames_destroy ( filenames ); } else if (ctx->output == OUTPUT_MESSAGES) { - /* special case 1 for speed */ - if (ctx->dupe <= 1 || ctx->dupe <= _count_filenames (message)) { - format->set_prefix (format, "id"); - format->string (format, - notmuch_message_get_message_id (message)); - format->separator (format); - } + /* special case 1 for speed */ + if (ctx->dupe <= 1 || ctx->dupe <= _count_filenames (message)) { + format->set_prefix (format, "id"); + format->string (format, + notmuch_message_get_message_id (message)); + format->separator (format); + } } else { if (ctx->output & OUTPUT_SENDER) { const char *addrs; @@ -657,8 +654,7 @@ do_search_tags (const search_context_t *ctx) for (; notmuch_tags_valid (tags); - notmuch_tags_move_to_next (tags)) - { + notmuch_tags_move_to_next (tags)) { tag = notmuch_tags_get (tags); format->string (format, tag); @@ -702,7 +698,7 @@ _notmuch_search_prepare (search_context_t *ctx, notmuch_config_t *config, int ar break; default: /* this should never happen */ - INTERNAL_ERROR("no output format selected"); + INTERNAL_ERROR ("no output format selected"); } notmuch_exit_if_unsupported_format (); @@ -791,15 +787,15 @@ static search_context_t search_context = { static const notmuch_opt_desc_t common_options[] = { { .opt_keyword = &search_context.sort, .name = "sort", .keywords = - (notmuch_keyword_t []){ { "oldest-first", NOTMUCH_SORT_OLDEST_FIRST }, - { "newest-first", NOTMUCH_SORT_NEWEST_FIRST }, - { 0, 0 } } }, + (notmuch_keyword_t []){ { "oldest-first", NOTMUCH_SORT_OLDEST_FIRST }, + { "newest-first", NOTMUCH_SORT_NEWEST_FIRST }, + { 0, 0 } } }, { .opt_keyword = &search_context.format_sel, .name = "format", .keywords = - (notmuch_keyword_t []){ { "json", NOTMUCH_FORMAT_JSON }, - { "sexp", NOTMUCH_FORMAT_SEXP }, - { "text", NOTMUCH_FORMAT_TEXT }, - { "text0", NOTMUCH_FORMAT_TEXT0 }, - { 0, 0 } } }, + (notmuch_keyword_t []){ { "json", NOTMUCH_FORMAT_JSON }, + { "sexp", NOTMUCH_FORMAT_SEXP }, + { "text", NOTMUCH_FORMAT_TEXT }, + { "text0", NOTMUCH_FORMAT_TEXT0 }, + { 0, 0 } } }, { .opt_int = ¬much_format_version, .name = "format-version" }, { } }; @@ -812,18 +808,18 @@ notmuch_search_command (notmuch_config_t *config, int argc, char *argv[]) notmuch_opt_desc_t options[] = { { .opt_keyword = &ctx->output, .name = "output", .keywords = - (notmuch_keyword_t []){ { "summary", OUTPUT_SUMMARY }, - { "threads", OUTPUT_THREADS }, - { "messages", OUTPUT_MESSAGES }, - { "files", OUTPUT_FILES }, - { "tags", OUTPUT_TAGS }, - { 0, 0 } } }, - { .opt_keyword = &ctx->exclude, .name = "exclude", .keywords = - (notmuch_keyword_t []){ { "true", NOTMUCH_EXCLUDE_TRUE }, - { "false", NOTMUCH_EXCLUDE_FALSE }, - { "flag", NOTMUCH_EXCLUDE_FLAG }, - { "all", NOTMUCH_EXCLUDE_ALL }, - { 0, 0 } } }, + (notmuch_keyword_t []){ { "summary", OUTPUT_SUMMARY }, + { "threads", OUTPUT_THREADS }, + { "messages", OUTPUT_MESSAGES }, + { "files", OUTPUT_FILES }, + { "tags", OUTPUT_TAGS }, + { 0, 0 } } }, + { .opt_keyword = &ctx->exclude, .name = "exclude", .keywords = + (notmuch_keyword_t []){ { "true", NOTMUCH_EXCLUDE_TRUE }, + { "false", NOTMUCH_EXCLUDE_FALSE }, + { "flag", NOTMUCH_EXCLUDE_FLAG }, + { "all", NOTMUCH_EXCLUDE_ALL }, + { 0, 0 } } }, { .opt_int = &ctx->offset, .name = "offset" }, { .opt_int = &ctx->limit, .name = "limit" }, { .opt_int = &ctx->dupe, .name = "duplicate" }, @@ -841,8 +837,8 @@ notmuch_search_command (notmuch_config_t *config, int argc, char *argv[]) if (ctx->output != OUTPUT_FILES && ctx->output != OUTPUT_MESSAGES && ctx->dupe != -1) { - fprintf (stderr, "Error: --duplicate=N is only supported with --output=files and --output=messages.\n"); - return EXIT_FAILURE; + fprintf (stderr, "Error: --duplicate=N is only supported with --output=files and --output=messages.\n"); + return EXIT_FAILURE; } if (_notmuch_search_prepare (ctx, config, @@ -878,20 +874,20 @@ notmuch_address_command (notmuch_config_t *config, int argc, char *argv[]) notmuch_opt_desc_t options[] = { { .opt_flags = &ctx->output, .name = "output", .keywords = - (notmuch_keyword_t []){ { "sender", OUTPUT_SENDER }, - { "recipients", OUTPUT_RECIPIENTS }, - { "count", OUTPUT_COUNT }, - { "address", OUTPUT_ADDRESS }, - { 0, 0 } } }, + (notmuch_keyword_t []){ { "sender", OUTPUT_SENDER }, + { "recipients", OUTPUT_RECIPIENTS }, + { "count", OUTPUT_COUNT }, + { "address", OUTPUT_ADDRESS }, + { 0, 0 } } }, { .opt_keyword = &ctx->exclude, .name = "exclude", .keywords = - (notmuch_keyword_t []){ { "true", NOTMUCH_EXCLUDE_TRUE }, - { "false", NOTMUCH_EXCLUDE_FALSE }, - { 0, 0 } } }, + (notmuch_keyword_t []){ { "true", NOTMUCH_EXCLUDE_TRUE }, + { "false", NOTMUCH_EXCLUDE_FALSE }, + { 0, 0 } } }, { .opt_keyword = &ctx->dedup, .name = "deduplicate", .keywords = - (notmuch_keyword_t []){ { "no", DEDUP_NONE }, - { "mailbox", DEDUP_MAILBOX }, - { "address", DEDUP_ADDRESS }, - { 0, 0 } } }, + (notmuch_keyword_t []){ { "no", DEDUP_NONE }, + { "mailbox", DEDUP_MAILBOX }, + { "address", DEDUP_ADDRESS }, + { 0, 0 } } }, { .opt_inherit = common_options }, { .opt_inherit = notmuch_shared_options }, { } diff --git a/notmuch-setup.c b/notmuch-setup.c index 81419ccf..cd1a52ff 100644 --- a/notmuch-setup.c +++ b/notmuch-setup.c @@ -47,50 +47,51 @@ static void welcome_message_pre_setup (void) { printf ( -"Welcome to notmuch!\n\n" + "Welcome to notmuch!\n\n" -"The goal of notmuch is to help you manage and search your collection of\n" -"email, and to efficiently keep up with the flow of email as it comes in.\n\n" + "The goal of notmuch is to help you manage and search your collection of\n" + "email, and to efficiently keep up with the flow of email as it comes in.\n\n" -"Notmuch needs to know a few things about you such as your name and email\n" -"address, as well as the directory that contains your email. This is where\n" -"you already have mail stored and where messages will be delivered in the\n" -"future. This directory can contain any number of sub-directories. Regular\n" -"files in these directories should be individual email messages. If there\n" -"are other, non-email files (such as indexes maintained by other email\n" -"programs) then notmuch will do its best to detect those and ignore them.\n\n" + "Notmuch needs to know a few things about you such as your name and email\n" + "address, as well as the directory that contains your email. This is where\n" + "you already have mail stored and where messages will be delivered in the\n" + "future. This directory can contain any number of sub-directories. Regular\n" + "files in these directories should be individual email messages. If there\n" + "are other, non-email files (such as indexes maintained by other email\n" + "programs) then notmuch will do its best to detect those and ignore them.\n\n" -"If you already have your email being delivered to directories in either\n" -"maildir or mh format, then that's perfect. Mail storage that uses mbox\n" -"format, (where one mbox file contains many messages), will not work with\n" -"notmuch. If that's how your mail is currently stored, we recommend you\n" -"first convert it to maildir format with a utility such as mb2md. You can\n" -"continue configuring notmuch now, but be sure to complete the conversion\n" -"before you run \"notmuch new\" for the first time.\n\n"); + "If you already have your email being delivered to directories in either\n" + "maildir or mh format, then that's perfect. Mail storage that uses mbox\n" + "format, (where one mbox file contains many messages), will not work with\n" + "notmuch. If that's how your mail is currently stored, we recommend you\n" + "first convert it to maildir format with a utility such as mb2md. You can\n" + "continue configuring notmuch now, but be sure to complete the conversion\n" + "before you run \"notmuch new\" for the first time.\n\n"); } static void welcome_message_post_setup (void) { printf ("\n" -"Notmuch is now configured, and the configuration settings are saved in\n" -"a file in your home directory named .notmuch-config . If you'd like to\n" -"change the configuration in the future, you can either edit that file\n" -"directly or run \"notmuch setup\". To choose an alternate configuration\n" -"location, set ${NOTMUCH_CONFIG}.\n\n" + "Notmuch is now configured, and the configuration settings are saved in\n" + "a file in your home directory named .notmuch-config . If you'd like to\n" + "change the configuration in the future, you can either edit that file\n" + "directly or run \"notmuch setup\". To choose an alternate configuration\n" + "location, set ${NOTMUCH_CONFIG}.\n\n" -"The next step is to run \"notmuch new\" which will create a database\n" -"that indexes all of your mail. Depending on the amount of mail you have\n" -"the initial indexing process can take a long time, so expect that.\n" -"Also, the resulting database will require roughly the same amount of\n" -"storage space as your current collection of email. So please ensure you\n" -"have sufficient storage space available now.\n\n"); + "The next step is to run \"notmuch new\" which will create a database\n" + "that indexes all of your mail. Depending on the amount of mail you have\n" + "the initial indexing process can take a long time, so expect that.\n" + "Also, the resulting database will require roughly the same amount of\n" + "storage space as your current collection of email. So please ensure you\n" + "have sufficient storage space available now.\n\n"); } static void print_tag_list (const char **tags, size_t tags_len) { unsigned int i; + for (i = 0; i < tags_len; i++) { if (i != 0) printf (" "); @@ -134,15 +135,15 @@ notmuch_setup_command (notmuch_config_t *config, const char **search_exclude_tags; size_t search_exclude_tags_len; -#define prompt(format, ...) \ - do { \ - printf (format, ##__VA_ARGS__); \ - fflush (stdout); \ - if (getline (&response, &response_size, stdin) < 0) { \ - printf ("Exiting.\n"); \ - exit (EXIT_FAILURE); \ - } \ - chomp_newline (response); \ +#define prompt(format, ...) \ + do { \ + printf (format, ##__VA_ARGS__); \ + fflush (stdout); \ + if (getline (&response, &response_size, stdin) < 0) { \ + printf ("Exiting.\n"); \ + exit (EXIT_FAILURE); \ + } \ + chomp_newline (response); \ } while (0) if (notmuch_minimal_options ("setup", argc, argv) < 0) @@ -167,14 +168,14 @@ notmuch_setup_command (notmuch_config_t *config, other_emails = g_ptr_array_new (); old_other_emails = notmuch_config_get_user_other_email (config, - &old_other_emails_len); + &old_other_emails_len); for (i = 0; i < old_other_emails_len; i++) { prompt ("Additional email address [%s]: ", old_other_emails[i]); if (strlen (response)) g_ptr_array_add (other_emails, talloc_strdup (config, response)); else g_ptr_array_add (other_emails, talloc_strdup (config, - old_other_emails[i])); + old_other_emails[i])); } do { diff --git a/notmuch-show.c b/notmuch-show.c index bce7d827..9779cfa5 100644 --- a/notmuch-show.c +++ b/notmuch-show.c @@ -37,8 +37,7 @@ _get_tags_as_string (const void *ctx, notmuch_message_t *message) for (tags = notmuch_message_get_tags (message); notmuch_tags_valid (tags); - notmuch_tags_move_to_next (tags)) - { + notmuch_tags_move_to_next (tags)) { tag = notmuch_tags_get (tags); result = talloc_asprintf_append (result, "%s%s", @@ -69,12 +68,13 @@ _get_one_line_summary (const void *ctx, notmuch_message_t *message) from, relative_date, tags); } -static const char *_get_disposition(GMimeObject *meta) +static const char * +_get_disposition (GMimeObject *meta) { GMimeContentDisposition *disposition; disposition = g_mime_object_get_content_disposition (meta); - if (!disposition) + if (! disposition) return NULL; return g_mime_content_disposition_get_disposition (disposition); @@ -170,7 +170,7 @@ _extract_email_address (const void *ctx, const char *from) g_object_unref (addresses); return email; - } +} /* Return 1 if 'line' is an mbox From_ line---that is, a line * beginning with zero or more '>' characters followed by the @@ -290,7 +290,7 @@ show_text_part_content (GMimeObject *part, GMimeStream *stream_out, charset = g_mime_object_get_content_type_parameter (part, "charset"); charset = charset ? g_mime_charset_canon_name (charset) : NULL; wrapper = g_mime_part_get_content (GMIME_PART (part)); - if (wrapper && charset && !g_ascii_strncasecmp (charset, "iso-8859-", 9)) { + if (wrapper && charset && ! g_ascii_strncasecmp (charset, "iso-8859-", 9)) { GMimeStream *null_stream = NULL; GMimeStream *null_stream_filter = NULL; @@ -298,10 +298,10 @@ show_text_part_content (GMimeObject *part, GMimeStream *stream_out, null_stream = g_mime_stream_null_new (); null_stream_filter = g_mime_stream_filter_new (null_stream); windows_filter = g_mime_filter_windows_new (charset); - g_mime_stream_filter_add(GMIME_STREAM_FILTER (null_stream_filter), - windows_filter); + g_mime_stream_filter_add (GMIME_STREAM_FILTER (null_stream_filter), + windows_filter); g_mime_data_wrapper_write_to_stream (wrapper, null_stream_filter); - charset = g_mime_filter_windows_real_charset( + charset = g_mime_filter_windows_real_charset ( (GMimeFilterWindows *) windows_filter); if (null_stream_filter) @@ -314,8 +314,8 @@ show_text_part_content (GMimeObject *part, GMimeStream *stream_out, stream_filter = g_mime_stream_filter_new (stream_out); crlf_filter = g_mime_filter_dos2unix_new (false); - g_mime_stream_filter_add(GMIME_STREAM_FILTER (stream_filter), - crlf_filter); + g_mime_stream_filter_add (GMIME_STREAM_FILTER (stream_filter), + crlf_filter); g_object_unref (crlf_filter); if (charset) { @@ -345,12 +345,12 @@ show_text_part_content (GMimeObject *part, GMimeStream *stream_out, if (wrapper && stream_filter) g_mime_data_wrapper_write_to_stream (wrapper, stream_filter); if (stream_filter) - g_object_unref(stream_filter); + g_object_unref (stream_filter); if (windows_filter) g_object_unref (windows_filter); } -static const char* +static const char * signature_status_to_string (GMimeSignatureStatus status) { if (g_mime_signature_status_bad (status)) @@ -368,12 +368,13 @@ signature_status_to_string (GMimeSignatureStatus status) /* Print signature flags */ struct key_map_struct { GMimeSignatureStatus bit; - const char * string; + const char *string; }; static void do_format_signature_errors (sprinter_t *sp, struct key_map_struct *key_map, - unsigned int array_map_len, GMimeSignatureStatus errors) { + unsigned int array_map_len, GMimeSignatureStatus errors) +{ sp->map_key (sp, "errors"); sp->begin_map (sp); @@ -392,22 +393,22 @@ format_signature_errors (sprinter_t *sp, GMimeSignature *signature) { GMimeSignatureStatus errors = g_mime_signature_get_status (signature); - if (!(errors & GMIME_SIGNATURE_STATUS_ERROR_MASK)) + if (! (errors & GMIME_SIGNATURE_STATUS_ERROR_MASK)) return; struct key_map_struct key_map[] = { - { GMIME_SIGNATURE_STATUS_KEY_REVOKED, "key-revoked"}, - { GMIME_SIGNATURE_STATUS_KEY_EXPIRED, "key-expired"}, + { GMIME_SIGNATURE_STATUS_KEY_REVOKED, "key-revoked" }, + { GMIME_SIGNATURE_STATUS_KEY_EXPIRED, "key-expired" }, { GMIME_SIGNATURE_STATUS_SIG_EXPIRED, "sig-expired" }, - { GMIME_SIGNATURE_STATUS_KEY_MISSING, "key-missing"}, - { GMIME_SIGNATURE_STATUS_CRL_MISSING, "crl-missing"}, - { GMIME_SIGNATURE_STATUS_CRL_TOO_OLD, "crl-too-old"}, - { GMIME_SIGNATURE_STATUS_BAD_POLICY, "bad-policy"}, - { GMIME_SIGNATURE_STATUS_SYS_ERROR, "sys-error"}, - { GMIME_SIGNATURE_STATUS_TOFU_CONFLICT, "tofu-conflict"}, + { GMIME_SIGNATURE_STATUS_KEY_MISSING, "key-missing" }, + { GMIME_SIGNATURE_STATUS_CRL_MISSING, "crl-missing" }, + { GMIME_SIGNATURE_STATUS_CRL_TOO_OLD, "crl-too-old" }, + { GMIME_SIGNATURE_STATUS_BAD_POLICY, "bad-policy" }, + { GMIME_SIGNATURE_STATUS_SYS_ERROR, "sys-error" }, + { GMIME_SIGNATURE_STATUS_TOFU_CONFLICT, "tofu-conflict" }, }; - do_format_signature_errors (sp, key_map, ARRAY_SIZE(key_map), errors); + do_format_signature_errors (sp, key_map, ARRAY_SIZE (key_map), errors); } /* Signature status sprinter */ @@ -419,7 +420,7 @@ format_part_sigstatus_sprinter (sprinter_t *sp, GMimeSignatureList *siglist) sp->begin_list (sp); - if (!siglist) { + if (! siglist) { sp->end (sp); return; } @@ -479,7 +480,7 @@ format_part_sigstatus_sprinter (sprinter_t *sp, GMimeSignatureList *siglist) } sp->end (sp); - } + } sp->end (sp); } @@ -555,8 +556,7 @@ format_part_text (const void *ctx, sprinter_t *sp, mime_node_t *node, g_mime_stream_printf (stream, "Date: %s\n", date_string); g_mime_stream_printf (stream, "\fheader}\n"); - if (!params->output_body) - { + if (! params->output_body) { g_mime_stream_printf (stream, "\f%s}\n", part_type); return NOTMUCH_STATUS_SUCCESS; } @@ -566,8 +566,7 @@ format_part_text (const void *ctx, sprinter_t *sp, mime_node_t *node, if (leaf) { if (g_mime_content_type_is_type (content_type, "text", "*") && (params->include_html || - ! g_mime_content_type_is_type (content_type, "text", "html"))) - { + ! g_mime_content_type_is_type (content_type, "text", "html"))) { show_text_part_content (node->part, stream, 0); } else { char *content_string = g_mime_content_type_get_mime_type (content_type); @@ -751,8 +750,7 @@ format_part_sprinter (const void *ctx, sprinter_t *sp, mime_node_t *node, */ if (g_mime_content_type_is_type (content_type, "text", "*") && (include_html || - ! g_mime_content_type_is_type (content_type, "text", "html"))) - { + ! g_mime_content_type_is_type (content_type, "text", "html"))) { GMimeStream *stream_memory = g_mime_stream_mem_new (); GByteArray *part_content; show_text_part_content (node->part, stream_memory, 0); @@ -824,7 +822,7 @@ format_part_mbox (const void *ctx, unused (sprinter_t *sp), mime_node_t *node, ssize_t line_size; ssize_t line_len; - if (!message) + if (! message) INTERNAL_ERROR ("format_part_mbox requires a root part"); filename = notmuch_message_get_filename (message); @@ -883,7 +881,7 @@ format_part_raw (unused (const void *ctx), unused (sprinter_t *sp), } while (! g_mime_stream_eos (stream)) { - ssize = g_mime_stream_read (stream, buf, sizeof(buf)); + ssize = g_mime_stream_read (stream, buf, sizeof (buf)); if (ssize < 0) { fprintf (stderr, "Error: Read failed from %s\n", filename); goto DONE; @@ -898,7 +896,7 @@ format_part_raw (unused (const void *ctx), unused (sprinter_t *sp), ret = NOTMUCH_STATUS_SUCCESS; /* XXX This DONE is just for the special case of a node in a single file */ - DONE: + DONE: if (stream) g_object_unref (stream); @@ -967,7 +965,7 @@ show_message (void *ctx, } } } - DONE: + DONE : talloc_free (local); return status; } @@ -990,8 +988,7 @@ show_messages (void *ctx, for (; notmuch_messages_valid (messages); - notmuch_messages_move_to_next (messages)) - { + notmuch_messages_move_to_next (messages)) { sp->begin_list (sp); message = notmuch_messages_get (messages); @@ -1001,9 +998,9 @@ show_messages (void *ctx, next_indent = indent; - if ((match && (!excluded || !params->omit_excluded)) || params->entire_thread) { + if ((match && (! excluded || ! params->omit_excluded)) || params->entire_thread) { status = show_message (ctx, format, sp, message, indent, params); - if (status && !res) + if (status && ! res) res = status; next_indent = indent + 1; } else { @@ -1015,7 +1012,7 @@ show_messages (void *ctx, notmuch_message_get_replies (message), next_indent, params); - if (status && !res) + if (status && ! res) res = status; notmuch_message_destroy (message); @@ -1064,7 +1061,7 @@ do_show_single (void *ctx, notmuch_message_set_flag (message, NOTMUCH_MESSAGE_FLAG_MATCH, 1); return show_message (ctx, format, sp, message, 0, params) - != NOTMUCH_STATUS_SUCCESS; + != NOTMUCH_STATUS_SUCCESS; } /* Formatted output of threads */ @@ -1080,16 +1077,15 @@ do_show (void *ctx, notmuch_messages_t *messages; notmuch_status_t status, res = NOTMUCH_STATUS_SUCCESS; - status= notmuch_query_search_threads (query, &threads); + status = notmuch_query_search_threads (query, &threads); if (print_status_query ("notmuch show", query, status)) return 1; sp->begin_list (sp); - for ( ; + for (; notmuch_threads_valid (threads); - notmuch_threads_move_to_next (threads)) - { + notmuch_threads_move_to_next (threads)) { thread = notmuch_threads_get (threads); messages = notmuch_thread_get_toplevel_messages (thread); @@ -1099,7 +1095,7 @@ do_show (void *ctx, notmuch_thread_get_thread_id (thread)); status = show_messages (ctx, format, sp, messages, 0, params); - if (status && !res) + if (status && ! res) res = status; notmuch_thread_destroy (thread); @@ -1175,24 +1171,24 @@ notmuch_show_command (notmuch_config_t *config, int argc, char *argv[]) notmuch_opt_desc_t options[] = { { .opt_keyword = &format, .name = "format", .keywords = - (notmuch_keyword_t []){ { "json", NOTMUCH_FORMAT_JSON }, - { "text", NOTMUCH_FORMAT_TEXT }, - { "sexp", NOTMUCH_FORMAT_SEXP }, - { "mbox", NOTMUCH_FORMAT_MBOX }, - { "raw", NOTMUCH_FORMAT_RAW }, - { 0, 0 } } }, + (notmuch_keyword_t []){ { "json", NOTMUCH_FORMAT_JSON }, + { "text", NOTMUCH_FORMAT_TEXT }, + { "sexp", NOTMUCH_FORMAT_SEXP }, + { "mbox", NOTMUCH_FORMAT_MBOX }, + { "raw", NOTMUCH_FORMAT_RAW }, + { 0, 0 } } }, { .opt_int = ¬much_format_version, .name = "format-version" }, { .opt_bool = &exclude, .name = "exclude" }, { .opt_bool = ¶ms.entire_thread, .name = "entire-thread", .present = &entire_thread_set }, { .opt_int = ¶ms.part, .name = "part" }, - { .opt_keyword = (int*)(¶ms.crypto.decrypt), .name = "decrypt", + { .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 }, - { "stash", NOTMUCH_DECRYPT_TRUE }, - { 0, 0 } } }, + (notmuch_keyword_t []){ { "false", NOTMUCH_DECRYPT_FALSE }, + { "auto", NOTMUCH_DECRYPT_AUTO }, + { "true", NOTMUCH_DECRYPT_NOSTASH }, + { "stash", NOTMUCH_DECRYPT_TRUE }, + { 0, 0 } } }, { .opt_bool = ¶ms.crypto.verify, .name = "verify" }, { .opt_bool = ¶ms.output_body, .name = "body" }, { .opt_bool = ¶ms.include_html, .name = "include-html" }, @@ -1240,7 +1236,7 @@ notmuch_show_command (notmuch_config_t *config, int argc, char *argv[]) (format == NOTMUCH_FORMAT_JSON || format == NOTMUCH_FORMAT_SEXP)) params.entire_thread = true; - if (!params.output_body) { + if (! params.output_body) { if (params.part > 0) { fprintf (stderr, "Warning: --body=false is incompatible with --part > 0. Disabling.\n"); params.output_body = true; @@ -1254,13 +1250,13 @@ notmuch_show_command (notmuch_config_t *config, int argc, char *argv[]) } if (params.include_html && - (format != NOTMUCH_FORMAT_TEXT && + (format != NOTMUCH_FORMAT_TEXT && format != NOTMUCH_FORMAT_JSON && format != NOTMUCH_FORMAT_SEXP)) { fprintf (stderr, "Warning: --include-html only implemented for format=text, format=json and format=sexp\n"); } - query_string = query_string_from_args (config, argc-opt_index, argv+opt_index); + query_string = query_string_from_args (config, argc - opt_index, argv + opt_index); if (query_string == NULL) { fprintf (stderr, "Out of memory\n"); return EXIT_FAILURE; @@ -1288,7 +1284,7 @@ notmuch_show_command (notmuch_config_t *config, int argc, char *argv[]) /* Create structure printer. */ formatter = formatters[format]; - sprinter = formatter->new_sprinter(config, stdout); + sprinter = formatter->new_sprinter (config, stdout); params.out_stream = g_mime_stream_stdout_new (); @@ -1305,7 +1301,7 @@ notmuch_show_command (notmuch_config_t *config, int argc, char *argv[]) notmuch_status_t status; search_exclude_tags = notmuch_config_get_search_exclude_tags - (config, &search_exclude_tags_length); + (config, &search_exclude_tags_length); for (i = 0; i < search_exclude_tags_length; i++) { status = notmuch_query_add_tag_exclude (query, search_exclude_tags[i]); @@ -1324,7 +1320,7 @@ notmuch_show_command (notmuch_config_t *config, int argc, char *argv[]) ret = do_show (config, query, formatter, sprinter, ¶ms); } - DONE: + DONE : g_mime_stream_flush (params.out_stream); g_object_unref (params.out_stream); diff --git a/notmuch-time.c b/notmuch-time.c index 2734b36a..cc7ffc23 100644 --- a/notmuch-time.c +++ b/notmuch-time.c @@ -39,14 +39,14 @@ * */ #define MINUTE (60) -#define HOUR (60 * MINUTE) -#define DAY (24 * HOUR) +#define HOUR (60 *MINUTE) +#define DAY (24 *HOUR) #define RELATIVE_DATE_MAX 20 const char * notmuch_time_relative_date (const void *ctx, time_t then) { struct tm tm_now, tm_then; - time_t now = time(NULL); + time_t now = time (NULL); time_t delta; char *result; @@ -76,26 +76,25 @@ notmuch_time_relative_date (const void *ctx, time_t then) if (delta <= 7 * DAY) { if (tm_then.tm_wday == tm_now.tm_wday && - delta < DAY) - { + delta < DAY) { strftime (result, RELATIVE_DATE_MAX, - "Today %R", &tm_then); /* Today 12:30 */ + "Today %R", &tm_then); /* Today 12:30 */ return result; } else if ((tm_now.tm_wday + 7 - tm_then.tm_wday) % 7 == 1) { strftime (result, RELATIVE_DATE_MAX, - "Yest. %R", &tm_then); /* Yest. 12:30 */ + "Yest. %R", &tm_then); /* Yest. 12:30 */ return result; } else { if (tm_then.tm_wday != tm_now.tm_wday) { strftime (result, RELATIVE_DATE_MAX, - "%a. %R", &tm_then); /* Mon. 12:30 */ + "%a. %R", &tm_then); /* Mon. 12:30 */ return result; } } } strftime (result, RELATIVE_DATE_MAX, - "%B %d", &tm_then); /* October 12 */ + "%B %d", &tm_then); /* October 12 */ return result; } #undef MINUTE diff --git a/notmuch.c b/notmuch.c index 2ddc8fbc..4ef1484f 100644 --- a/notmuch.c +++ b/notmuch.c @@ -61,9 +61,10 @@ const notmuch_opt_desc_t notmuch_shared_options [] = { * notmuch_process_shared_options (subcommand_name); */ void -notmuch_process_shared_options (const char *subcommand_name) { +notmuch_process_shared_options (const char *subcommand_name) +{ if (print_version) { - printf ("notmuch " STRINGIFY(NOTMUCH_VERSION) "\n"); + printf ("notmuch " STRINGIFY (NOTMUCH_VERSION) "\n"); exit (EXIT_SUCCESS); } @@ -76,8 +77,9 @@ notmuch_process_shared_options (const char *subcommand_name) { /* This is suitable for subcommands that do not actually open the * database. */ -int notmuch_minimal_options (const char *subcommand_name, - int argc, char **argv) +int +notmuch_minimal_options (const char *subcommand_name, + int argc, char **argv) { int opt_index; @@ -98,14 +100,14 @@ int notmuch_minimal_options (const char *subcommand_name, struct _notmuch_client_indexing_cli_choices indexing_cli_choices = { }; -const notmuch_opt_desc_t notmuch_shared_indexing_options [] = { +const notmuch_opt_desc_t notmuch_shared_indexing_options [] = { { .opt_keyword = &indexing_cli_choices.decrypt_policy, .present = &indexing_cli_choices.decrypt_policy_set, .keywords = - (notmuch_keyword_t []){ { "false", NOTMUCH_DECRYPT_FALSE }, - { "true", NOTMUCH_DECRYPT_TRUE }, - { "auto", NOTMUCH_DECRYPT_AUTO }, - { "nostash", NOTMUCH_DECRYPT_NOSTASH }, - { 0, 0 } }, + (notmuch_keyword_t []){ { "false", NOTMUCH_DECRYPT_FALSE }, + { "true", NOTMUCH_DECRYPT_TRUE }, + { "auto", NOTMUCH_DECRYPT_AUTO }, + { "nostash", NOTMUCH_DECRYPT_NOSTASH }, + { 0, 0 } }, .name = "decrypt" }, { } }; @@ -192,7 +194,7 @@ find_command (const char *name) size_t i; for (i = 0; i < ARRAY_SIZE (commands); i++) - if ((!name && !commands[i].name) || + if ((! name && ! commands[i].name) || (name && commands[i].name && strcmp (name, commands[i].name) == 0)) return &commands[i]; @@ -270,11 +272,11 @@ notmuch_exit_if_unmatched_db_uuid (notmuch_database_t *notmuch) { const char *uuid = NULL; - if (!notmuch_requested_db_uuid) + if (! notmuch_requested_db_uuid) return; IGNORE_RESULT (notmuch_database_get_revision (notmuch, &uuid)); - if (strcmp (notmuch_requested_db_uuid, uuid) != 0){ + if (strcmp (notmuch_requested_db_uuid, uuid) != 0) { fprintf (stderr, "Error: requested database revision %s does not match %s\n", notmuch_requested_db_uuid, uuid); exit (1); @@ -297,7 +299,7 @@ _help_for (const char *topic_name) help_topic_t *topic; unsigned int i; - if (!topic_name) { + if (! topic_name) { printf ("The notmuch mail system.\n\n"); usage (stdout); return EXIT_SUCCESS; @@ -333,7 +335,7 @@ _help_for (const char *topic_name) } static int -notmuch_help_command (unused (notmuch_config_t * config), int argc, char *argv[]) +notmuch_help_command (unused (notmuch_config_t *config), int argc, char *argv[]) { int opt_index; @@ -342,8 +344,8 @@ notmuch_help_command (unused (notmuch_config_t * config), int argc, char *argv[] return EXIT_FAILURE; /* skip at least subcommand argument */ - argc-= opt_index; - argv+= opt_index; + argc -= opt_index; + argv += opt_index; if (argc == 0) { return _help_for (NULL); @@ -417,7 +419,8 @@ notmuch_command (notmuch_config_t *config, * executed. Return true if external command is not found. Return * false on errors. */ -static bool try_external_command(char *argv[]) +static bool +try_external_command (char *argv[]) { char *old_argv0 = argv[0]; bool ret = true; @@ -431,7 +434,7 @@ static bool try_external_command(char *argv[]) execvp (argv[0], argv); if (errno != ENOENT) { fprintf (stderr, "Error: Running external command '%s' failed: %s\n", - argv[0], strerror(errno)); + argv[0], strerror (errno)); ret = false; } @@ -464,7 +467,7 @@ main (int argc, char *argv[]) local = talloc_new (NULL); g_mime_init (); -#if !GLIB_CHECK_VERSION(2, 35, 1) +#if ! GLIB_CHECK_VERSION (2, 35, 1) g_type_init (); #endif @@ -484,9 +487,9 @@ main (int argc, char *argv[]) command = find_command (command_name); /* if command->function is NULL, try external command */ - if (!command || !command->function) { + if (! command || ! command->function) { /* This won't return if the external command is found. */ - if (try_external_command(argv + opt_index)) + if (try_external_command (argv + opt_index)) fprintf (stderr, "Error: Unknown command '%s' (see \"notmuch help\")\n", command_name); ret = EXIT_FAILURE; @@ -494,7 +497,7 @@ main (int argc, char *argv[]) } config = notmuch_config_open (local, config_file_name, command->config_mode); - if (!config) { + if (! config) { ret = EXIT_FAILURE; goto DONE; } diff --git a/sprinter.h b/sprinter.h index 9d2e9b6f..182b1a8b 100644 --- a/sprinter.h +++ b/sprinter.h @@ -13,15 +13,15 @@ typedef struct sprinter { * a sequence of alternating calls to map_key and one of the * value-printing functions until the map is ended by end. */ - void (*begin_map) (struct sprinter *); + void (*begin_map)(struct sprinter *); /* Start a new list/array structure. */ - void (*begin_list) (struct sprinter *); + void (*begin_list)(struct sprinter *); /* End the last opened list or map structure. */ - void (*end) (struct sprinter *); + void (*end)(struct sprinter *); /* Print one string/integer/boolean/null element (possibly inside * a list or map, followed or preceded by separators). For string @@ -31,16 +31,16 @@ typedef struct sprinter { * string (but not string_len) the string pointer passed may be * NULL. */ - void (*string) (struct sprinter *, const char *); - void (*string_len) (struct sprinter *, const char *, size_t); - void (*integer) (struct sprinter *, int); - void (*boolean) (struct sprinter *, bool); - void (*null) (struct sprinter *); + void (*string)(struct sprinter *, const char *); + void (*string_len)(struct sprinter *, const char *, size_t); + void (*integer)(struct sprinter *, int); + void (*boolean)(struct sprinter *, bool); + void (*null)(struct sprinter *); /* Print the key of a map's key/value pair. The char * must be UTF-8 * encoded. */ - void (*map_key) (struct sprinter *, const char *); + void (*map_key)(struct sprinter *, const char *); /* Insert a separator (usually extra whitespace). For the text * printers, this is a syntactic separator. For the structured @@ -48,13 +48,13 @@ typedef struct sprinter { * the abstract syntax of the structure being printed. For JSON, * this could simply be a line break. */ - void (*separator) (struct sprinter *); + void (*separator)(struct sprinter *); /* Set the current string prefix. This only affects the text * printer, which will print this string, followed by a colon, * before any string. For other printers, this does nothing. */ - void (*set_prefix) (struct sprinter *, const char *); + void (*set_prefix)(struct sprinter *, const char *); /* True if this is the special-cased plain text printer. */ diff --git a/status.c b/status.c index 01fd8c99..d0ae47f4 100644 --- a/status.c +++ b/status.c @@ -42,8 +42,8 @@ print_status_message (const char *loc, notmuch_status_t print_status_database (const char *loc, - const notmuch_database_t *notmuch, - notmuch_status_t status) + const notmuch_database_t *notmuch, + notmuch_status_t status) { if (status) { const char *msg; diff --git a/tag-util.h b/tag-util.h index ba0d98c8..bbe54d99 100644 --- a/tag-util.h +++ b/tag-util.h @@ -8,25 +8,25 @@ typedef struct _tag_op_list_t tag_op_list_t; /* Use powers of 2 */ typedef enum { - TAG_FLAG_NONE = 0, + TAG_FLAG_NONE = 0, /* Operations are synced to maildir, if possible. */ - TAG_FLAG_MAILDIR_SYNC = (1 << 0), + TAG_FLAG_MAILDIR_SYNC = (1 << 0), /* Remove all tags from message before applying list. */ - TAG_FLAG_REMOVE_ALL = (1 << 1), + TAG_FLAG_REMOVE_ALL = (1 << 1), /* Don't try to avoid database operations. Useful when we * know that message passed needs these operations. */ - TAG_FLAG_PRE_OPTIMIZED = (1 << 2), + TAG_FLAG_PRE_OPTIMIZED = (1 << 2), /* Accept strange tags that might be user error; * intended for use by notmuch-restore. */ - TAG_FLAG_BE_GENEROUS = (1 << 3) + TAG_FLAG_BE_GENEROUS = (1 << 3) } tag_op_flag_t; @@ -34,16 +34,16 @@ typedef enum { * skipped lines are positive. */ typedef enum { - TAG_PARSE_OUT_OF_MEMORY = -1, + TAG_PARSE_OUT_OF_MEMORY = -1, /* Line parsed successfully. */ - TAG_PARSE_SUCCESS = 0, + TAG_PARSE_SUCCESS = 0, /* Line has a syntax error */ - TAG_PARSE_INVALID = 1, + TAG_PARSE_INVALID = 1, /* Line was blank or a comment */ - TAG_PARSE_SKIPPED = 2 + TAG_PARSE_SKIPPED = 2 } tag_parse_status_t; From 1a8916786f9464af6c8a05713a4c987a6b097a12 Mon Sep 17 00:00:00 2001 From: uncrustify Date: Thu, 13 Jun 2019 07:33:13 -0300 Subject: [PATCH 015/427] util: run uncrustify This is the result of running $ uncrustify --replace --config ../devel/uncrustify.cfg *.c *.h in the util directory --- util/crypto.c | 21 +++++++++++--------- util/crypto.h | 4 ++-- util/error_util.h | 4 ++-- util/gmime-extra.c | 48 ++++++++++++++++++++++++++++----------------- util/gmime-extra.h | 14 ++++++------- util/hex-escape.c | 14 ++++++------- util/hex-escape.h | 4 ++-- util/string-util.c | 20 ++++++++++--------- util/string-util.h | 2 +- util/unicode-util.c | 4 ++-- util/util.c | 2 +- util/xutil.c | 2 +- util/zlib-extra.c | 5 +++-- 13 files changed, 81 insertions(+), 63 deletions(-) diff --git a/util/crypto.c b/util/crypto.c index 9e185e03..225f537a 100644 --- a/util/crypto.c +++ b/util/crypto.c @@ -24,7 +24,8 @@ #define ARRAY_SIZE(arr) (sizeof (arr) / sizeof (arr[0])) -void _notmuch_crypto_cleanup (unused(_notmuch_crypto_t *crypto)) +void +_notmuch_crypto_cleanup (unused(_notmuch_crypto_t *crypto)) { } @@ -37,6 +38,7 @@ _notmuch_crypto_decrypt (bool *attempted, GError **err) { GMimeObject *ret = NULL; + if (decrypt == NOTMUCH_DECRYPT_FALSE) return NULL; @@ -78,15 +80,15 @@ _notmuch_crypto_decrypt (bool *attempted, GMimeDecryptFlags flags = GMIME_DECRYPT_NONE; if (decrypt == NOTMUCH_DECRYPT_TRUE && decrypt_result) flags |= GMIME_DECRYPT_EXPORT_SESSION_KEY; - ret = g_mime_multipart_encrypted_decrypt(part, flags, NULL, - decrypt_result, err); + ret = g_mime_multipart_encrypted_decrypt (part, flags, NULL, + decrypt_result, err); return ret; } static int _notmuch_message_crypto_destructor (_notmuch_message_crypto_t *msg_crypto) { - if (!msg_crypto) + if (! msg_crypto) return 0; if (msg_crypto->sig_list) g_object_unref (msg_crypto->sig_list); @@ -99,6 +101,7 @@ _notmuch_message_crypto_t * _notmuch_message_crypto_new (void *ctx) { _notmuch_message_crypto_t *ret = talloc_zero (ctx, _notmuch_message_crypto_t); + talloc_set_destructor (ret, _notmuch_message_crypto_destructor); return ret; } @@ -106,7 +109,7 @@ _notmuch_message_crypto_new (void *ctx) notmuch_status_t _notmuch_message_crypto_potential_sig_list (_notmuch_message_crypto_t *msg_crypto, GMimeSignatureList *sigs) { - if (!msg_crypto) + if (! msg_crypto) return NOTMUCH_STATUS_NULL_POINTER; /* Signatures that arrive after a payload part during DFS are not @@ -139,7 +142,7 @@ _notmuch_message_crypto_potential_payload (_notmuch_message_crypto_t *msg_crypto const char *forwarded = NULL; const char *subject = NULL; - if (!msg_crypto || !payload) + if (! msg_crypto || ! payload) return NOTMUCH_STATUS_NULL_POINTER; /* only fire on the first payload part encountered */ @@ -182,7 +185,7 @@ _notmuch_message_crypto_potential_payload (_notmuch_message_crypto_t *msg_crypto } else { /* Consider "memoryhole"-style protected headers as practiced by Enigmail and K-9 */ protected_headers = g_mime_object_get_content_type_parameter (payload, "protected-headers"); - if (protected_headers && strcasecmp("v1", protected_headers) == 0) + if (protected_headers && strcasecmp ("v1", protected_headers) == 0) subject = g_mime_object_get_header (payload, "Subject"); /* FIXME: handle more than just Subject: at some point */ } @@ -200,12 +203,12 @@ _notmuch_message_crypto_potential_payload (_notmuch_message_crypto_t *msg_crypto notmuch_status_t _notmuch_message_crypto_successful_decryption (_notmuch_message_crypto_t *msg_crypto) { - if (!msg_crypto) + if (! msg_crypto) return NOTMUCH_STATUS_NULL_POINTER; /* see the rationale for different values of * _notmuch_message_decryption_status_t in util/crypto.h */ - if (!msg_crypto->payload_encountered) + if (! msg_crypto->payload_encountered) msg_crypto->decryption_status = NOTMUCH_MESSAGE_DECRYPTED_FULL; else if (msg_crypto->decryption_status == NOTMUCH_MESSAGE_DECRYPTED_NONE) msg_crypto->decryption_status = NOTMUCH_MESSAGE_DECRYPTED_PARTIAL; diff --git a/util/crypto.h b/util/crypto.h index fdbb5da5..11e8060a 100644 --- a/util/crypto.h +++ b/util/crypto.h @@ -50,7 +50,7 @@ typedef struct _notmuch_message_crypto { /* signature status of the whole message (either the whole message * is signed, or it is not) -- this means that partially-signed * messages will get no signature status. */ - GMimeSignatureList * sig_list; + GMimeSignatureList *sig_list; /* if part of the message was signed, and the MUA is clever, it * can determine on its own exactly which part and try to make * more sense of it. */ @@ -62,7 +62,7 @@ typedef struct _notmuch_message_crypto { /* the value of any "Subject:" header in the cryptographic payload * (the top level part within the crypto envelope), converted to * UTF-8 */ - char * payload_subject; + char *payload_subject; /* if both signed and encrypted, was the signature encrypted? */ bool signature_encrypted; diff --git a/util/error_util.h b/util/error_util.h index aa3b77c4..a51f001f 100644 --- a/util/error_util.h +++ b/util/error_util.h @@ -44,8 +44,8 @@ _internal_error (const char *format, ...) PRINTF_ATTRIBUTE (1, 2) NORETURN_ATTRI * * Note that __location__ comes from talloc.h. */ -#define INTERNAL_ERROR(format, ...) \ - _internal_error (format " (%s).\n", \ +#define INTERNAL_ERROR(format, ...) \ + _internal_error (format " (%s).\n", \ ##__VA_ARGS__, __location__) #ifdef __cplusplus diff --git a/util/gmime-extra.c b/util/gmime-extra.c index 7562d906..b515d126 100644 --- a/util/gmime-extra.c +++ b/util/gmime-extra.c @@ -3,7 +3,8 @@ static GMimeStream * -_gzfile_maybe_filter (GMimeStream *file_stream) { +_gzfile_maybe_filter (GMimeStream *file_stream) +{ char buf[4]; int bytes_read; @@ -14,7 +15,7 @@ _gzfile_maybe_filter (GMimeStream *file_stream) { return NULL; /* check for gzipped input */ - if (bytes_read >= 2 && buf[0] == 0x1f && (unsigned char)buf[1] == 0x8b) { + if (bytes_read >= 2 && buf[0] == 0x1f && (unsigned char) buf[1] == 0x8b) { GMimeStream *gzstream; GMimeFilter *gzfilter; @@ -27,7 +28,7 @@ _gzfile_maybe_filter (GMimeStream *file_stream) { return NULL; /* ignore filter id */ - (void)g_mime_stream_filter_add ((GMimeStreamFilter *)gzstream, gzfilter); + (void) g_mime_stream_filter_add ((GMimeStreamFilter *) gzstream, gzfilter); return gzstream; } else { return file_stream; @@ -59,13 +60,13 @@ g_mime_stream_gzfile_open (const char *filename) } GMimeStream * -g_mime_stream_stdout_new() +g_mime_stream_stdout_new () { GMimeStream *stream_stdout = NULL; GMimeStream *stream_buffered = NULL; stream_stdout = g_mime_stream_pipe_new (STDOUT_FILENO); - if (!stream_stdout) + if (! stream_stdout) return NULL; g_mime_stream_pipe_set_owner (GMIME_STREAM_PIPE (stream_stdout), FALSE); @@ -80,10 +81,11 @@ g_mime_stream_stdout_new() /** * copy a glib string into a talloc context, and free it. */ -static char* +static char * g_string_talloc_strdup (void *ctx, char *g_string) { char *new_str = talloc_strdup (ctx, g_string); + g_free (g_string); return new_str; } @@ -93,6 +95,7 @@ g_mime_certificate_get_valid_userid (GMimeCertificate *cert) { /* output user id only if validity is FULL or ULTIMATE. */ const char *uid = g_mime_certificate_get_user_id (cert); + if (uid == NULL) return uid; GMimeValidity validity = g_mime_certificate_get_id_validity (cert); @@ -101,10 +104,12 @@ g_mime_certificate_get_valid_userid (GMimeCertificate *cert) return NULL; } -const char* -g_mime_certificate_get_fpr16 (GMimeCertificate *cert) { +const char * +g_mime_certificate_get_fpr16 (GMimeCertificate *cert) +{ const char *fpr = g_mime_certificate_get_fingerprint (cert); - if (!fpr || strlen (fpr) < 16) + + if (! fpr || strlen (fpr) < 16) return fpr; return fpr + (strlen (fpr) - 16); @@ -114,23 +119,25 @@ char * g_mime_message_get_address_string (GMimeMessage *message, GMimeAddressType type) { InternetAddressList *list = g_mime_message_get_addresses (message, type); + return internet_address_list_to_string (list, NULL, 0); } char * g_mime_message_get_date_string (void *ctx, GMimeMessage *message) { - GDateTime* parsed_date = g_mime_message_get_date (message); + GDateTime *parsed_date = g_mime_message_get_date (message); + if (parsed_date) { char *date = g_mime_utils_header_format_date (parsed_date); return g_string_talloc_strdup (ctx, date); } else { - return talloc_strdup(ctx, "Thu, 01 Jan 1970 00:00:00 +0000"); + return talloc_strdup (ctx, "Thu, 01 Jan 1970 00:00:00 +0000"); } } InternetAddressList * -g_mime_message_get_reply_to_list(GMimeMessage *message) +g_mime_message_get_reply_to_list (GMimeMessage *message) { return g_mime_message_get_reply_to (message); } @@ -145,6 +152,7 @@ char * g_mime_message_get_reply_to_string (void *ctx, GMimeMessage *message) { InternetAddressList *list = g_mime_message_get_reply_to (message); + return g_string_talloc_strdup (ctx, internet_address_list_to_string (list, NULL, 0)); } @@ -161,23 +169,27 @@ g_mime_parser_set_scan_from (GMimeParser *parser, gboolean flag) */ gboolean -g_mime_signature_status_good (GMimeSignatureStatus status) { - return ((status & (GMIME_SIGNATURE_STATUS_RED | GMIME_SIGNATURE_STATUS_ERROR_MASK)) == 0); +g_mime_signature_status_good (GMimeSignatureStatus status) +{ + return ((status & (GMIME_SIGNATURE_STATUS_RED | GMIME_SIGNATURE_STATUS_ERROR_MASK)) == 0); } gboolean -g_mime_signature_status_bad (GMimeSignatureStatus status) { +g_mime_signature_status_bad (GMimeSignatureStatus status) +{ return (status & GMIME_SIGNATURE_STATUS_RED); } gboolean -g_mime_signature_status_error (GMimeSignatureStatus status) { +g_mime_signature_status_error (GMimeSignatureStatus status) +{ return (status & GMIME_SIGNATURE_STATUS_ERROR_MASK); } gint64 -g_mime_utils_header_decode_date_unix (const char *date) { - GDateTime* parsed_date = g_mime_utils_header_decode_date (date); +g_mime_utils_header_decode_date_unix (const char *date) +{ + GDateTime *parsed_date = g_mime_utils_header_decode_date (date); time_t ret; if (parsed_date) { diff --git a/util/gmime-extra.h b/util/gmime-extra.h index b0c8d3d8..094309ec 100644 --- a/util/gmime-extra.h +++ b/util/gmime-extra.h @@ -7,7 +7,7 @@ extern "C" { #endif -GMimeStream *g_mime_stream_stdout_new(void); +GMimeStream *g_mime_stream_stdout_new (void); /* Return a GMime stream for this open file descriptor, un-gzipping if * necessary */ @@ -27,7 +27,7 @@ const char *g_mime_certificate_get_fpr16 (GMimeCertificate *cert); */ char *g_mime_message_get_address_string (GMimeMessage *message, GMimeAddressType type); -InternetAddressList * g_mime_message_get_addresses (GMimeMessage *message, GMimeAddressType type); +InternetAddressList *g_mime_message_get_addresses (GMimeMessage *message, GMimeAddressType type); /** * return talloc allocated date string @@ -39,21 +39,21 @@ char *g_mime_message_get_date_string (void *ctx, GMimeMessage *message); * glib allocated list of From: addresses */ -InternetAddressList * g_mime_message_get_from (GMimeMessage *message); +InternetAddressList *g_mime_message_get_from (GMimeMessage *message); /** * return string for From: address * (owned by gmime) */ -const char * g_mime_message_get_from_string (GMimeMessage *message); +const char *g_mime_message_get_from_string (GMimeMessage *message); -InternetAddressList * g_mime_message_get_reply_to_list (GMimeMessage *message); +InternetAddressList *g_mime_message_get_reply_to_list (GMimeMessage *message); /** * return talloc allocated reply-to string */ -char * g_mime_message_get_reply_to_string (void *ctx, GMimeMessage *message); +char *g_mime_message_get_reply_to_string (void *ctx, GMimeMessage *message); void g_mime_parser_set_scan_from (GMimeParser *parser, gboolean flag); @@ -68,7 +68,7 @@ gint64 g_mime_utils_header_decode_date_unix (const char *date); /** * Return string for valid User ID (or NULL if no valid User ID exists) */ -const char * g_mime_certificate_get_valid_userid (GMimeCertificate *cert); +const char *g_mime_certificate_get_valid_userid (GMimeCertificate *cert); #ifdef __cplusplus } diff --git a/util/hex-escape.c b/util/hex-escape.c index 8883ff90..81534a8c 100644 --- a/util/hex-escape.c +++ b/util/hex-escape.c @@ -72,7 +72,7 @@ hex_encode (void *ctx, const char *in, char **out, size_t *out_size) if (*out == NULL) *out_size = 0; - if (!maybe_realloc (ctx, needed, out, out_size)) + if (! maybe_realloc (ctx, needed, out, out_size)) return HEX_OUT_OF_MEMORY; q = *out; @@ -82,7 +82,7 @@ hex_encode (void *ctx, const char *in, char **out, size_t *out_size) if (is_output (*p)) { *q++ = *p++; } else { - sprintf (q, "%%%02x", (unsigned char)*p++); + sprintf (q, "%%%02x", (unsigned char) *p++); q += 3; } } @@ -105,8 +105,8 @@ hex_decode_internal (const char *in, unsigned char *out) char *endp; /* This also handles unexpected end-of-string. */ - if (!isxdigit ((unsigned char) in[1]) || - !isxdigit ((unsigned char) in[2])) + if (! isxdigit ((unsigned char) in[1]) || + ! isxdigit ((unsigned char) in[2])) return HEX_SYNTAX_ERROR; buf[0] = in[1]; @@ -139,10 +139,10 @@ hex_decode_inplace (char *s) } hex_status_t -hex_decode (void *ctx, const char *in, char **out, size_t * out_size) +hex_decode (void *ctx, const char *in, char **out, size_t *out_size) { const char *p; - size_t needed = 1; /* for the NUL */ + size_t needed = 1; /* for the NUL */ assert (ctx); assert (in); assert (out); assert (out_size); @@ -152,7 +152,7 @@ hex_decode (void *ctx, const char *in, char **out, size_t * out_size) else needed += 1; - if (!maybe_realloc (ctx, needed, out, out_size)) + if (! maybe_realloc (ctx, needed, out, out_size)) return HEX_OUT_OF_MEMORY; return hex_decode_internal (in, (unsigned char *) *out); diff --git a/util/hex-escape.h b/util/hex-escape.h index 50d946ed..8703334c 100644 --- a/util/hex-escape.h +++ b/util/hex-escape.h @@ -29,11 +29,11 @@ typedef enum hex_status { hex_status_t hex_encode (void *talloc_ctx, const char *in, char **out, - size_t *out_size); + size_t *out_size); hex_status_t hex_decode (void *talloc_ctx, const char *in, char **out, - size_t *out_size); + size_t *out_size); /* * Non-allocating hex decode to decode 's' in-place. The length of the diff --git a/util/string-util.c b/util/string-util.c index fc2058e0..de8430b2 100644 --- a/util/string-util.c +++ b/util/string-util.c @@ -42,7 +42,7 @@ strtok_len_c (const char *s, const char *delim, size_t *len) { /* strtok_len is already const-safe, but we can't express both * versions in the C type system. */ - return strtok_len ((char*)s, delim, len); + return strtok_len ((char *) s, delim, len); } char * @@ -60,7 +60,7 @@ sanitize_string (const void *ctx, const char *str) for (loop = out; *loop; loop++) { if (*loop == '\t' || *loop == '\n') *loop = ' '; - else if ((unsigned char)(*loop) < 32) + else if ((unsigned char) (*loop) < 32) *loop = '?'; } @@ -87,9 +87,9 @@ make_boolean_term (void *ctx, const char *prefix, const char *term, * beginning, and anything containing non-ASCII text. */ if (! term[0]) need_quoting = 1; - for (in = term; *in && !need_quoting; in++) + for (in = term; *in && ! need_quoting; in++) if (is_unquoted_terminator (*in) || *in == '"' || *in == '(' - || (unsigned char)*in > 127) + || (unsigned char) *in > 127) need_quoting = 1; if (need_quoting) @@ -141,7 +141,7 @@ make_boolean_term (void *ctx, const char *prefix, const char *term, return 0; } -const char* +const char * skip_space (const char *str) { while (*str && isspace ((unsigned char) *str)) @@ -154,6 +154,7 @@ parse_boolean_term (void *ctx, const char *str, char **prefix_out, char **term_out) { int err = EINVAL; + *prefix_out = *term_out = NULL; /* Parse prefix */ @@ -193,7 +194,7 @@ parse_boolean_term (void *ctx, const char *str, } /* Did the term terminate without a closing quote or is there * trailing text after the closing quote? */ - if (!closed || *pos) + if (! closed || *pos) goto FAIL; *out = '\0'; } else { @@ -215,7 +216,7 @@ parse_boolean_term (void *ctx, const char *str, } return 0; - FAIL: + FAIL: talloc_free (*prefix_out); talloc_free (*term_out); errno = err; @@ -230,9 +231,9 @@ strcmp_null (const char *s1, const char *s2) else if (! s1 && ! s2) return 0; else if (s1) - return 1; /* s1 (non-NULL) is greater than s2 (NULL) */ + return 1; /* s1 (non-NULL) is greater than s2 (NULL) */ else - return -1; /* s1 (NULL) is less than s2 (non-NULL) */ + return -1; /* s1 (NULL) is less than s2 (non-NULL) */ } int @@ -248,6 +249,7 @@ strcase_hash (const void *ptr) /* This is the djb2 hash. */ unsigned int hash = 5381; + while (s && *s) { hash = ((hash << 5) + hash) + tolower (*s); s++; diff --git a/util/string-util.h b/util/string-util.h index 4c110a20..fb95a740 100644 --- a/util/string-util.h +++ b/util/string-util.h @@ -77,7 +77,7 @@ unsigned int strcase_hash (const void *ptr); void strip_trailing (char *str, char ch); -const char* skip_space (const char *str); +const char *skip_space (const char *str); #ifdef __cplusplus } diff --git a/util/unicode-util.c b/util/unicode-util.c index 312e900f..ccb787e2 100644 --- a/util/unicode-util.c +++ b/util/unicode-util.c @@ -1,8 +1,8 @@ #include "unicode-util.h" /* Based on Xapian::Unicode::is_wordchar, to avoid forcing clients to - link directly to libxapian. -*/ + * link directly to libxapian. + */ static bool unicode_is_wordchar (notmuch_unichar ch) diff --git a/util/util.c b/util/util.c index 06659b35..6abe2215 100644 --- a/util/util.c +++ b/util/util.c @@ -19,6 +19,6 @@ util_error_string (util_status_t errnum) /* we lack context to be more informative here */ return "zlib error"; default: - INTERNAL_ERROR("unexpected error status %d", errnum); + INTERNAL_ERROR ("unexpected error status %d", errnum); } } diff --git a/util/xutil.c b/util/xutil.c index f211eaaa..07a00343 100644 --- a/util/xutil.c +++ b/util/xutil.c @@ -111,7 +111,7 @@ xregcomp (regex_t *preg, const char *regex, int cflags) regerror (rerr, preg, error, error_size); fprintf (stderr, "compiling regex %s: %s\n", - regex, error); + regex, error); free (error); return 1; } diff --git a/util/zlib-extra.c b/util/zlib-extra.c index 2b2cd8f9..f691cccf 100644 --- a/util/zlib-extra.c +++ b/util/zlib-extra.c @@ -70,13 +70,14 @@ gz_getline (void *talloc_ctx, char **bufptr, ssize_t *bytes_read, gzFile stream) if (buf == NULL) return UTIL_OUT_OF_MEMORY; } - SUCCESS: + SUCCESS: *bufptr = buf; *bytes_read = offset; return UTIL_SUCCESS; } -const char *gz_error_string (util_status_t status, gzFile file) +const char * +gz_error_string (util_status_t status, gzFile file) { if (status == UTIL_GZERROR) return gzerror (file, NULL); From 8099050c7191525c63b03e8b175620d2a2c1a9a3 Mon Sep 17 00:00:00 2001 From: uncrustify Date: Thu, 13 Jun 2019 07:34:25 -0300 Subject: [PATCH 016/427] compat: run uncrustify This is the result of running $ uncrustify --replace --config ../devel/uncrustify.cfg *.c *.h in the compat directory --- compat/canonicalize_file_name.c | 4 +- compat/check_asctime.c | 3 +- compat/check_getpwuid.c | 3 +- compat/compat.h | 32 +++--- compat/function-attributes.h | 6 +- compat/gen_zlib_pc.c | 23 ++-- compat/getdelim.c | 152 +++++++++++++-------------- compat/getline.c | 34 +++--- compat/have_canonicalize_file_name.c | 3 +- compat/have_d_type.c | 3 +- compat/have_getline.c | 5 +- compat/have_strcasestr.c | 5 +- compat/have_strsep.c | 5 +- compat/have_timegm.c | 5 +- compat/strcasestr.c | 50 ++++----- compat/strsep.c | 96 ++++++++--------- compat/timegm.c | 38 +++---- 17 files changed, 233 insertions(+), 234 deletions(-) diff --git a/compat/canonicalize_file_name.c b/compat/canonicalize_file_name.c index e92c0f62..000f9e78 100644 --- a/compat/canonicalize_file_name.c +++ b/compat/canonicalize_file_name.c @@ -4,10 +4,10 @@ #include char * -canonicalize_file_name (const char * path) +canonicalize_file_name (const char *path) { #ifdef PATH_MAX - char *resolved_path = malloc (PATH_MAX+1); + char *resolved_path = malloc (PATH_MAX + 1); if (resolved_path == NULL) return NULL; diff --git a/compat/check_asctime.c b/compat/check_asctime.c index b0e56f0c..62ad69d6 100644 --- a/compat/check_asctime.c +++ b/compat/check_asctime.c @@ -1,7 +1,8 @@ #include #include -int main() +int +main () { struct tm tm; diff --git a/compat/check_getpwuid.c b/compat/check_getpwuid.c index c435eb89..babeb742 100644 --- a/compat/check_getpwuid.c +++ b/compat/check_getpwuid.c @@ -1,7 +1,8 @@ #include #include -int main() +int +main () { struct passwd passwd, *ignored; diff --git a/compat/compat.h b/compat/compat.h index 88bc4df4..8f15e585 100644 --- a/compat/compat.h +++ b/compat/compat.h @@ -30,14 +30,14 @@ extern "C" { #endif -#if !STD_GETPWUID +#if ! STD_GETPWUID #define _POSIX_PTHREAD_SEMANTICS 1 #endif -#if !STD_ASCTIME +#if ! STD_ASCTIME #define _POSIX_PTHREAD_SEMANTICS 1 #endif -#if !HAVE_CANONICALIZE_FILE_NAME +#if ! HAVE_CANONICALIZE_FILE_NAME /* we only call this function from C, and this makes testing easier */ #ifndef __cplusplus char * @@ -45,7 +45,7 @@ canonicalize_file_name (const char *path); #endif #endif -#if !HAVE_GETLINE +#if ! HAVE_GETLINE #include #include @@ -55,31 +55,31 @@ getline (char **lineptr, size_t *n, FILE *stream); ssize_t getdelim (char **lineptr, size_t *n, int delimiter, FILE *fp); -#endif /* !HAVE_GETLINE */ +#endif /* !HAVE_GETLINE */ -#if !HAVE_STRCASESTR -char* strcasestr(const char *haystack, const char *needle); -#endif /* !HAVE_STRCASESTR */ +#if ! HAVE_STRCASESTR +char *strcasestr (const char *haystack, const char *needle); +#endif /* !HAVE_STRCASESTR */ -#if !HAVE_STRSEP -char *strsep(char **stringp, const char *delim); -#endif /* !HAVE_STRSEP */ +#if ! HAVE_STRSEP +char *strsep (char **stringp, const char *delim); +#endif /* !HAVE_STRSEP */ -#if !HAVE_TIMEGM +#if ! HAVE_TIMEGM #include time_t timegm (struct tm *tm); -#endif /* !HAVE_TIMEGM */ +#endif /* !HAVE_TIMEGM */ /* Silence gcc warnings about unused results. These warnings exist * for a reason; any use of this needs to be justified. */ #ifdef __GNUC__ -#define IGNORE_RESULT(x) ({ __typeof__(x) __z = (x); (void)(__z = __z); }) +#define IGNORE_RESULT(x) ({ __typeof__(x) __z = (x); (void) (__z = __z); }) #else /* !__GNUC__ */ #define IGNORE_RESULT(x) x -#endif /* __GNUC__ */ +#endif /* __GNUC__ */ #ifdef __cplusplus } #endif -#endif /* NOTMUCH_COMPAT_H */ +#endif /* NOTMUCH_COMPAT_H */ diff --git a/compat/function-attributes.h b/compat/function-attributes.h index 1945b5bf..8f08bec4 100644 --- a/compat/function-attributes.h +++ b/compat/function-attributes.h @@ -35,9 +35,9 @@ * provides support for testing for function attributes. */ #ifndef NORETURN_ATTRIBUTE -#if (__GNUC__ >= 3 || \ - (__GNUC__ == 2 && __GNUC_MINOR__ >= 5) || \ - __has_attribute (noreturn)) +#if (__GNUC__ >= 3 || \ + (__GNUC__ == 2 && __GNUC_MINOR__ >= 5) || \ + __has_attribute (noreturn)) #define NORETURN_ATTRIBUTE __attribute__ ((noreturn)) #else #define NORETURN_ATTRIBUTE diff --git a/compat/gen_zlib_pc.c b/compat/gen_zlib_pc.c index 198a727c..7c0ee727 100644 --- a/compat/gen_zlib_pc.c +++ b/compat/gen_zlib_pc.c @@ -2,17 +2,18 @@ #include static const char *template = - "prefix=/usr\n" - "exec_prefix=${prefix}\n" - "libdir=${exec_prefix}/lib\n" - "\n" - "Name: zlib\n" - "Description: zlib compression library\n" - "Version: %s\n" - "Libs: -lz\n"; + "prefix=/usr\n" + "exec_prefix=${prefix}\n" + "libdir=${exec_prefix}/lib\n" + "\n" + "Name: zlib\n" + "Description: zlib compression library\n" + "Version: %s\n" + "Libs: -lz\n"; -int main(void) +int +main (void) { - printf(template, ZLIB_VERSION); - return 0; + printf (template, ZLIB_VERSION); + return 0; } diff --git a/compat/getdelim.c b/compat/getdelim.c index 407f3d07..e5c1f07c 100644 --- a/compat/getdelim.c +++ b/compat/getdelim.c @@ -1,21 +1,21 @@ /* getdelim.c --- Implementation of replacement getdelim function. - Copyright (C) 1994, 1996, 1997, 1998, 2001, 2003, 2005, 2006, 2007, - 2008, 2009 Free Software Foundation, Inc. - - This program is free software; you can redistribute it and/or - modify it under the terms of the GNU General Public License as - published by the Free Software Foundation; either version 3, or (at - your option) any later version. - - This program is distributed in the hope that it will be useful, but - WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA - 02110-1301, USA. */ + * Copyright (C) 1994, 1996, 1997, 1998, 2001, 2003, 2005, 2006, 2007, + * 2008, 2009 Free Software Foundation, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 3, or (at + * your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. */ /* Ported from glibc by Simon Josefsson. */ @@ -34,100 +34,92 @@ #if USE_UNLOCKED_IO # include "unlocked-io.h" -# define getc_maybe_unlocked(fp) getc(fp) -#elif !HAVE_FLOCKFILE || !HAVE_FUNLOCKFILE || !HAVE_DECL_GETC_UNLOCKED +# define getc_maybe_unlocked(fp) getc (fp) +#elif ! HAVE_FLOCKFILE || ! HAVE_FUNLOCKFILE || ! HAVE_DECL_GETC_UNLOCKED # undef flockfile # undef funlockfile # define flockfile(x) ((void) 0) # define funlockfile(x) ((void) 0) -# define getc_maybe_unlocked(fp) getc(fp) +# define getc_maybe_unlocked(fp) getc (fp) #else -# define getc_maybe_unlocked(fp) getc_unlocked(fp) +# define getc_maybe_unlocked(fp) getc_unlocked (fp) #endif /* Read up to (and including) a DELIMITER from FP into *LINEPTR (and - NUL-terminate it). *LINEPTR is a pointer returned from malloc (or - NULL), pointing to *N characters of space. It is realloc'ed as - necessary. Returns the number of characters read (not including - the null terminator), or -1 on error or EOF. */ + * NUL-terminate it). *LINEPTR is a pointer returned from malloc (or + * NULL), pointing to *N characters of space. It is realloc'ed as + * necessary. Returns the number of characters read (not including + * the null terminator), or -1 on error or EOF. */ ssize_t getdelim (char **lineptr, size_t *n, int delimiter, FILE *fp) { - ssize_t result = -1; - size_t cur_len = 0; + ssize_t result = -1; + size_t cur_len = 0; - if (lineptr == NULL || n == NULL || fp == NULL) - { - errno = EINVAL; - return -1; + if (lineptr == NULL || n == NULL || fp == NULL) { + errno = EINVAL; + return -1; } - flockfile (fp); + flockfile (fp); - if (*lineptr == NULL || *n == 0) - { - char *new_lineptr; - *n = 120; - new_lineptr = (char *) realloc (*lineptr, *n); - if (new_lineptr == NULL) - { - result = -1; - goto unlock_return; + if (*lineptr == NULL || *n == 0) { + char *new_lineptr; + *n = 120; + new_lineptr = (char *) realloc (*lineptr, *n); + if (new_lineptr == NULL) { + result = -1; + goto unlock_return; } - *lineptr = new_lineptr; + *lineptr = new_lineptr; } - for (;;) - { - int i; + for (;;) { + int i; - i = getc_maybe_unlocked (fp); - if (i == EOF) - { - result = -1; - break; + i = getc_maybe_unlocked (fp); + if (i == EOF) { + result = -1; + break; } - /* Make enough space for len+1 (for final NUL) bytes. */ - if (cur_len + 1 >= *n) - { - size_t needed_max = - SSIZE_MAX < SIZE_MAX ? (size_t) SSIZE_MAX + 1 : SIZE_MAX; - size_t needed = 2 * *n + 1; /* Be generous. */ - char *new_lineptr; + /* Make enough space for len+1 (for final NUL) bytes. */ + if (cur_len + 1 >= *n) { + size_t needed_max = + SSIZE_MAX < SIZE_MAX ? (size_t) SSIZE_MAX + 1 : SIZE_MAX; + size_t needed = 2 * *n + 1; /* Be generous. */ + char *new_lineptr; - if (needed_max < needed) - needed = needed_max; - if (cur_len + 1 >= needed) - { - result = -1; - errno = EOVERFLOW; - goto unlock_return; + if (needed_max < needed) + needed = needed_max; + if (cur_len + 1 >= needed) { + result = -1; + errno = EOVERFLOW; + goto unlock_return; } - new_lineptr = (char *) realloc (*lineptr, needed); - if (new_lineptr == NULL) - { - result = -1; - goto unlock_return; + new_lineptr = (char *) realloc (*lineptr, needed); + if (new_lineptr == NULL) { + result = -1; + goto unlock_return; } - *lineptr = new_lineptr; - *n = needed; + *lineptr = new_lineptr; + *n = needed; } - (*lineptr)[cur_len] = i; - cur_len++; + (*lineptr)[cur_len] = i; + cur_len++; - if (i == delimiter) - break; + if (i == delimiter) + break; } - (*lineptr)[cur_len] = '\0'; - result = cur_len ? (ssize_t) cur_len : result; + (*lineptr)[cur_len] = '\0'; + result = cur_len ? (ssize_t) cur_len : result; - unlock_return: - funlockfile (fp); /* doesn't set errno */ + unlock_return: + funlockfile (fp); /* doesn't set errno */ - return result; + return result; } diff --git a/compat/getline.c b/compat/getline.c index 222e0f6c..2fcaba14 100644 --- a/compat/getline.c +++ b/compat/getline.c @@ -1,20 +1,20 @@ /* getline.c --- Implementation of replacement getline function. - Copyright (C) 2005, 2006, 2007 Free Software Foundation, Inc. - - This program is free software; you can redistribute it and/or - modify it under the terms of the GNU General Public License as - published by the Free Software Foundation; either version 3, or (at - your option) any later version. - - This program is distributed in the hope that it will be useful, but - WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA - 02110-1301, USA. */ + * Copyright (C) 2005, 2006, 2007 Free Software Foundation, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 3, or (at + * your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. */ /* Written by Simon Josefsson. */ @@ -25,5 +25,5 @@ ssize_t getline (char **lineptr, size_t *n, FILE *stream) { - return getdelim (lineptr, n, '\n', stream); + return getdelim (lineptr, n, '\n', stream); } diff --git a/compat/have_canonicalize_file_name.c b/compat/have_canonicalize_file_name.c index 24c848ec..e5609793 100644 --- a/compat/have_canonicalize_file_name.c +++ b/compat/have_canonicalize_file_name.c @@ -1,7 +1,8 @@ #define _GNU_SOURCE #include -int main() +int +main () { char *found; char *string; diff --git a/compat/have_d_type.c b/compat/have_d_type.c index 9ca6c6e0..5338ee4d 100644 --- a/compat/have_d_type.c +++ b/compat/have_d_type.c @@ -1,6 +1,7 @@ #include -int main() +int +main () { struct dirent ent; diff --git a/compat/have_getline.c b/compat/have_getline.c index a8bcd17e..6952a3b3 100644 --- a/compat/have_getline.c +++ b/compat/have_getline.c @@ -2,12 +2,13 @@ #include #include -int main() +int +main () { ssize_t count = 0; size_t n = 0; char **lineptr = NULL; FILE *stream = NULL; - count = getline(lineptr, &n, stream); + count = getline (lineptr, &n, stream); } diff --git a/compat/have_strcasestr.c b/compat/have_strcasestr.c index c0fb7629..3cd1838d 100644 --- a/compat/have_strcasestr.c +++ b/compat/have_strcasestr.c @@ -1,10 +1,11 @@ #define _GNU_SOURCE #include -int main() +int +main () { char *found; const char *haystack, *needle; - found = strcasestr(haystack, needle); + found = strcasestr (haystack, needle); } diff --git a/compat/have_strsep.c b/compat/have_strsep.c index 2abab819..dd4aae75 100644 --- a/compat/have_strsep.c +++ b/compat/have_strsep.c @@ -1,11 +1,12 @@ #define _GNU_SOURCE #include -int main() +int +main () { char *found; char **stringp; const char *delim; - found = strsep(stringp, delim); + found = strsep (stringp, delim); } diff --git a/compat/have_timegm.c b/compat/have_timegm.c index 483fc3b6..8f7b380e 100644 --- a/compat/have_timegm.c +++ b/compat/have_timegm.c @@ -1,6 +1,7 @@ #include -int main() +int +main () { - return (int) timegm((struct tm *)0); + return (int) timegm ((struct tm *) 0); } diff --git a/compat/strcasestr.c b/compat/strcasestr.c index 62a3a545..d4480bf3 100644 --- a/compat/strcasestr.c +++ b/compat/strcasestr.c @@ -3,20 +3,20 @@ * don't include it in their library * * based on a GPL implementation in OpenTTD found under GPL v2 - - This program is free software; you can redistribute it and/or - modify it under the terms of the GNU General Public License as - published by the Free Software Foundation, version 2. - - This program is distributed in the hope that it will be useful, but - WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA - 02110-1301, USA. */ + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, version 2. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. */ /* Imported into notmuch by Dirk Hohndel - original author unknown. */ @@ -24,17 +24,19 @@ #include "compat.h" -char *strcasestr(const char *haystack, const char *needle) +char * +strcasestr (const char *haystack, const char *needle) { - size_t hay_len = strlen(haystack); - size_t needle_len = strlen(needle); - while (hay_len >= needle_len) { - if (strncasecmp(haystack, needle, needle_len) == 0) - return (char *) haystack; + size_t hay_len = strlen (haystack); + size_t needle_len = strlen (needle); - haystack++; - hay_len--; - } + while (hay_len >= needle_len) { + if (strncasecmp (haystack, needle, needle_len) == 0) + return (char *) haystack; - return NULL; + haystack++; + hay_len--; + } + + return NULL; } diff --git a/compat/strsep.c b/compat/strsep.c index 78ab9e71..4b6926d9 100644 --- a/compat/strsep.c +++ b/compat/strsep.c @@ -1,65 +1,61 @@ /* Copyright (C) 1992, 93, 96, 97, 98, 99, 2004 Free Software Foundation, Inc. - This file is part of the GNU C Library. - - The GNU C Library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - - The GNU C Library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with the GNU C Library; if not, write to the Free - Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA - 02111-1307 USA. */ + * This file is part of the GNU C Library. + * + * The GNU C Library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * The GNU C Library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with the GNU C Library; if not, write to the Free + * Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA + * 02111-1307 USA. */ #include /* Taken from glibc 2.6.1 */ -char *strsep (char **stringp, const char *delim) +char * +strsep (char **stringp, const char *delim) { - char *begin, *end; + char *begin, *end; - begin = *stringp; - if (begin == NULL) - return NULL; + begin = *stringp; + if (begin == NULL) + return NULL; - /* A frequent case is when the delimiter string contains only one - character. Here we don't need to call the expensive `strpbrk' - function and instead work using `strchr'. */ - if (delim[0] == '\0' || delim[1] == '\0') - { - char ch = delim[0]; + /* A frequent case is when the delimiter string contains only one + * character. Here we don't need to call the expensive `strpbrk' + * function and instead work using `strchr'. */ + if (delim[0] == '\0' || delim[1] == '\0') { + char ch = delim[0]; - if (ch == '\0') - end = NULL; - else - { - if (*begin == ch) - end = begin; - else if (*begin == '\0') + if (ch == '\0') end = NULL; - else - end = strchr (begin + 1, ch); + else { + if (*begin == ch) + end = begin; + else if (*begin == '\0') + end = NULL; + else + end = strchr (begin + 1, ch); } - } - else - /* Find the end of the token. */ - end = strpbrk (begin, delim); + } else + /* Find the end of the token. */ + end = strpbrk (begin, delim); - if (end) - { - /* Terminate the token and set *STRINGP past NUL character. */ - *end++ = '\0'; - *stringp = end; - } - else - /* No more delimiters; this is the last token. */ - *stringp = NULL; + if (end) { + /* Terminate the token and set *STRINGP past NUL character. */ + *end++ = '\0'; + *stringp = end; + } else + /* No more delimiters; this is the last token. */ + *stringp = NULL; - return begin; + return begin; } diff --git a/compat/timegm.c b/compat/timegm.c index 3560c370..005a4239 100644 --- a/compat/timegm.c +++ b/compat/timegm.c @@ -1,19 +1,19 @@ /* timegm.c --- Implementation of replacement timegm function. - - This program is free software; you can redistribute it and/or - modify it under the terms of the GNU General Public License as - published by the Free Software Foundation; either version 3, or (at - your option) any later version. - - This program is distributed in the hope that it will be useful, but - WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA - 02110-1301, USA. */ + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 3, or (at + * your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. */ /* Copyright 2013 Blake Jones. */ @@ -35,20 +35,20 @@ leapyear (int year) time_t timegm (struct tm *tm) { - int monthlen[2][12] = { + int monthlen[2][12] = { { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }, { 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }, }; - int year, month, days; + int year, month, days; days = 365 * (tm->tm_year - 70); for (year = 70; year < tm->tm_year; year++) { - if (leapyear(1900 + year)) { + if (leapyear (1900 + year)) { days++; } } for (month = 0; month < tm->tm_mon; month++) { - days += monthlen[leapyear(1900 + year)][month]; + days += monthlen[leapyear (1900 + year)][month]; } days += tm->tm_mday - 1; From 8a3f86f2f9b53d34e001537b113628ceba20a02d Mon Sep 17 00:00:00 2001 From: uncrustify Date: Thu, 13 Jun 2019 07:35:36 -0300 Subject: [PATCH 017/427] parse-time-string: run uncrustify This is the result of running $ uncrustify --replace --config ../devel/uncrustify.cfg *.c *.h in the parse-time-string directory --- parse-time-string/parse-time-string.c | 306 +++++++++++++------------- parse-time-string/parse-time-string.h | 26 +-- 2 files changed, 169 insertions(+), 163 deletions(-) diff --git a/parse-time-string/parse-time-string.c b/parse-time-string/parse-time-string.c index 48ec5b0c..33372ece 100644 --- a/parse-time-string/parse-time-string.c +++ b/parse-time-string/parse-time-string.c @@ -102,8 +102,8 @@ /* XXX: Redefine these to add i18n support. The keyword table uses * N_() to mark strings to be translated; they are accessed * dynamically using _(). */ -#define _(s) (s) /* i18n: define as gettext (s) */ -#define N_(s) (s) /* i18n: define as gettext_noop (s) */ +#define _(s) (s) /* i18n: define as gettext (s) */ +#define N_(s) (s) /* i18n: define as gettext_noop (s) */ #define ARRAY_SIZE(a) (sizeof (a) / sizeof (a[0])) @@ -114,40 +114,40 @@ */ enum field { /* Keep SEC...YEAR in this order. */ - TM_ABS_SEC, /* seconds */ - TM_ABS_MIN, /* minutes */ - TM_ABS_HOUR, /* hours */ - TM_ABS_MDAY, /* day of the month */ - TM_ABS_MON, /* month */ - TM_ABS_YEAR, /* year */ + TM_ABS_SEC, /* seconds */ + TM_ABS_MIN, /* minutes */ + TM_ABS_HOUR, /* hours */ + TM_ABS_MDAY, /* day of the month */ + TM_ABS_MON, /* month */ + TM_ABS_YEAR, /* year */ - TM_WDAY, /* day of the week. special: may be relative */ - TM_ABS_ISDST, /* daylight saving time */ + TM_WDAY, /* day of the week. special: may be relative */ + TM_ABS_ISDST, /* daylight saving time */ - TM_AMPM, /* am vs. pm */ - TM_TZ, /* timezone in minutes */ + TM_AMPM, /* am vs. pm */ + TM_TZ, /* timezone in minutes */ /* Keep SEC...YEAR in this order. */ - TM_REL_SEC, /* seconds relative to absolute or reference time */ - TM_REL_MIN, /* minutes ... */ - TM_REL_HOUR, /* hours ... */ - TM_REL_DAY, /* days ... */ - TM_REL_MON, /* months ... */ - TM_REL_YEAR, /* years ... */ - TM_REL_WEEK, /* weeks ... */ + TM_REL_SEC, /* seconds relative to absolute or reference time */ + TM_REL_MIN, /* minutes ... */ + TM_REL_HOUR, /* hours ... */ + TM_REL_DAY, /* days ... */ + TM_REL_MON, /* months ... */ + TM_REL_YEAR, /* years ... */ + TM_REL_WEEK, /* weeks ... */ - TM_NONE, /* not a field */ + TM_NONE, /* not a field */ - TM_SIZE = TM_NONE, - TM_FIRST_ABS = TM_ABS_SEC, - TM_FIRST_REL = TM_REL_SEC, + TM_SIZE = TM_NONE, + TM_FIRST_ABS = TM_ABS_SEC, + TM_FIRST_REL = TM_REL_SEC, }; /* Values for the set array of struct state. */ enum field_set { - FIELD_UNSET, /* The field has not been touched by parser. */ - FIELD_SET, /* The field has been set by parser. */ - FIELD_NOW, /* The field will be set to reference time. */ + FIELD_UNSET, /* The field has not been touched by parser. */ + FIELD_SET, /* The field has been set by parser. */ + FIELD_NOW, /* The field will be set to reference time. */ }; static enum field @@ -180,15 +180,15 @@ get_field_epoch_value (enum field field) /* The parsing state. */ struct state { - int tm[TM_SIZE]; /* parsed date and time */ - enum field_set set[TM_SIZE]; /* set status of tm */ + int tm[TM_SIZE]; /* parsed date and time */ + enum field_set set[TM_SIZE]; /* set status of tm */ - enum field last_field; /* Previously set field. */ + enum field last_field; /* Previously set field. */ char delim; - int postponed_length; /* Number of digits in postponed value. */ + int postponed_length; /* Number of digits in postponed value. */ int postponed_value; - char postponed_delim; /* The delimiter preceding postponed number. */ + char postponed_delim; /* The delimiter preceding postponed number. */ }; /* @@ -215,7 +215,7 @@ get_postponed_length (struct state *state) static bool consume_postponed_number (struct state *state, int *v, int *n, char *d) { - if (!state->postponed_length) + if (! state->postponed_length) return false; if (n) @@ -383,12 +383,14 @@ get_field (struct state *state, enum field field) /* * Validity checkers. */ -static bool is_valid_12hour (int h) +static bool +is_valid_12hour (int h) { return h >= 1 && h <= 12; } -static bool is_valid_time (int h, int m, int s) +static bool +is_valid_time (int h, int m, int s) { /* Allow 24:00:00 to denote end of day. */ if (h == 24 && m == 0 && s == 0) @@ -397,22 +399,26 @@ static bool is_valid_time (int h, int m, int s) return h >= 0 && h <= 23 && m >= 0 && m <= 59 && s >= 0 && s <= 59; } -static bool is_valid_mday (int mday) +static bool +is_valid_mday (int mday) { return mday >= 1 && mday <= 31; } -static bool is_valid_mon (int mon) +static bool +is_valid_mon (int mon) { return mon >= 1 && mon <= 12; } -static bool is_valid_year (int year) +static bool +is_valid_year (int year) { return year >= 1970; } -static bool is_valid_date (int year, int mon, int mday) +static bool +is_valid_date (int year, int mon, int mday) { return is_valid_year (year) && is_valid_mon (mon) && is_valid_mday (mday); } @@ -475,10 +481,10 @@ struct keyword; typedef int (*setter_t)(struct state *state, struct keyword *kw); struct keyword { - const char *name; /* keyword */ - enum field field; /* field to set, or FIELD_NONE if N/A */ - int value; /* value to set, or 0 if N/A */ - setter_t set; /* function to use for setting, if non-NULL */ + const char *name; /* keyword */ + enum field field; /* field to set, or FIELD_NONE if N/A */ + int value; /* value to set, or 0 if N/A */ + setter_t set; /* function to use for setting, if non-NULL */ }; /* @@ -515,7 +521,7 @@ kw_set_month (struct state *state, struct keyword *kw) consume_postponed_number (state, &v, NULL, NULL); - if (!is_valid_mday (v)) + if (! is_valid_mday (v)) return -PARSE_TIME_ERR_INVALIDDATE; r = set_field (state, TM_ABS_MDAY, v); @@ -538,7 +544,7 @@ kw_set_ampm (struct state *state, struct keyword *kw) consume_postponed_number (state, &v, NULL, NULL); - if (!is_valid_12hour (v)) + if (! is_valid_12hour (v)) return -PARSE_TIME_ERR_INVALIDTIME; r = set_abs_time (state, v, 0, 0); @@ -577,7 +583,7 @@ kw_set_ordinal (struct state *state, struct keyword *kw) int n, v; /* Require a postponed number. */ - if (!consume_postponed_number (state, &v, &n, NULL)) + if (! consume_postponed_number (state, &v, &n, NULL)) return -PARSE_TIME_ERR_DATEFORMAT; /* Ordinals are mday. */ @@ -591,7 +597,7 @@ kw_set_ordinal (struct state *state, struct keyword *kw) return -PARSE_TIME_ERR_INVALIDDATE; else if (strcasecmp (kw->name, "rd") == 0 && v != 3 && v != 23) return -PARSE_TIME_ERR_INVALIDDATE; - else if (strcasecmp (kw->name, "th") == 0 && !is_valid_mday (v)) + else if (strcasecmp (kw->name, "th") == 0 && ! is_valid_mday (v)) return -PARSE_TIME_ERR_INVALIDDATE; return set_field (state, TM_ABS_MDAY, v); @@ -622,95 +628,95 @@ kw_ignore (unused (struct state *state), unused (struct keyword *kw)) */ static struct keyword keywords[] = { /* Weekdays. */ - { N_("sun|day"), TM_WDAY, 0, NULL }, - { N_("mon|day"), TM_WDAY, 1, NULL }, - { N_("tue|sday"), TM_WDAY, 2, NULL }, - { N_("wed|nesday"), TM_WDAY, 3, NULL }, - { N_("thu|rsday"), TM_WDAY, 4, NULL }, - { N_("fri|day"), TM_WDAY, 5, NULL }, - { N_("sat|urday"), TM_WDAY, 6, NULL }, + { N_ ("sun|day"), TM_WDAY, 0, NULL }, + { N_ ("mon|day"), TM_WDAY, 1, NULL }, + { N_ ("tue|sday"), TM_WDAY, 2, NULL }, + { N_ ("wed|nesday"), TM_WDAY, 3, NULL }, + { N_ ("thu|rsday"), TM_WDAY, 4, NULL }, + { N_ ("fri|day"), TM_WDAY, 5, NULL }, + { N_ ("sat|urday"), TM_WDAY, 6, NULL }, /* Months. */ - { N_("jan|uary"), TM_ABS_MON, 1, kw_set_month }, - { N_("feb|ruary"), TM_ABS_MON, 2, kw_set_month }, - { N_("mar|ch"), TM_ABS_MON, 3, kw_set_month }, - { N_("apr|il"), TM_ABS_MON, 4, kw_set_month }, - { N_("may"), TM_ABS_MON, 5, kw_set_month }, - { N_("jun|e"), TM_ABS_MON, 6, kw_set_month }, - { N_("jul|y"), TM_ABS_MON, 7, kw_set_month }, - { N_("aug|ust"), TM_ABS_MON, 8, kw_set_month }, - { N_("sep|tember"), TM_ABS_MON, 9, kw_set_month }, - { N_("oct|ober"), TM_ABS_MON, 10, kw_set_month }, - { N_("nov|ember"), TM_ABS_MON, 11, kw_set_month }, - { N_("dec|ember"), TM_ABS_MON, 12, kw_set_month }, + { N_ ("jan|uary"), TM_ABS_MON, 1, kw_set_month }, + { N_ ("feb|ruary"), TM_ABS_MON, 2, kw_set_month }, + { N_ ("mar|ch"), TM_ABS_MON, 3, kw_set_month }, + { N_ ("apr|il"), TM_ABS_MON, 4, kw_set_month }, + { N_ ("may"), TM_ABS_MON, 5, kw_set_month }, + { N_ ("jun|e"), TM_ABS_MON, 6, kw_set_month }, + { N_ ("jul|y"), TM_ABS_MON, 7, kw_set_month }, + { N_ ("aug|ust"), TM_ABS_MON, 8, kw_set_month }, + { N_ ("sep|tember"), TM_ABS_MON, 9, kw_set_month }, + { N_ ("oct|ober"), TM_ABS_MON, 10, kw_set_month }, + { N_ ("nov|ember"), TM_ABS_MON, 11, kw_set_month }, + { N_ ("dec|ember"), TM_ABS_MON, 12, kw_set_month }, /* Durations. */ - { N_("y|ears"), TM_REL_YEAR, 1, kw_set_rel }, - { N_("mo|nths"), TM_REL_MON, 1, kw_set_rel }, - { N_("*M"), TM_REL_MON, 1, kw_set_rel }, - { N_("w|eeks"), TM_REL_WEEK, 1, kw_set_rel }, - { N_("d|ays"), TM_REL_DAY, 1, kw_set_rel }, - { N_("h|ours"), TM_REL_HOUR, 1, kw_set_rel }, - { N_("hr|s"), TM_REL_HOUR, 1, kw_set_rel }, - { N_("mi|nutes"), TM_REL_MIN, 1, kw_set_rel }, - { N_("mins"), TM_REL_MIN, 1, kw_set_rel }, - { N_("*m"), TM_REL_MIN, 1, kw_set_rel }, - { N_("s|econds"), TM_REL_SEC, 1, kw_set_rel }, - { N_("secs"), TM_REL_SEC, 1, kw_set_rel }, + { N_ ("y|ears"), TM_REL_YEAR, 1, kw_set_rel }, + { N_ ("mo|nths"), TM_REL_MON, 1, kw_set_rel }, + { N_ ("*M"), TM_REL_MON, 1, kw_set_rel }, + { N_ ("w|eeks"), TM_REL_WEEK, 1, kw_set_rel }, + { N_ ("d|ays"), TM_REL_DAY, 1, kw_set_rel }, + { N_ ("h|ours"), TM_REL_HOUR, 1, kw_set_rel }, + { N_ ("hr|s"), TM_REL_HOUR, 1, kw_set_rel }, + { N_ ("mi|nutes"), TM_REL_MIN, 1, kw_set_rel }, + { N_ ("mins"), TM_REL_MIN, 1, kw_set_rel }, + { N_ ("*m"), TM_REL_MIN, 1, kw_set_rel }, + { N_ ("s|econds"), TM_REL_SEC, 1, kw_set_rel }, + { N_ ("secs"), TM_REL_SEC, 1, kw_set_rel }, /* Numbers. */ - { N_("one"), TM_NONE, 1, kw_set_number }, - { N_("two"), TM_NONE, 2, kw_set_number }, - { N_("three"), TM_NONE, 3, kw_set_number }, - { N_("four"), TM_NONE, 4, kw_set_number }, - { N_("five"), TM_NONE, 5, kw_set_number }, - { N_("six"), TM_NONE, 6, kw_set_number }, - { N_("seven"), TM_NONE, 7, kw_set_number }, - { N_("eight"), TM_NONE, 8, kw_set_number }, - { N_("nine"), TM_NONE, 9, kw_set_number }, - { N_("ten"), TM_NONE, 10, kw_set_number }, - { N_("dozen"), TM_NONE, 12, kw_set_number }, - { N_("hundred"), TM_NONE, 100, kw_set_number }, + { N_ ("one"), TM_NONE, 1, kw_set_number }, + { N_ ("two"), TM_NONE, 2, kw_set_number }, + { N_ ("three"), TM_NONE, 3, kw_set_number }, + { N_ ("four"), TM_NONE, 4, kw_set_number }, + { N_ ("five"), TM_NONE, 5, kw_set_number }, + { N_ ("six"), TM_NONE, 6, kw_set_number }, + { N_ ("seven"), TM_NONE, 7, kw_set_number }, + { N_ ("eight"), TM_NONE, 8, kw_set_number }, + { N_ ("nine"), TM_NONE, 9, kw_set_number }, + { N_ ("ten"), TM_NONE, 10, kw_set_number }, + { N_ ("dozen"), TM_NONE, 12, kw_set_number }, + { N_ ("hundred"), TM_NONE, 100, kw_set_number }, /* Special number forms. */ - { N_("this"), TM_NONE, 0, kw_set_number }, - { N_("last"), TM_NONE, 1, kw_set_number }, + { N_ ("this"), TM_NONE, 0, kw_set_number }, + { N_ ("last"), TM_NONE, 1, kw_set_number }, /* Other special keywords. */ - { N_("yesterday"), TM_REL_DAY, 1, kw_set_rel }, - { N_("today"), TM_NONE, 0, kw_set_today }, - { N_("now"), TM_NONE, 0, kw_set_now }, - { N_("noon"), TM_NONE, 12, kw_set_timeofday }, - { N_("midnight"), TM_NONE, 0, kw_set_timeofday }, - { N_("am"), TM_AMPM, 0, kw_set_ampm }, - { N_("a.m."), TM_AMPM, 0, kw_set_ampm }, - { N_("pm"), TM_AMPM, 1, kw_set_ampm }, - { N_("p.m."), TM_AMPM, 1, kw_set_ampm }, - { N_("st"), TM_NONE, 0, kw_set_ordinal }, - { N_("nd"), TM_NONE, 0, kw_set_ordinal }, - { N_("rd"), TM_NONE, 0, kw_set_ordinal }, - { N_("th"), TM_NONE, 0, kw_set_ordinal }, - { N_("ago"), TM_NONE, 0, kw_ignore }, + { N_ ("yesterday"), TM_REL_DAY, 1, kw_set_rel }, + { N_ ("today"), TM_NONE, 0, kw_set_today }, + { N_ ("now"), TM_NONE, 0, kw_set_now }, + { N_ ("noon"), TM_NONE, 12, kw_set_timeofday }, + { N_ ("midnight"), TM_NONE, 0, kw_set_timeofday }, + { N_ ("am"), TM_AMPM, 0, kw_set_ampm }, + { N_ ("a.m."), TM_AMPM, 0, kw_set_ampm }, + { N_ ("pm"), TM_AMPM, 1, kw_set_ampm }, + { N_ ("p.m."), TM_AMPM, 1, kw_set_ampm }, + { N_ ("st"), TM_NONE, 0, kw_set_ordinal }, + { N_ ("nd"), TM_NONE, 0, kw_set_ordinal }, + { N_ ("rd"), TM_NONE, 0, kw_set_ordinal }, + { N_ ("th"), TM_NONE, 0, kw_set_ordinal }, + { N_ ("ago"), TM_NONE, 0, kw_ignore }, /* Timezone codes: offset in minutes. XXX: Add more codes. */ - { N_("pst"), TM_TZ, -8*60, NULL }, - { N_("mst"), TM_TZ, -7*60, NULL }, - { N_("cst"), TM_TZ, -6*60, NULL }, - { N_("est"), TM_TZ, -5*60, NULL }, - { N_("ast"), TM_TZ, -4*60, NULL }, - { N_("nst"), TM_TZ, -(3*60+30), NULL }, + { N_ ("pst"), TM_TZ, -8 * 60, NULL }, + { N_ ("mst"), TM_TZ, -7 * 60, NULL }, + { N_ ("cst"), TM_TZ, -6 * 60, NULL }, + { N_ ("est"), TM_TZ, -5 * 60, NULL }, + { N_ ("ast"), TM_TZ, -4 * 60, NULL }, + { N_ ("nst"), TM_TZ, -(3 * 60 + 30), NULL }, - { N_("gmt"), TM_TZ, 0, NULL }, - { N_("utc"), TM_TZ, 0, NULL }, + { N_ ("gmt"), TM_TZ, 0, NULL }, + { N_ ("utc"), TM_TZ, 0, NULL }, - { N_("wet"), TM_TZ, 0, NULL }, - { N_("cet"), TM_TZ, 1*60, NULL }, - { N_("eet"), TM_TZ, 2*60, NULL }, - { N_("fet"), TM_TZ, 3*60, NULL }, + { N_ ("wet"), TM_TZ, 0, NULL }, + { N_ ("cet"), TM_TZ, 1 * 60, NULL }, + { N_ ("eet"), TM_TZ, 2 * 60, NULL }, + { N_ ("fet"), TM_TZ, 3 * 60, NULL }, - { N_("wat"), TM_TZ, 1*60, NULL }, - { N_("cat"), TM_TZ, 2*60, NULL }, - { N_("eat"), TM_TZ, 3*60, NULL }, + { N_ ("wat"), TM_TZ, 1 * 60, NULL }, + { N_ ("cat"), TM_TZ, 2 * 60, NULL }, + { N_ ("eat"), TM_TZ, 3 * 60, NULL }, }; /* @@ -745,7 +751,7 @@ match_keyword (const char *str, const char *keyword, bool match_case) keyword++; } - if (!*s || !isalpha ((unsigned char) *s) || !*keyword) + if (! *s || ! isalpha ((unsigned char) *s) || ! *keyword) break; if (match_case) { @@ -765,7 +771,7 @@ match_keyword (const char *str, const char *keyword, bool match_case) return 0; /* did not match enough of keyword */ - if (*keyword && !prefix_matched) + if (*keyword && ! prefix_matched) return 0; return s - str; @@ -784,7 +790,7 @@ parse_keyword (struct state *state, const char *s) int r; for (i = 0; i < ARRAY_SIZE (keywords); i++) { - const char *keyword = _(keywords[i].name); + const char *keyword = _ (keywords[i].name); bool mcase = false; /* Match case if keyword begins with '*'. */ @@ -800,7 +806,7 @@ parse_keyword (struct state *state, const char *s) } } - if (!kw) + if (! kw) return -PARSE_TIME_ERR_KEYWORD; if (kw->set) @@ -846,7 +852,7 @@ parse_postponed_number (struct state *state, unused (enum field next_field)) char d; /* Bail out if there's no postponed number. */ - if (!consume_postponed_number (state, &v, &n, &d)) + if (! consume_postponed_number (state, &v, &n, &d)) return 0; if (n == 1 || n == 2) { @@ -854,7 +860,7 @@ parse_postponed_number (struct state *state, unused (enum field next_field)) * handles "January 20". */ if (state->last_field == TM_ABS_MON) { /* D[D] */ - if (!is_valid_mday (v)) + if (! is_valid_mday (v)) return -PARSE_TIME_ERR_INVALIDDATE; return set_field (state, TM_ABS_MDAY, v); @@ -869,7 +875,7 @@ parse_postponed_number (struct state *state, unused (enum field next_field)) /* Notable exception: Value affects parsing. Time zones are * always at most 1400 and we don't understand years before * 1970. */ - if (!is_valid_year (v)) { + if (! is_valid_year (v)) { if (d == '+' || d == '-') { /* +/-HHMM */ return set_user_tz (state, d, v / 100, v % 100); @@ -884,7 +890,7 @@ parse_postponed_number (struct state *state, unused (enum field next_field)) int min = (v / 100) % 100; int sec = v % 100; - if (!is_valid_time (hour, min, sec)) + if (! is_valid_time (hour, min, sec)) return -PARSE_TIME_ERR_INVALIDTIME; return set_abs_time (state, hour, min, sec); @@ -894,7 +900,7 @@ parse_postponed_number (struct state *state, unused (enum field next_field)) int mon = (v / 100) % 100; int mday = v % 100; - if (!is_valid_date (year, mon, mday)) + if (! is_valid_date (year, mon, mday)) return -PARSE_TIME_ERR_INVALIDDATE; return set_abs_date (state, year, mon, mday); @@ -1039,13 +1045,13 @@ parse_date (struct state *state, char sep, break; } - if (year != UNSET && !is_valid_year (year)) + if (year != UNSET && ! is_valid_year (year)) return -PARSE_TIME_ERR_INVALIDDATE; - if (mon != UNSET && !is_valid_mon (mon)) + if (mon != UNSET && ! is_valid_mon (mon)) return -PARSE_TIME_ERR_INVALIDDATE; - if (mday != UNSET && !is_valid_mday (mday)) + if (mday != UNSET && ! is_valid_mday (mday)) return -PARSE_TIME_ERR_INVALIDDATE; return set_abs_date (state, year, mon, mday); @@ -1081,7 +1087,7 @@ parse_time (struct state *state, char sep, return set_user_tz (state, state->delim, v1, v2); } - if (!is_valid_time (v1, v2, n3 ? v3 : 0)) + if (! is_valid_time (v1, v2, n3 ? v3 : 0)) return -PARSE_TIME_ERR_INVALIDTIME; return set_abs_time (state, v1, v2, n3 ? (int) v3 : UNSET); @@ -1112,7 +1118,7 @@ parse_number (struct state *state, const char *s) v1 = strtoul_len (p, &p, &n1); - if (!is_sep (*p) || !isdigit ((unsigned char) *(p + 1))) { + if (! is_sep (*p) || ! isdigit ((unsigned char) *(p + 1))) { /* A single number. */ r = parse_single_number (state, v1, n1); if (r) @@ -1155,7 +1161,7 @@ parse_delim (struct state *state, const char *s) * Skip non-alpha and non-digit, and store the last for further * processing. */ - while (*p && !isalnum ((unsigned char) *p)) { + while (*p && ! isalnum ((unsigned char) *p)) { set_delim (state, *p); p++; } @@ -1258,7 +1264,7 @@ normalize_tm (struct tm *tm) if (t == (time_t) -1) return -PARSE_TIME_ERR_LIB; - if (!localtime_r (&t, tm)) + if (! localtime_r (&t, tm)) return -PARSE_TIME_ERR_LIB; return 0; @@ -1269,14 +1275,14 @@ static int tm_get_field (const struct tm *tm, enum field field) { switch (field) { - case TM_ABS_SEC: return tm->tm_sec; - case TM_ABS_MIN: return tm->tm_min; - case TM_ABS_HOUR: return tm->tm_hour; - case TM_ABS_MDAY: return tm->tm_mday; - case TM_ABS_MON: return tm->tm_mon + 1; /* 0- to 1-based */ - case TM_ABS_YEAR: return 1900 + tm->tm_year; - case TM_WDAY: return tm->tm_wday; - case TM_ABS_ISDST: return tm->tm_isdst; + case TM_ABS_SEC: return tm->tm_sec; + case TM_ABS_MIN: return tm->tm_min; + case TM_ABS_HOUR: return tm->tm_hour; + case TM_ABS_MDAY: return tm->tm_mday; + case TM_ABS_MON: return tm->tm_mon + 1; /* 0- to 1-based */ + case TM_ABS_YEAR: return 1900 + tm->tm_year; + case TM_WDAY: return tm->tm_wday; + case TM_ABS_ISDST: return tm->tm_isdst; default: assert (false); break; @@ -1291,14 +1297,14 @@ fixup_ampm (struct state *state) { int hour, hdiff = 0; - if (!is_field_set (state, TM_AMPM)) + if (! is_field_set (state, TM_AMPM)) return 0; - if (!is_field_set (state, TM_ABS_HOUR)) + if (! is_field_set (state, TM_ABS_HOUR)) return -PARSE_TIME_ERR_TIMEFORMAT; hour = get_field (state, TM_ABS_HOUR); - if (!is_valid_12hour (hour)) + if (! is_valid_12hour (hour)) return -PARSE_TIME_ERR_INVALIDTIME; if (get_field (state, TM_AMPM)) { @@ -1348,7 +1354,7 @@ create_output (struct state *state, time_t *t_out, const time_t *ref, * date? */ if (is_field_set (state, TM_WDAY) && - !is_field_set (state, TM_ABS_MDAY)) { + ! is_field_set (state, TM_ABS_MDAY)) { int wday = get_field (state, TM_WDAY); int today = tm_get_field (&now, TM_WDAY); int rel_days; @@ -1405,7 +1411,7 @@ create_output (struct state *state, time_t *t_out, const time_t *ref, } } - if (!is_field_set (state, f)) + if (! is_field_set (state, f)) set_field (state, f, tm_get_field (&now, f)); } @@ -1489,7 +1495,7 @@ parse_time_string (const char *s, time_t *t, const time_t *ref, int round) struct state state = { .last_field = TM_NONE }; int r; - if (!s || !t) + if (! s || ! t) return EXTERNAL_ERR (-PARSE_TIME_ERR); r = parse_input (&state, s); diff --git a/parse-time-string/parse-time-string.h b/parse-time-string/parse-time-string.h index c394ecde..c90d6947 100644 --- a/parse-time-string/parse-time-string.h +++ b/parse-time-string/parse-time-string.h @@ -30,23 +30,23 @@ extern "C" { /* return values for parse_time_string() */ enum { PARSE_TIME_OK = 0, - PARSE_TIME_ERR, /* unspecified error */ - PARSE_TIME_ERR_LIB, /* library call failed */ - PARSE_TIME_ERR_ALREADYSET, /* attempt to set unit twice */ - PARSE_TIME_ERR_FORMAT, /* generic date/time format error */ - PARSE_TIME_ERR_DATEFORMAT, /* date format error */ - PARSE_TIME_ERR_TIMEFORMAT, /* time format error */ - PARSE_TIME_ERR_INVALIDDATE, /* date value error */ - PARSE_TIME_ERR_INVALIDTIME, /* time value error */ - PARSE_TIME_ERR_KEYWORD, /* unknown keyword */ + PARSE_TIME_ERR, /* unspecified error */ + PARSE_TIME_ERR_LIB, /* library call failed */ + PARSE_TIME_ERR_ALREADYSET, /* attempt to set unit twice */ + PARSE_TIME_ERR_FORMAT, /* generic date/time format error */ + PARSE_TIME_ERR_DATEFORMAT, /* date format error */ + PARSE_TIME_ERR_TIMEFORMAT, /* time format error */ + PARSE_TIME_ERR_INVALIDDATE, /* date value error */ + PARSE_TIME_ERR_INVALIDTIME, /* time value error */ + PARSE_TIME_ERR_KEYWORD, /* unknown keyword */ }; /* round values for parse_time_string() */ enum { - PARSE_TIME_ROUND_DOWN = -1, - PARSE_TIME_NO_ROUND = 0, - PARSE_TIME_ROUND_UP = 1, - PARSE_TIME_ROUND_UP_INCLUSIVE = 2, + PARSE_TIME_ROUND_DOWN = -1, + PARSE_TIME_NO_ROUND = 0, + PARSE_TIME_ROUND_UP = 1, + PARSE_TIME_ROUND_UP_INCLUSIVE = 2, }; /** From 2b62ca2e3b786beca8d89fa737bda0b49faa638d Mon Sep 17 00:00:00 2001 From: uncrustify Date: Thu, 13 Jun 2019 07:55:35 -0300 Subject: [PATCH 018/427] lib: run uncrustify This is the result of running $ uncrustify --replace --config ../devel/uncrustify.cfg *.c *.h *.cc in the lib directory --- lib/add-message.cc | 22 ++-- lib/config.cc | 8 +- lib/database-private.h | 38 +++---- lib/database.cc | 237 ++++++++++++++++++++-------------------- lib/directory.cc | 20 ++-- lib/index.cc | 117 ++++++++++---------- lib/indexopts.c | 23 ++-- lib/message-file.c | 15 +-- lib/message-id.c | 4 +- lib/message-property.cc | 3 +- lib/message.cc | 153 +++++++++++++------------- lib/messages.c | 4 +- lib/notmuch-private.h | 62 +++++------ lib/notmuch.h | 46 ++++---- lib/parse-time-vrp.cc | 16 +-- lib/parse-time-vrp.h | 6 +- lib/query-fp.h | 10 +- lib/query.cc | 66 ++++++----- lib/regexp-fields.cc | 33 +++--- lib/regexp-fields.h | 14 ++- lib/sha1.c | 1 + lib/string-list.c | 6 +- lib/thread-fp.h | 10 +- lib/thread.cc | 112 +++++++++---------- 24 files changed, 513 insertions(+), 513 deletions(-) diff --git a/lib/add-message.cc b/lib/add-message.cc index da37032c..8c92689b 100644 --- a/lib/add-message.cc +++ b/lib/add-message.cc @@ -34,7 +34,7 @@ parse_references (void *ctx, * reference to the database. We should avoid making a message * its own parent, thus the above check. */ - return talloc_strdup(ctx, last_ref); + return talloc_strdup (ctx, last_ref); } static const char * @@ -165,12 +165,12 @@ _resolve_message_id_to_thread_id_old (notmuch_database_t *notmuch, metadata_key = _get_metadata_thread_id_key (ctx, message_id); thread_id_string = notmuch->xapian_db->get_metadata (metadata_key); - if (thread_id_string.empty()) { + if (thread_id_string.empty ()) { *thread_id_ret = talloc_strdup (ctx, _notmuch_database_generate_thread_id (notmuch)); db->set_metadata (metadata_key, *thread_id_ret); } else { - *thread_id_ret = talloc_strdup (ctx, thread_id_string.c_str()); + *thread_id_ret = talloc_strdup (ctx, thread_id_string.c_str ()); } talloc_free (metadata_key); @@ -190,7 +190,7 @@ _merge_threads (notmuch_database_t *notmuch, _notmuch_database_find_doc_ids (notmuch, "thread", loser_thread_id, &loser, &loser_end); - for ( ; loser != loser_end; loser++) { + for (; loser != loser_end; loser++) { message = _notmuch_message_create (notmuch, notmuch, *loser, &private_status); if (message == NULL) { @@ -264,7 +264,7 @@ _notmuch_database_link_message_to_parents (notmuch_database_t *notmuch, last_ref_message_id); } else if (in_reply_to_message_id) { _notmuch_message_add_term (message, "replyto", - in_reply_to_message_id); + in_reply_to_message_id); } keys = g_hash_table_get_keys (parents); @@ -317,7 +317,7 @@ _notmuch_database_link_message_to_children (notmuch_database_t *notmuch, _notmuch_database_find_doc_ids (notmuch, "reference", message_id, &child, &children_end); - for ( ; child != children_end; child++) { + for (; child != children_end; child++) { child_message = _notmuch_message_create (message, notmuch, *child, &private_status); @@ -461,7 +461,7 @@ _notmuch_database_link_message (notmuch_database_t *notmuch, _notmuch_message_add_term (message, "thread", thread_id); } - DONE: + DONE: talloc_free (local); return status; @@ -544,14 +544,14 @@ notmuch_database_index_file (notmuch_database_t *notmuch, } ret = _notmuch_database_link_message (notmuch, message, - message_file, is_ghost); + message_file, is_ghost); if (ret) goto DONE; if (is_new || is_ghost) _notmuch_message_set_header_values (message, date, from, subject); - if (!indexopts) { + if (! indexopts) { def_indexopts = notmuch_database_get_default_indexopts (notmuch); indexopts = def_indexopts; } @@ -560,13 +560,13 @@ notmuch_database_index_file (notmuch_database_t *notmuch, if (ret) goto DONE; - if (! is_new && !is_ghost) + if (! is_new && ! is_ghost) ret = NOTMUCH_STATUS_DUPLICATE_MESSAGE_ID; _notmuch_message_sync (message); } catch (const Xapian::Error &error) { _notmuch_database_log (notmuch, "A Xapian exception occurred adding message: %s.\n", - error.get_msg().c_str()); + error.get_msg ().c_str ()); notmuch->exception_reported = true; ret = NOTMUCH_STATUS_XAPIAN_EXCEPTION; goto DONE; diff --git a/lib/config.cc b/lib/config.cc index da71c16e..8ee4da01 100644 --- a/lib/config.cc +++ b/lib/config.cc @@ -58,7 +58,7 @@ notmuch_database_set_config (notmuch_database_t *notmuch, status = NOTMUCH_STATUS_XAPIAN_EXCEPTION; notmuch->exception_reported = true; _notmuch_database_log (notmuch, "Error: A Xapian exception occurred setting metadata: %s\n", - error.get_msg().c_str()); + error.get_msg ().c_str ()); } return NOTMUCH_STATUS_SUCCESS; } @@ -76,7 +76,7 @@ _metadata_value (notmuch_database_t *notmuch, status = NOTMUCH_STATUS_XAPIAN_EXCEPTION; notmuch->exception_reported = true; _notmuch_database_log (notmuch, "Error: A Xapian exception occurred getting metadata: %s\n", - error.get_msg().c_str()); + error.get_msg ().c_str ()); } return status; } @@ -123,11 +123,11 @@ notmuch_database_get_config_list (notmuch_database_t *notmuch, try { new(&(list->iterator)) Xapian::TermIterator (notmuch->xapian_db->metadata_keys_begin - (CONFIG_PREFIX + (prefix ? prefix : ""))); + (CONFIG_PREFIX + (prefix ? prefix : ""))); } catch (const Xapian::Error &error) { _notmuch_database_log (notmuch, "A Xapian exception occurred getting metadata iterator: %s.\n", - error.get_msg().c_str()); + error.get_msg ().c_str ()); notmuch->exception_reported = true; status = NOTMUCH_STATUS_XAPIAN_EXCEPTION; } diff --git a/lib/database-private.h b/lib/database-private.h index 9d1dabf1..87ae1bdf 100644 --- a/lib/database-private.h +++ b/lib/database-private.h @@ -62,7 +62,7 @@ enum _notmuch_features { * unset, file names are stored in document data. * * Introduced: version 1. */ - NOTMUCH_FEATURE_FILE_TERMS = 1 << 0, + NOTMUCH_FEATURE_FILE_TERMS = 1 << 0, /* If set, directory timestamps are stored in documents with * XDIRECTORY terms and relative paths. If unset, directory @@ -70,7 +70,7 @@ enum _notmuch_features { * absolute paths. * * Introduced: version 1. */ - NOTMUCH_FEATURE_DIRECTORY_DOCS = 1 << 1, + NOTMUCH_FEATURE_DIRECTORY_DOCS = 1 << 1, /* If set, the from, subject, and message-id headers are stored in * message document values. If unset, message documents *may* @@ -79,21 +79,21 @@ enum _notmuch_features { * * Introduced: optional in version 1, required as of version 3. */ - NOTMUCH_FEATURE_FROM_SUBJECT_ID_VALUES = 1 << 2, + NOTMUCH_FEATURE_FROM_SUBJECT_ID_VALUES = 1 << 2, /* If set, folder terms are boolean and path terms exist. If * unset, folder terms are probabilistic and stemmed and path * terms do not exist. * * Introduced: version 2. */ - NOTMUCH_FEATURE_BOOL_FOLDER = 1 << 3, + NOTMUCH_FEATURE_BOOL_FOLDER = 1 << 3, /* If set, missing messages are stored in ghost mail documents. * If unset, thread IDs of ghost messages are stored as database * metadata instead of in ghost documents. * * Introduced: version 3. */ - NOTMUCH_FEATURE_GHOSTS = 1 << 4, + NOTMUCH_FEATURE_GHOSTS = 1 << 4, /* If set, then the database was created after the introduction of @@ -101,52 +101,52 @@ enum _notmuch_features { * mixture of messages with indexed and non-indexed mime types. * * Introduced: version 3. */ - NOTMUCH_FEATURE_INDEXED_MIMETYPES = 1 << 5, + NOTMUCH_FEATURE_INDEXED_MIMETYPES = 1 << 5, /* If set, messages store the revision number of the last * modification in NOTMUCH_VALUE_LAST_MOD. * * Introduced: version 3. */ - NOTMUCH_FEATURE_LAST_MOD = 1 << 6, + NOTMUCH_FEATURE_LAST_MOD = 1 << 6, /* If set, unprefixed terms are stored only for the message body, * not for headers. * * Introduced: version 3. */ - NOTMUCH_FEATURE_UNPREFIX_BODY_ONLY = 1 << 7, + NOTMUCH_FEATURE_UNPREFIX_BODY_ONLY = 1 << 7, }; /* In C++, a named enum is its own type, so define bitwise operators * on _notmuch_features. */ inline _notmuch_features -operator|(_notmuch_features a, _notmuch_features b) +operator| (_notmuch_features a, _notmuch_features b) { return static_cast<_notmuch_features>( static_cast(a) | static_cast(b)); } inline _notmuch_features -operator&(_notmuch_features a, _notmuch_features b) +operator& (_notmuch_features a, _notmuch_features b) { return static_cast<_notmuch_features>( static_cast(a) & static_cast(b)); } inline _notmuch_features -operator~(_notmuch_features a) +operator~ (_notmuch_features a) { return static_cast<_notmuch_features>(~static_cast(a)); } inline _notmuch_features& -operator|=(_notmuch_features &a, _notmuch_features b) +operator|= (_notmuch_features &a, _notmuch_features b) { a = a | b; return a; } inline _notmuch_features& -operator&=(_notmuch_features &a, _notmuch_features b) +operator&= (_notmuch_features &a, _notmuch_features b) { a = a & b; return a; @@ -155,23 +155,23 @@ operator&=(_notmuch_features &a, _notmuch_features b) /* * Configuration options for xapian database fields */ typedef enum notmuch_field_flags { - NOTMUCH_FIELD_NO_FLAGS = 0, - NOTMUCH_FIELD_EXTERNAL = 1 << 0, + NOTMUCH_FIELD_NO_FLAGS = 0, + NOTMUCH_FIELD_EXTERNAL = 1 << 0, NOTMUCH_FIELD_PROBABILISTIC = 1 << 1, - NOTMUCH_FIELD_PROCESSOR = 1 << 2, + NOTMUCH_FIELD_PROCESSOR = 1 << 2, } notmuch_field_flag_t; /* * define bitwise operators to hide casts */ inline notmuch_field_flag_t -operator|(notmuch_field_flag_t a, notmuch_field_flag_t b) +operator| (notmuch_field_flag_t a, notmuch_field_flag_t b) { return static_cast( static_cast(a) | static_cast(b)); } inline notmuch_field_flag_t -operator&(notmuch_field_flag_t a, notmuch_field_flag_t b) +operator& (notmuch_field_flag_t a, notmuch_field_flag_t b) { return static_cast( static_cast(a) & static_cast(b)); @@ -230,7 +230,7 @@ struct _notmuch_database { /* Prior to database version 3, features were implied by the database * version number, so hard-code them for earlier versions. */ -#define NOTMUCH_FEATURES_V0 ((enum _notmuch_features)0) +#define NOTMUCH_FEATURES_V0 ((enum _notmuch_features) 0) #define NOTMUCH_FEATURES_V1 (NOTMUCH_FEATURES_V0 | NOTMUCH_FEATURE_FILE_TERMS | \ NOTMUCH_FEATURE_DIRECTORY_DOCS) #define NOTMUCH_FEATURES_V2 (NOTMUCH_FEATURES_V1 | NOTMUCH_FEATURE_BOOL_FOLDER) diff --git a/lib/database.cc b/lib/database.cc index 4c4c9edc..24b7ec43 100644 --- a/lib/database.cc +++ b/lib/database.cc @@ -32,10 +32,10 @@ #include #include -#include /* g_free, GPtrArray, GHashTable */ -#include /* g_type_init */ +#include /* g_free, GPtrArray, GHashTable */ +#include /* g_type_init */ -#include /* g_mime_init */ +#include /* g_mime_init */ using namespace std; @@ -49,7 +49,7 @@ typedef struct { #define NOTMUCH_DATABASE_VERSION 3 -#define STRINGIFY(s) _SUB_STRINGIFY(s) +#define STRINGIFY(s) _SUB_STRINGIFY (s) #define _SUB_STRINGIFY(s) #s #if HAVE_XAPIAN_DB_RETRY_LOCK @@ -263,59 +263,59 @@ typedef struct { static const prefix_t prefix_table[] = { /* name term prefix flags */ - { "type", "T", NOTMUCH_FIELD_NO_FLAGS }, - { "reference", "XREFERENCE", NOTMUCH_FIELD_NO_FLAGS }, - { "replyto", "XREPLYTO", NOTMUCH_FIELD_NO_FLAGS }, - { "directory", "XDIRECTORY", NOTMUCH_FIELD_NO_FLAGS }, - { "file-direntry", "XFDIRENTRY", NOTMUCH_FIELD_NO_FLAGS }, - { "directory-direntry", "XDDIRENTRY", NOTMUCH_FIELD_NO_FLAGS }, - { "body", "", NOTMUCH_FIELD_EXTERNAL | - NOTMUCH_FIELD_PROBABILISTIC}, - { "thread", "G", NOTMUCH_FIELD_EXTERNAL | - NOTMUCH_FIELD_PROCESSOR }, - { "tag", "K", NOTMUCH_FIELD_EXTERNAL | - NOTMUCH_FIELD_PROCESSOR }, - { "is", "K", NOTMUCH_FIELD_EXTERNAL | - NOTMUCH_FIELD_PROCESSOR }, - { "id", "Q", NOTMUCH_FIELD_EXTERNAL }, - { "mid", "Q", NOTMUCH_FIELD_EXTERNAL | - NOTMUCH_FIELD_PROCESSOR }, - { "path", "P", NOTMUCH_FIELD_EXTERNAL| - NOTMUCH_FIELD_PROCESSOR }, - { "property", "XPROPERTY", NOTMUCH_FIELD_EXTERNAL }, + { "type", "T", NOTMUCH_FIELD_NO_FLAGS }, + { "reference", "XREFERENCE", NOTMUCH_FIELD_NO_FLAGS }, + { "replyto", "XREPLYTO", NOTMUCH_FIELD_NO_FLAGS }, + { "directory", "XDIRECTORY", NOTMUCH_FIELD_NO_FLAGS }, + { "file-direntry", "XFDIRENTRY", NOTMUCH_FIELD_NO_FLAGS }, + { "directory-direntry", "XDDIRENTRY", NOTMUCH_FIELD_NO_FLAGS }, + { "body", "", NOTMUCH_FIELD_EXTERNAL | + NOTMUCH_FIELD_PROBABILISTIC }, + { "thread", "G", NOTMUCH_FIELD_EXTERNAL | + NOTMUCH_FIELD_PROCESSOR }, + { "tag", "K", NOTMUCH_FIELD_EXTERNAL | + NOTMUCH_FIELD_PROCESSOR }, + { "is", "K", NOTMUCH_FIELD_EXTERNAL | + NOTMUCH_FIELD_PROCESSOR }, + { "id", "Q", NOTMUCH_FIELD_EXTERNAL }, + { "mid", "Q", NOTMUCH_FIELD_EXTERNAL | + NOTMUCH_FIELD_PROCESSOR }, + { "path", "P", NOTMUCH_FIELD_EXTERNAL | + NOTMUCH_FIELD_PROCESSOR }, + { "property", "XPROPERTY", NOTMUCH_FIELD_EXTERNAL }, /* * Unconditionally add ':' to reduce potential ambiguity with * overlapping prefixes and/or terms that start with capital * letters. See Xapian document termprefixes.html for related * discussion. */ - { "folder", "XFOLDER:", NOTMUCH_FIELD_EXTERNAL | - NOTMUCH_FIELD_PROCESSOR }, + { "folder", "XFOLDER:", NOTMUCH_FIELD_EXTERNAL | + NOTMUCH_FIELD_PROCESSOR }, #if HAVE_XAPIAN_FIELD_PROCESSOR - { "date", NULL, NOTMUCH_FIELD_EXTERNAL | - NOTMUCH_FIELD_PROCESSOR }, - { "query", NULL, NOTMUCH_FIELD_EXTERNAL | - NOTMUCH_FIELD_PROCESSOR }, + { "date", NULL, NOTMUCH_FIELD_EXTERNAL | + NOTMUCH_FIELD_PROCESSOR }, + { "query", NULL, NOTMUCH_FIELD_EXTERNAL | + NOTMUCH_FIELD_PROCESSOR }, #endif - { "from", "XFROM", NOTMUCH_FIELD_EXTERNAL | - NOTMUCH_FIELD_PROBABILISTIC | - NOTMUCH_FIELD_PROCESSOR }, - { "to", "XTO", NOTMUCH_FIELD_EXTERNAL | - NOTMUCH_FIELD_PROBABILISTIC }, - { "attachment", "XATTACHMENT", NOTMUCH_FIELD_EXTERNAL | - NOTMUCH_FIELD_PROBABILISTIC }, - { "mimetype", "XMIMETYPE", NOTMUCH_FIELD_EXTERNAL | - NOTMUCH_FIELD_PROBABILISTIC }, - { "subject", "XSUBJECT", NOTMUCH_FIELD_EXTERNAL | - NOTMUCH_FIELD_PROBABILISTIC | - NOTMUCH_FIELD_PROCESSOR}, + { "from", "XFROM", NOTMUCH_FIELD_EXTERNAL | + NOTMUCH_FIELD_PROBABILISTIC | + NOTMUCH_FIELD_PROCESSOR }, + { "to", "XTO", NOTMUCH_FIELD_EXTERNAL | + NOTMUCH_FIELD_PROBABILISTIC }, + { "attachment", "XATTACHMENT", NOTMUCH_FIELD_EXTERNAL | + NOTMUCH_FIELD_PROBABILISTIC }, + { "mimetype", "XMIMETYPE", NOTMUCH_FIELD_EXTERNAL | + NOTMUCH_FIELD_PROBABILISTIC }, + { "subject", "XSUBJECT", NOTMUCH_FIELD_EXTERNAL | + NOTMUCH_FIELD_PROBABILISTIC | + NOTMUCH_FIELD_PROCESSOR }, }; static void _setup_query_field_default (const prefix_t *prefix, notmuch_database_t *notmuch) { if (prefix->prefix) - notmuch->query_parser->add_prefix ("",prefix->prefix); + notmuch->query_parser->add_prefix ("", prefix->prefix); if (prefix->flags & NOTMUCH_FIELD_PROBABILISTIC) notmuch->query_parser->add_prefix (prefix->name, prefix->prefix); else @@ -329,9 +329,9 @@ _notmuch_database_user_headers (notmuch_database_t *notmuch) } const char * -_user_prefix (void *ctx, const char* name) +_user_prefix (void *ctx, const char *name) { - return talloc_asprintf(ctx, "XU%s:", name); + return talloc_asprintf (ctx, "XU%s:", name); } static notmuch_status_t @@ -357,7 +357,7 @@ _setup_user_query_fields (notmuch_database_t *notmuch) prefix_t query_field; const char *key = notmuch_config_list_key (list) - + sizeof (CONFIG_HEADER_PREFIX) - 1; + + sizeof (CONFIG_HEADER_PREFIX) - 1; _notmuch_string_map_append (notmuch->user_prefix, key, @@ -370,7 +370,7 @@ _setup_user_query_fields (notmuch_database_t *notmuch) query_field.name = talloc_strdup (notmuch, key); query_field.prefix = _user_prefix (notmuch, key); query_field.flags = NOTMUCH_FIELD_PROBABILISTIC - | NOTMUCH_FIELD_EXTERNAL; + | NOTMUCH_FIELD_EXTERNAL; _setup_query_field_default (&query_field, notmuch); } @@ -388,10 +388,10 @@ _setup_query_field (const prefix_t *prefix, notmuch_database_t *notmuch) Xapian::FieldProcessor *fp; if (STRNCMP_LITERAL (prefix->name, "date") == 0) - fp = (new DateFieldProcessor())->release (); - else if (STRNCMP_LITERAL(prefix->name, "query") == 0) + fp = (new DateFieldProcessor ())->release (); + else if (STRNCMP_LITERAL (prefix->name, "query") == 0) fp = (new QueryFieldProcessor (*notmuch->query_parser, notmuch))->release (); - else if (STRNCMP_LITERAL(prefix->name, "thread") == 0) + else if (STRNCMP_LITERAL (prefix->name, "thread") == 0) fp = (new ThreadFieldProcessor (*notmuch->query_parser, notmuch))->release (); else fp = (new RegexpFieldProcessor (prefix->name, prefix->flags, @@ -399,7 +399,7 @@ _setup_query_field (const prefix_t *prefix, notmuch_database_t *notmuch) /* we treat all field-processor fields as boolean in order to get the raw input */ if (prefix->prefix) - notmuch->query_parser->add_prefix ("",prefix->prefix); + notmuch->query_parser->add_prefix ("", prefix->prefix); notmuch->query_parser->add_boolean_prefix (prefix->name, fp); } else { _setup_query_field_default (prefix, notmuch); @@ -469,18 +469,18 @@ static const struct { { NOTMUCH_FEATURE_BOOL_FOLDER, "exact folder:/path: search", "rw" }, { NOTMUCH_FEATURE_GHOSTS, - "mail documents for missing messages", "w"}, + "mail documents for missing messages", "w" }, /* Knowledge of the index mime-types are not required for reading * a database because a reader will just be unable to query * them. */ { NOTMUCH_FEATURE_INDEXED_MIMETYPES, - "indexed MIME types", "w"}, + "indexed MIME types", "w" }, { NOTMUCH_FEATURE_LAST_MOD, - "modification tracking", "w"}, + "modification tracking", "w" }, /* Existing databases will work fine for all queries not involving * 'body:' */ { NOTMUCH_FEATURE_UNPREFIX_BODY_ONLY, - "index body and headers separately", "w"}, + "index body and headers separately", "w" }, }; const char * @@ -529,8 +529,8 @@ notmuch_status_to_string (notmuch_status_t status) void _notmuch_database_log (notmuch_database_t *notmuch, - const char *format, - ...) + const char *format, + ...) { va_list va_args; @@ -545,8 +545,8 @@ _notmuch_database_log (notmuch_database_t *notmuch, void _notmuch_database_log_append (notmuch_database_t *notmuch, - const char *format, - ...) + const char *format, + ...) { va_list va_args; @@ -669,7 +669,7 @@ notmuch_database_find_message (notmuch_database_t *notmuch, return NOTMUCH_STATUS_SUCCESS; } catch (const Xapian::Error &error) { _notmuch_database_log (notmuch, "A Xapian exception occurred finding message: %s.\n", - error.get_msg().c_str()); + error.get_msg ().c_str ()); notmuch->exception_reported = true; *message_ret = NULL; return NOTMUCH_STATUS_XAPIAN_EXCEPTION; @@ -720,7 +720,7 @@ notmuch_database_create_verbose (const char *path, err = stat (path, &st); if (err) { IGNORE_RESULT (asprintf (&message, "Error: Cannot create database at %s: %s.\n", - path, strerror (errno))); + path, strerror (errno))); status = NOTMUCH_STATUS_FILE_ERROR; goto DONE; } @@ -758,7 +758,7 @@ notmuch_database_create_verbose (const char *path, status = notmuch_database_upgrade (notmuch, NULL, NULL); if (status) { - notmuch_database_close(notmuch); + notmuch_database_close (notmuch); notmuch = NULL; } @@ -893,7 +893,7 @@ notmuch_database_open (const char *path, notmuch_status_t status; status = notmuch_database_open_verbose (path, mode, database, - &status_string); + &status_string); if (status_string) { fputs (status_string, stderr); @@ -952,7 +952,7 @@ notmuch_database_open_verbose (const char *path, } /* Initialize the GLib type system and threads */ -#if !GLIB_CHECK_VERSION(2, 35, 1) +#if ! GLIB_CHECK_VERSION (2, 35, 1) g_type_init (); #endif @@ -967,7 +967,7 @@ notmuch_database_open_verbose (const char *path, notmuch->status_string = NULL; notmuch->path = talloc_strdup (notmuch, path); - strip_trailing(notmuch->path, '/'); + strip_trailing (notmuch->path, '/'); notmuch->mode = mode; notmuch->atomic_nesting = 0; @@ -989,9 +989,9 @@ notmuch_database_open_verbose (const char *path, version = notmuch_database_get_version (notmuch); if (version > NOTMUCH_DATABASE_VERSION) { IGNORE_RESULT (asprintf (&message, - "Error: Notmuch database at %s\n" - " has a newer database format version (%u) than supported by this\n" - " version of notmuch (%u).\n", + "Error: Notmuch database at %s\n" + " has a newer database format version (%u) than supported by this\n" + " version of notmuch (%u).\n", notmuch_path, version, NOTMUCH_DATABASE_VERSION)); notmuch->mode = NOTMUCH_DATABASE_MODE_READ_ONLY; notmuch_database_destroy (notmuch); @@ -1008,9 +1008,9 @@ notmuch_database_open_verbose (const char *path, &incompat_features); if (incompat_features) { IGNORE_RESULT (asprintf (&message, - "Error: Notmuch database at %s\n" - " requires features (%s)\n" - " not supported by this version of notmuch.\n", + "Error: Notmuch database at %s\n" + " requires features (%s)\n" + " not supported by this version of notmuch.\n", notmuch_path, incompat_features)); notmuch->mode = NOTMUCH_DATABASE_MODE_READ_ONLY; notmuch_database_destroy (notmuch); @@ -1067,7 +1067,7 @@ notmuch_database_open_verbose (const char *path, status = _setup_user_query_fields (notmuch); } catch (const Xapian::Error &error) { IGNORE_RESULT (asprintf (&message, "A Xapian exception occurred opening database: %s\n", - error.get_msg().c_str())); + error.get_msg ().c_str ())); notmuch_database_destroy (notmuch); notmuch = NULL; status = NOTMUCH_STATUS_XAPIAN_EXCEPTION; @@ -1108,16 +1108,16 @@ notmuch_database_close (notmuch_database_t *notmuch) if (notmuch->mode == NOTMUCH_DATABASE_MODE_READ_WRITE && notmuch->atomic_nesting) (static_cast (notmuch->xapian_db)) - ->cancel_transaction (); + ->cancel_transaction (); /* Close the database. This implicitly flushes * outstanding changes. */ - notmuch->xapian_db->close(); + notmuch->xapian_db->close (); } catch (const Xapian::Error &error) { status = NOTMUCH_STATUS_XAPIAN_EXCEPTION; if (! notmuch->exception_reported) { _notmuch_database_log (notmuch, "Error: A Xapian exception occurred closing database: %s\n", - error.get_msg().c_str()); + error.get_msg ().c_str ()); } } } @@ -1182,7 +1182,9 @@ class NotmuchCompactor : public Xapian::Compactor public: NotmuchCompactor(notmuch_compact_status_cb_t cb, void *closure) : - status_cb (cb), status_closure (closure) { } + status_cb (cb), status_closure (closure) + { + } virtual void set_status (const std::string &table, const std::string &status) @@ -1193,9 +1195,9 @@ public: return; if (status.length () == 0) - msg = talloc_asprintf (NULL, "compacting table %s", table.c_str()); + msg = talloc_asprintf (NULL, "compacting table %s", table.c_str ()); else - msg = talloc_asprintf (NULL, " %s", status.c_str()); + msg = talloc_asprintf (NULL, " %s", status.c_str ()); if (msg == NULL) { return; @@ -1265,8 +1267,7 @@ notmuch_database_compact (const char *path, goto DONE; } keep_backup = false; - } - else { + } else { keep_backup = true; } @@ -1277,7 +1278,7 @@ notmuch_database_compact (const char *path, } if (errno != ENOENT) { _notmuch_database_log (notmuch, "Unknown error while stat()ing path: %s\n", - strerror (errno)); + strerror (errno)); ret = NOTMUCH_STATUS_FILE_ERROR; goto DONE; } @@ -1296,21 +1297,21 @@ notmuch_database_compact (const char *path, compactor.set_destdir (compact_xapian_path); compactor.compact (); } catch (const Xapian::Error &error) { - _notmuch_database_log (notmuch, "Error while compacting: %s\n", error.get_msg().c_str()); + _notmuch_database_log (notmuch, "Error while compacting: %s\n", error.get_msg ().c_str ()); ret = NOTMUCH_STATUS_XAPIAN_EXCEPTION; goto DONE; } if (rename (xapian_path, backup_path)) { _notmuch_database_log (notmuch, "Error moving %s to %s: %s\n", - xapian_path, backup_path, strerror (errno)); + xapian_path, backup_path, strerror (errno)); ret = NOTMUCH_STATUS_FILE_ERROR; goto DONE; } if (rename (compact_xapian_path, xapian_path)) { _notmuch_database_log (notmuch, "Error moving %s to %s: %s\n", - compact_xapian_path, xapian_path, strerror (errno)); + compact_xapian_path, xapian_path, strerror (errno)); ret = NOTMUCH_STATUS_FILE_ERROR; goto DONE; } @@ -1318,7 +1319,7 @@ notmuch_database_compact (const char *path, if (! keep_backup) { if (rmtree (backup_path)) { _notmuch_database_log (notmuch, "Error removing old database %s: %s\n", - backup_path, strerror (errno)); + backup_path, strerror (errno)); ret = NOTMUCH_STATUS_FILE_ERROR; goto DONE; } @@ -1388,8 +1389,8 @@ notmuch_bool_t notmuch_database_needs_upgrade (notmuch_database_t *notmuch) { return notmuch->mode == NOTMUCH_DATABASE_MODE_READ_WRITE && - ((NOTMUCH_FEATURES_CURRENT & ~notmuch->features) || - (notmuch_database_get_version (notmuch) < NOTMUCH_DATABASE_VERSION)); + ((NOTMUCH_FEATURES_CURRENT & ~notmuch->features) || + (notmuch_database_get_version (notmuch) < NOTMUCH_DATABASE_VERSION)); } static volatile sig_atomic_t do_progress_notify = 0; @@ -1414,8 +1415,8 @@ handle_sigalrm (unused (int signal)) */ notmuch_status_t notmuch_database_upgrade (notmuch_database_t *notmuch, - void (*progress_notify) (void *closure, - double progress), + void (*progress_notify)(void *closure, + double progress), void *closure) { void *local = talloc_new (NULL); @@ -1510,8 +1511,7 @@ notmuch_database_upgrade (notmuch_database_t *notmuch, goto DONE; for (; notmuch_messages_valid (messages); - notmuch_messages_move_to_next (messages)) - { + notmuch_messages_move_to_next (messages)) { if (do_progress_notify) { progress_notify (closure, (double) count / total); do_progress_notify = 0; @@ -1568,8 +1568,7 @@ notmuch_database_upgrade (notmuch_database_t *notmuch, for (t = notmuch->xapian_db->allterms_begin ("XTIMESTAMP"); t != t_end; - t++) - { + t++) { Xapian::PostingIterator p, p_end; std::string term = *t; @@ -1577,8 +1576,7 @@ notmuch_database_upgrade (notmuch_database_t *notmuch, for (p = notmuch->xapian_db->postlist_begin (term); p != p_end; - p++) - { + p++) { Xapian::Document document; time_t mtime; notmuch_directory_t *directory; @@ -1592,7 +1590,7 @@ notmuch_database_upgrade (notmuch_database_t *notmuch, mtime = Xapian::sortable_unserialise ( document.get_value (NOTMUCH_VALUE_TIMESTAMP)); - directory = _notmuch_directory_create (notmuch, term.c_str() + 10, + directory = _notmuch_directory_create (notmuch, term.c_str () + 10, NOTMUCH_FIND_CREATE, &status); notmuch_directory_set_mtime (directory, mtime); notmuch_directory_destroy (directory); @@ -1641,7 +1639,7 @@ notmuch_database_upgrade (notmuch_database_t *notmuch, if (private_status) { _notmuch_database_log (notmuch, - "Upgrade failed while creating ghost messages.\n"); + "Upgrade failed while creating ghost messages.\n"); status = COERCE_STATUS (private_status, "Unexpected status from _notmuch_message_initialize_ghost"); goto DONE; } @@ -1657,7 +1655,7 @@ notmuch_database_upgrade (notmuch_database_t *notmuch, db->set_metadata ("features", _print_features (local, notmuch->features)); db->set_metadata ("version", STRINGIFY (NOTMUCH_DATABASE_VERSION)); - DONE: + DONE: if (status == NOTMUCH_STATUS_SUCCESS) db->commit_transaction (); else @@ -1697,12 +1695,12 @@ notmuch_database_begin_atomic (notmuch_database_t *notmuch) (static_cast (notmuch->xapian_db))->begin_transaction (false); } catch (const Xapian::Error &error) { _notmuch_database_log (notmuch, "A Xapian exception occurred beginning transaction: %s.\n", - error.get_msg().c_str()); + error.get_msg ().c_str ()); notmuch->exception_reported = true; return NOTMUCH_STATUS_XAPIAN_EXCEPTION; } -DONE: + DONE: notmuch->atomic_nesting++; return NOTMUCH_STATUS_SUCCESS; } @@ -1731,7 +1729,7 @@ notmuch_database_end_atomic (notmuch_database_t *notmuch) db->commit (); } catch (const Xapian::Error &error) { _notmuch_database_log (notmuch, "A Xapian exception occurred committing transaction: %s.\n", - error.get_msg().c_str()); + error.get_msg ().c_str ()); notmuch->exception_reported = true; return NOTMUCH_STATUS_XAPIAN_EXCEPTION; } @@ -1741,14 +1739,14 @@ notmuch_database_end_atomic (notmuch_database_t *notmuch) notmuch->atomic_dirty = false; } -DONE: + DONE: notmuch->atomic_nesting--; return NOTMUCH_STATUS_SUCCESS; } unsigned long notmuch_database_get_revision (notmuch_database_t *notmuch, - const char **uuid) + const char **uuid) { if (uuid) *uuid = notmuch->uuid; @@ -1866,7 +1864,7 @@ _notmuch_database_find_directory_id (notmuch_database_t *notmuch, } directory = _notmuch_directory_create (notmuch, path, flags, &status); - if (status || !directory) { + if (status || ! directory) { *directory_id = -1; return status; } @@ -1920,7 +1918,7 @@ _notmuch_database_filename_to_direntry (void *ctx, status = _notmuch_database_find_directory_id (notmuch, directory, flags, &directory_id); - if (status || directory_id == (unsigned int)-1) { + if (status || directory_id == (unsigned int) -1) { *direntry = NULL; return status; } @@ -1950,11 +1948,10 @@ _notmuch_database_relative_path (notmuch_database_t *notmuch, relative = path; if (*relative == '/') { - while (*relative == '/' && *(relative+1) == '/') + while (*relative == '/' && *(relative + 1) == '/') relative++; - if (strncmp (relative, db_path, db_path_len) == 0) - { + if (strncmp (relative, db_path, db_path_len) == 0) { relative += db_path_len; while (*relative == '/') relative++; @@ -1980,7 +1977,7 @@ notmuch_database_get_directory (notmuch_database_t *notmuch, NOTMUCH_FIND_LOOKUP, &status); } catch (const Xapian::Error &error) { _notmuch_database_log (notmuch, "A Xapian exception occurred getting directory: %s.\n", - error.get_msg().c_str()); + error.get_msg ().c_str ()); notmuch->exception_reported = true; status = NOTMUCH_STATUS_XAPIAN_EXCEPTION; } @@ -2023,13 +2020,13 @@ notmuch_database_remove_message (notmuch_database_t *notmuch, &message); if (status == NOTMUCH_STATUS_SUCCESS && message) { - status = _notmuch_message_remove_filename (message, filename); - if (status == NOTMUCH_STATUS_SUCCESS) - _notmuch_message_delete (message); - else if (status == NOTMUCH_STATUS_DUPLICATE_MESSAGE_ID) - _notmuch_message_sync (message); + status = _notmuch_message_remove_filename (message, filename); + if (status == NOTMUCH_STATUS_SUCCESS) + _notmuch_message_delete (message); + else if (status == NOTMUCH_STATUS_DUPLICATE_MESSAGE_ID) + _notmuch_message_sync (message); - notmuch_message_destroy (message); + notmuch_message_destroy (message); } return status; @@ -2060,7 +2057,7 @@ notmuch_database_find_message_by_filename (notmuch_database_t *notmuch, try { status = _notmuch_database_filename_to_direntry ( local, notmuch, filename, NOTMUCH_FIND_LOOKUP, &direntry); - if (status || !direntry) + if (status || ! direntry) goto DONE; term = talloc_asprintf (local, "%s%s", prefix, direntry); @@ -2077,7 +2074,7 @@ notmuch_database_find_message_by_filename (notmuch_database_t *notmuch, } } catch (const Xapian::Error &error) { _notmuch_database_log (notmuch, "Error: A Xapian exception occurred finding message by filename: %s\n", - error.get_msg().c_str()); + error.get_msg ().c_str ()); notmuch->exception_reported = true; status = NOTMUCH_STATUS_XAPIAN_EXCEPTION; } @@ -2122,15 +2119,15 @@ notmuch_database_get_all_tags (notmuch_database_t *db) notmuch_string_list_t *tags; try { - i = db->xapian_db->allterms_begin(); - end = db->xapian_db->allterms_end(); + i = db->xapian_db->allterms_begin (); + end = db->xapian_db->allterms_end (); tags = _notmuch_database_get_terms_with_prefix (db, i, end, _find_prefix ("tag")); _notmuch_string_list_sort (tags); return _notmuch_tags_create (db, tags); } catch (const Xapian::Error &error) { _notmuch_database_log (db, "A Xapian exception occurred getting tags: %s.\n", - error.get_msg().c_str()); + error.get_msg ().c_str ()); db->exception_reported = true; return NULL; } diff --git a/lib/directory.cc b/lib/directory.cc index 4fcb0177..af71f402 100644 --- a/lib/directory.cc +++ b/lib/directory.cc @@ -32,8 +32,8 @@ _create_filenames_for_terms_with_prefix (void *ctx, notmuch_string_list_t *filename_list; Xapian::TermIterator i, end; - i = notmuch->xapian_db->allterms_begin(); - end = notmuch->xapian_db->allterms_end(); + i = notmuch->xapian_db->allterms_begin (); + end = notmuch->xapian_db->allterms_end (); filename_list = _notmuch_database_get_terms_with_prefix (ctx, i, end, prefix); if (unlikely (filename_list == NULL)) @@ -140,7 +140,7 @@ _notmuch_directory_create (notmuch_database_t *notmuch, directory->document_id = directory->doc.get_docid (); if (private_status == NOTMUCH_PRIVATE_STATUS_NO_DOCUMENT_FOUND) { - if (!create) { + if (! create) { notmuch_directory_destroy (directory); directory = NULL; *status_ret = NOTMUCH_STATUS_SUCCESS; @@ -187,8 +187,8 @@ _notmuch_directory_create (notmuch_database_t *notmuch, directory->doc.get_value (NOTMUCH_VALUE_TIMESTAMP)); } catch (const Xapian::Error &error) { _notmuch_database_log (notmuch, - "A Xapian exception occurred creating a directory: %s.\n", - error.get_msg().c_str()); + "A Xapian exception occurred creating a directory: %s.\n", + error.get_msg ().c_str ()); notmuch->exception_reported = true; notmuch_directory_destroy (directory); directory = NULL; @@ -224,7 +224,7 @@ notmuch_directory_set_mtime (notmuch_directory_t *directory, try { directory->doc.add_value (NOTMUCH_VALUE_TIMESTAMP, - Xapian::sortable_serialise (mtime)); + Xapian::sortable_serialise (mtime)); db->replace_document (directory->document_id, directory->doc); @@ -232,8 +232,8 @@ notmuch_directory_set_mtime (notmuch_directory_t *directory, } catch (const Xapian::Error &error) { _notmuch_database_log (notmuch, - "A Xapian exception occurred setting directory mtime: %s.\n", - error.get_msg().c_str()); + "A Xapian exception occurred setting directory mtime: %s.\n", + error.get_msg ().c_str ()); notmuch->exception_reported = true; return NOTMUCH_STATUS_XAPIAN_EXCEPTION; } @@ -277,7 +277,7 @@ notmuch_directory_get_child_directories (notmuch_directory_t *directory) directory->document_id); child_directories = _create_filenames_for_terms_with_prefix (directory, - directory->notmuch, term); + directory->notmuch, term); talloc_free (term); @@ -300,7 +300,7 @@ notmuch_directory_delete (notmuch_directory_t *directory) } catch (const Xapian::Error &error) { _notmuch_database_log (directory->notmuch, "A Xapian exception occurred deleting directory entry: %s.\n", - error.get_msg().c_str()); + error.get_msg ().c_str ()); directory->notmuch->exception_reported = true; status = NOTMUCH_STATUS_XAPIAN_EXCEPTION; } diff --git a/lib/index.cc b/lib/index.cc index 1fd9e67e..db3dd568 100644 --- a/lib/index.cc +++ b/lib/index.cc @@ -43,46 +43,46 @@ typedef struct { * which we discard data. */ static const int first_uuencode_skipping_state = 11; static const scanner_state_t uuencode_states[] = { - {0, 'b', 'b', 1, 0}, - {1, 'e', 'e', 2, 0}, - {2, 'g', 'g', 3, 0}, - {3, 'i', 'i', 4, 0}, - {4, 'n', 'n', 5, 0}, - {5, ' ', ' ', 6, 0}, - {6, '0', '7', 7, 0}, - {7, '0', '7', 8, 0}, - {8, '0', '7', 9, 0}, - {9, ' ', ' ', 10, 0}, - {10, '\n', '\n', 11, 10}, - {11, 'M', 'M', 12, 0}, - {12, ' ', '`', 12, 11} + { 0, 'b', 'b', 1, 0 }, + { 1, 'e', 'e', 2, 0 }, + { 2, 'g', 'g', 3, 0 }, + { 3, 'i', 'i', 4, 0 }, + { 4, 'n', 'n', 5, 0 }, + { 5, ' ', ' ', 6, 0 }, + { 6, '0', '7', 7, 0 }, + { 7, '0', '7', 8, 0 }, + { 8, '0', '7', 9, 0 }, + { 9, ' ', ' ', 10, 0 }, + { 10, '\n', '\n', 11, 10 }, + { 11, 'M', 'M', 12, 0 }, + { 12, ' ', '`', 12, 11 } }; /* The following table is intended to implement this DFA (in 'dot' - format). Note that 2 and 3 are "hidden" states used to step through - the possible out edges of state 1. - -digraph html_filter { - 0 -> 1 [label="<"]; - 0 -> 0; - 1 -> 4 [label="'"]; - 1 -> 5 [label="\""]; - 1 -> 0 [label=">"]; - 1 -> 1; - 4 -> 1 [label="'"]; - 4 -> 4; - 5 -> 1 [label="\""]; - 5 -> 5; -} -*/ + * format). Note that 2 and 3 are "hidden" states used to step through + * the possible out edges of state 1. + * + * digraph html_filter { + * 0 -> 1 [label="<"]; + * 0 -> 0; + * 1 -> 4 [label="'"]; + * 1 -> 5 [label="\""]; + * 1 -> 0 [label=">"]; + * 1 -> 1; + * 4 -> 1 [label="'"]; + * 4 -> 4; + * 5 -> 1 [label="\""]; + * 5 -> 5; + * } + */ static const int first_html_skipping_state = 1; static const scanner_state_t html_states[] = { - {0, '<', '<', 1, 0}, - {1, '\'', '\'', 4, 2}, /* scanning for quote or > */ - {1, '"', '"', 5, 3}, - {1, '>', '>', 0, 1}, - {4, '\'', '\'', 1, 4}, /* inside single quotes */ - {5, '"', '"', 1, 5}, /* inside double quotes */ + { 0, '<', '<', 1, 0 }, + { 1, '\'', '\'', 4, 2 }, /* scanning for quote or > */ + { 1, '"', '"', 5, 3 }, + { 1, '>', '>', 0, 1 }, + { 4, '\'', '\'', 1, 4 }, /* inside single quotes */ + { 5, '"', '"', 1, 5 }, /* inside double quotes */ }; /* Oh, how I wish that gobject didn't require so much noisy boilerplate! @@ -168,6 +168,7 @@ static GMimeFilter * filter_copy (GMimeFilter *gmime_filter) { NotmuchFilterDiscardNonTerm *filter = (NotmuchFilterDiscardNonTerm *) gmime_filter; + return notmuch_filter_discard_non_term_new (filter->content_type); } @@ -190,7 +191,7 @@ filter_filter (GMimeFilter *gmime_filter, char *inbuf, size_t inlen, size_t pres next = filter->state; while (inptr < inend) { - /* Each state is defined by a contiguous set of rows of the + /* Each state is defined by a contiguous set of rows of the * state table marked by a common value for '.state'. The * state numbers must be equal to the index of the first row * in a given state; thus the loop condition here looks for a @@ -198,9 +199,9 @@ filter_filter (GMimeFilter *gmime_filter, char *inbuf, size_t inlen, size_t pres * in the underlying DFA. */ do { - if (*inptr >= states[next].a && *inptr <= states[next].b) { + if (*inptr >= states[next].a && *inptr <= states[next].b) { next = states[next].next_if_match; - } else { + } else { next = states[next].next_if_not_match; } @@ -245,7 +246,7 @@ notmuch_filter_discard_non_term_new (GMimeContentType *content_type) static GType type = 0; NotmuchFilterDiscardNonTerm *filter; - if (!type) { + if (! type) { static const GTypeInfo info = { .class_size = sizeof (NotmuchFilterDiscardNonTermClass), .base_init = NULL, @@ -266,11 +267,11 @@ notmuch_filter_discard_non_term_new (GMimeContentType *content_type) filter->content_type = content_type; filter->state = 0; if (g_mime_content_type_is_type (content_type, "text", "html")) { - filter->states = html_states; - filter->first_skipping_state = first_html_skipping_state; + filter->states = html_states; + filter->first_skipping_state = first_html_skipping_state; } else { - filter->states = uuencode_states; - filter->first_skipping_state = first_uuencode_skipping_state; + filter->states = uuencode_states; + filter->first_skipping_state = first_uuencode_skipping_state; } return (GMimeFilter *) filter; @@ -356,6 +357,7 @@ static void _index_content_type (notmuch_message_t *message, GMimeObject *part) { GMimeContentType *content_type = g_mime_object_get_content_type (part); + if (content_type) { char *mime_string = g_mime_content_type_get_mime_type (content_type); if (mime_string) { @@ -388,7 +390,7 @@ _index_mime_part (notmuch_message_t *message, if (! part) { _notmuch_database_log (notmuch_message_get_database (message), - "Warning: Not indexing empty mime part.\n"); + "Warning: Not indexing empty mime part.\n"); return; } @@ -399,10 +401,10 @@ _index_mime_part (notmuch_message_t *message, int i; if (GMIME_IS_MULTIPART_SIGNED (multipart)) - _notmuch_message_add_term (message, "tag", "signed"); + _notmuch_message_add_term (message, "tag", "signed"); if (GMIME_IS_MULTIPART_ENCRYPTED (multipart)) - _notmuch_message_add_term (message, "tag", "encrypted"); + _notmuch_message_add_term (message, "tag", "encrypted"); for (i = 0; i < g_mime_multipart_get_count (multipart); i++) { notmuch_status_t status; @@ -422,9 +424,9 @@ _index_mime_part (notmuch_message_t *message, _index_content_type (message, g_mime_multipart_get_part (multipart, i)); if (i == GMIME_MULTIPART_ENCRYPTED_CONTENT) { - _index_encrypted_mime_part(message, indexopts, - GMIME_MULTIPART_ENCRYPTED (part), - msg_crypto); + _index_encrypted_mime_part (message, indexopts, + GMIME_MULTIPART_ENCRYPTED (part), + msg_crypto); } else { if (i != GMIME_MULTIPART_ENCRYPTED_VERSION) { _notmuch_database_log (notmuch_message_get_database (message), @@ -456,16 +458,15 @@ _index_mime_part (notmuch_message_t *message, if (! (GMIME_IS_PART (part))) { _notmuch_database_log (notmuch_message_get_database (message), - "Warning: Not indexing unknown mime part: %s.\n", - g_type_name (G_OBJECT_TYPE (part))); + "Warning: Not indexing unknown mime part: %s.\n", + g_type_name (G_OBJECT_TYPE (part))); return; } disposition = g_mime_object_get_content_disposition (part); if (disposition && strcasecmp (g_mime_content_disposition_get_disposition (disposition), - GMIME_DISPOSITION_ATTACHMENT) == 0) - { + GMIME_DISPOSITION_ATTACHMENT) == 0) { const char *filename = g_mime_part_get_filename (GMIME_PART (part)); _notmuch_message_add_term (message, "tag", "attachment"); @@ -531,10 +532,10 @@ _index_encrypted_mime_part (notmuch_message_t *message, { notmuch_status_t status; GError *err = NULL; - notmuch_database_t * notmuch = NULL; + notmuch_database_t *notmuch = NULL; GMimeObject *clear = NULL; - if (!indexopts || (notmuch_indexopts_get_decrypt_policy (indexopts) == NOTMUCH_DECRYPT_FALSE)) + if (! indexopts || (notmuch_indexopts_get_decrypt_policy (indexopts) == NOTMUCH_DECRYPT_FALSE)) return; notmuch = notmuch_message_get_database (message); @@ -544,15 +545,15 @@ _index_encrypted_mime_part (notmuch_message_t *message, bool get_sk = (notmuch_indexopts_get_decrypt_policy (indexopts) == NOTMUCH_DECRYPT_TRUE); clear = _notmuch_crypto_decrypt (&attempted, notmuch_indexopts_get_decrypt_policy (indexopts), message, encrypted_data, get_sk ? &decrypt_result : NULL, &err); - if (!attempted) + if (! attempted) return; - if (err || !clear) { + if (err || ! clear) { if (decrypt_result) g_object_unref (decrypt_result); if (err) { _notmuch_database_log (notmuch, "Failed to decrypt during indexing. (%d:%d) [%s]\n", err->domain, err->code, err->message); - g_error_free(err); + g_error_free (err); } else { _notmuch_database_log (notmuch, "Failed to decrypt during indexing. (unknown error)\n"); } diff --git a/lib/indexopts.c b/lib/indexopts.c index b78a57b6..1e8d180e 100644 --- a/lib/indexopts.c +++ b/lib/indexopts.c @@ -24,25 +24,26 @@ notmuch_indexopts_t * notmuch_database_get_default_indexopts (notmuch_database_t *db) { notmuch_indexopts_t *ret = talloc_zero (db, notmuch_indexopts_t); - if (!ret) + + if (! ret) return ret; ret->crypto.decrypt = NOTMUCH_DECRYPT_AUTO; - char * decrypt_policy; + char *decrypt_policy; notmuch_status_t err = notmuch_database_get_config (db, "index.decrypt", &decrypt_policy); if (err) return ret; if (decrypt_policy) { - if ((!(strcasecmp(decrypt_policy, "true"))) || - (!(strcasecmp(decrypt_policy, "yes"))) || - (!(strcasecmp(decrypt_policy, "1")))) + if ((! (strcasecmp (decrypt_policy, "true"))) || + (! (strcasecmp (decrypt_policy, "yes"))) || + (! (strcasecmp (decrypt_policy, "1")))) notmuch_indexopts_set_decrypt_policy (ret, NOTMUCH_DECRYPT_TRUE); - else if ((!(strcasecmp(decrypt_policy, "false"))) || - (!(strcasecmp(decrypt_policy, "no"))) || - (!(strcasecmp(decrypt_policy, "0")))) + else if ((! (strcasecmp (decrypt_policy, "false"))) || + (! (strcasecmp (decrypt_policy, "no"))) || + (! (strcasecmp (decrypt_policy, "0")))) notmuch_indexopts_set_decrypt_policy (ret, NOTMUCH_DECRYPT_FALSE); - else if (!strcasecmp(decrypt_policy, "nostash")) + else if (! strcasecmp (decrypt_policy, "nostash")) notmuch_indexopts_set_decrypt_policy (ret, NOTMUCH_DECRYPT_NOSTASH); } @@ -54,7 +55,7 @@ notmuch_status_t notmuch_indexopts_set_decrypt_policy (notmuch_indexopts_t *indexopts, notmuch_decryption_policy_t decrypt_policy) { - if (!indexopts) + if (! indexopts) return NOTMUCH_STATUS_NULL_POINTER; indexopts->crypto.decrypt = decrypt_policy; return NOTMUCH_STATUS_SUCCESS; @@ -63,7 +64,7 @@ notmuch_indexopts_set_decrypt_policy (notmuch_indexopts_t *indexopts, notmuch_decryption_policy_t notmuch_indexopts_get_decrypt_policy (const notmuch_indexopts_t *indexopts) { - if (!indexopts) + if (! indexopts) return false; return indexopts->crypto.decrypt; } diff --git a/lib/message-file.c b/lib/message-file.c index 24c5fda4..e1db26fb 100644 --- a/lib/message-file.c +++ b/lib/message-file.c @@ -78,7 +78,7 @@ _notmuch_message_file_open_ctx (notmuch_database_t *notmuch, FAIL: _notmuch_database_log (notmuch, "Error opening %s: %s\n", - filename, strerror (errno)); + filename, strerror (errno)); _notmuch_message_file_close (message); return NULL; @@ -110,7 +110,7 @@ _is_mbox (GMimeStream *stream) bool ret = false; /* Is this mbox? */ - if (g_mime_stream_read (stream, from_buf, sizeof (from_buf)) == sizeof(from_buf) && + if (g_mime_stream_read (stream, from_buf, sizeof (from_buf)) == sizeof (from_buf) && strncmp (from_buf, "From ", 5) == 0) ret = true; @@ -201,7 +201,8 @@ _notmuch_message_file_get_mime_message (notmuch_message_file_t *message, */ static char * -_extend_header (char *combined, const char *value) { +_extend_header (char *combined, const char *value) +{ char *decoded; decoded = g_mime_utils_header_decode_text (NULL, value); @@ -226,7 +227,7 @@ _extend_header (char *combined, const char *value) { } else { combined = decoded; } - DONE: + DONE: return combined; } @@ -242,7 +243,7 @@ _notmuch_message_file_get_combined_header (notmuch_message_file_t *message, return NULL; - for (int i=0; i < g_mime_header_list_get_count (headers); i++) { + for (int i = 0; i < g_mime_header_list_get_count (headers); i++) { const char *value; GMimeHeader *g_header = g_mime_header_list_get_header_at (headers, i); @@ -264,7 +265,7 @@ _notmuch_message_file_get_combined_header (notmuch_message_file_t *message, const char * _notmuch_message_file_get_header (notmuch_message_file_t *message, - const char *header) + const char *header) { const char *value; char *decoded; @@ -366,7 +367,7 @@ _notmuch_message_file_get_headers (notmuch_message_file_t *message_file, message_id = talloc_asprintf (message_file, "notmuch-sha1-%s", sha1); free (sha1); } - DONE: + DONE: if (ret == NOTMUCH_STATUS_SUCCESS) { if (from_out) *from_out = from; diff --git a/lib/message-id.c b/lib/message-id.c index e71ce9f4..54012354 100644 --- a/lib/message-id.c +++ b/lib/message-id.c @@ -27,7 +27,7 @@ skip_space_and_comments (const char **str) } else if (*s == ')') { nesting--; } else if (*s == '\\') { - if (*(s+1)) + if (*(s + 1)) s++; } s++; @@ -90,7 +90,7 @@ _notmuch_message_id_parse (void *ctx, const char *message_id, const char **next) for (r = result, len = strlen (r); *r; r++, len--) if (*r == ' ' || *r == '\t') - memmove (r, r+1, len); + memmove (r, r + 1, len); } return result; diff --git a/lib/message-property.cc b/lib/message-property.cc index 710ba046..ecf7e140 100644 --- a/lib/message-property.cc +++ b/lib/message-property.cc @@ -115,7 +115,7 @@ notmuch_status_t _notmuch_message_remove_all_properties (notmuch_message_t *message, const char *key, bool prefix) { notmuch_status_t status; - const char * term_prefix; + const char *term_prefix; status = _notmuch_database_ensure_writable (notmuch_message_get_database (message)); if (status) @@ -150,6 +150,7 @@ notmuch_message_properties_t * notmuch_message_get_properties (notmuch_message_t *message, const char *key, notmuch_bool_t exact) { notmuch_string_map_t *map; + map = _notmuch_message_property_map (message); return _notmuch_string_map_iterator_create (map, key, exact); } diff --git a/lib/message.cc b/lib/message.cc index 9e1005a3..5c9b58b2 100644 --- a/lib/message.cc +++ b/lib/message.cc @@ -69,10 +69,10 @@ struct maildir_flag_tag { /* ASCII ordered table of Maildir flags and associated tags */ static struct maildir_flag_tag flag2tag[] = { - { 'D', "draft", false}, - { 'F', "flagged", false}, - { 'P', "passed", false}, - { 'R', "replied", false}, + { 'D', "draft", false }, + { 'F', "flagged", false }, + { 'P', "passed", false }, + { 'R', "replied", false }, { 'S', "unread", true } }; @@ -274,8 +274,8 @@ _notmuch_message_create_for_message_id (notmuch_database_t *notmuch, doc_id = _notmuch_database_generate_doc_id (notmuch); } catch (const Xapian::Error &error) { - _notmuch_database_log(notmuch_message_get_database (message), "A Xapian exception occurred creating message: %s\n", - error.get_msg().c_str()); + _notmuch_database_log (notmuch_message_get_database (message), "A Xapian exception occurred creating message: %s\n", + error.get_msg ().c_str ()); notmuch->exception_reported = true; *status_ret = NOTMUCH_PRIVATE_STATUS_XAPIAN_EXCEPTION; return NULL; @@ -306,10 +306,10 @@ _notmuch_message_get_term (notmuch_message_t *message, return NULL; const std::string &term = *i; - if (strncmp (term.c_str(), prefix, prefix_len)) + if (strncmp (term.c_str (), prefix, prefix_len)) return NULL; - value = talloc_strdup (message, term.c_str() + prefix_len); + value = talloc_strdup (message, term.c_str () + prefix_len); #if DEBUG_DATABASE_SANITY i++; @@ -350,32 +350,32 @@ _notmuch_message_ensure_metadata (notmuch_message_t *message, void *field) return; const char *thread_prefix = _find_prefix ("thread"), - *tag_prefix = _find_prefix ("tag"), - *id_prefix = _find_prefix ("id"), - *type_prefix = _find_prefix ("type"), - *filename_prefix = _find_prefix ("file-direntry"), - *property_prefix = _find_prefix ("property"), - *reference_prefix = _find_prefix ("reference"), - *replyto_prefix = _find_prefix ("replyto"); + *tag_prefix = _find_prefix ("tag"), + *id_prefix = _find_prefix ("id"), + *type_prefix = _find_prefix ("type"), + *filename_prefix = _find_prefix ("file-direntry"), + *property_prefix = _find_prefix ("property"), + *reference_prefix = _find_prefix ("reference"), + *replyto_prefix = _find_prefix ("replyto"); /* We do this all in a single pass because Xapian decompresses the * term list every time you iterate over it. Thus, while this is * slightly more costly than looking up individual fields if only * one field of the message object is actually used, it's a huge * win as more fields are used. */ - for (int count=0; count < 3; count++) { + for (int count = 0; count < 3; count++) { try { i = message->doc.termlist_begin (); end = message->doc.termlist_end (); /* Get thread */ - if (!message->thread_id) + if (! message->thread_id) message->thread_id = _notmuch_message_get_term (message, i, end, thread_prefix); /* Get tags */ assert (strcmp (thread_prefix, tag_prefix) < 0); - if (!message->tag_list) { + if (! message->tag_list) { message->tag_list = _notmuch_database_get_terms_with_prefix (message, i, end, tag_prefix); @@ -384,7 +384,7 @@ _notmuch_message_ensure_metadata (notmuch_message_t *message, void *field) /* Get id */ assert (strcmp (tag_prefix, id_prefix) < 0); - if (!message->message_id) + if (! message->message_id) message->message_id = _notmuch_message_get_term (message, i, end, id_prefix); @@ -407,7 +407,7 @@ _notmuch_message_ensure_metadata (notmuch_message_t *message, void *field) * expand them to full file names when needed in * _notmuch_message_ensure_filename_list. */ assert (strcmp (type_prefix, filename_prefix) < 0); - if (!message->filename_term_list && !message->filename_list) + if (! message->filename_term_list && ! message->filename_list) message->filename_term_list = _notmuch_database_get_terms_with_prefix (message, i, end, filename_prefix); @@ -415,14 +415,14 @@ _notmuch_message_ensure_metadata (notmuch_message_t *message, void *field) /* Get property terms. Mimic the setup with filenames above */ assert (strcmp (filename_prefix, property_prefix) < 0); - if (!message->property_map && !message->property_term_list) + if (! message->property_map && ! message->property_term_list) message->property_term_list = _notmuch_database_get_terms_with_prefix (message, i, end, - property_prefix); + property_prefix); /* get references */ assert (strcmp (property_prefix, reference_prefix) < 0); - if (!message->reference_list) { + if (! message->reference_list) { message->reference_list = _notmuch_database_get_terms_with_prefix (message, i, end, reference_prefix); @@ -430,14 +430,14 @@ _notmuch_message_ensure_metadata (notmuch_message_t *message, void *field) /* Get reply to */ assert (strcmp (property_prefix, replyto_prefix) < 0); - if (!message->in_reply_to) + if (! message->in_reply_to) message->in_reply_to = _notmuch_message_get_term (message, i, end, replyto_prefix); /* It's perfectly valid for a message to have no In-Reply-To * header. For these cases, we return an empty string. */ - if (!message->in_reply_to) + if (! message->in_reply_to) message->in_reply_to = talloc_strdup (message, ""); /* all the way without an exception */ @@ -449,7 +449,7 @@ _notmuch_message_ensure_metadata (notmuch_message_t *message, void *field) notmuch_status_to_string (status)); } catch (const Xapian::Error &error) { INTERNAL_ERROR ("A Xapian exception occurred fetching message metadata: %s\n", - error.get_msg().c_str()); + error.get_msg ().c_str ()); } } message->last_view = message->notmuch->view; @@ -508,7 +508,7 @@ const char * notmuch_message_get_message_id (notmuch_message_t *message) { _notmuch_message_ensure_metadata (message, message->message_id); - if (!message->message_id) + if (! message->message_id) INTERNAL_ERROR ("Message with document ID of %u has no message ID.\n", message->doc_id); return message->message_id; @@ -553,12 +553,12 @@ notmuch_message_get_header (notmuch_message_t *message, const char *header) * it could just mean we didn't record the header. */ if ((message->notmuch->features & NOTMUCH_FEATURE_FROM_SUBJECT_ID_VALUES) || - ! value.empty()) + ! value.empty ()) return talloc_strdup (message, value.c_str ()); } catch (Xapian::Error &error) { - _notmuch_database_log(notmuch_message_get_database (message), "A Xapian exception occurred when reading header: %s\n", - error.get_msg().c_str()); + _notmuch_database_log (notmuch_message_get_database (message), "A Xapian exception occurred when reading header: %s\n", + error.get_msg ().c_str ()); message->notmuch->exception_reported = true; return NULL; } @@ -590,7 +590,7 @@ const char * notmuch_message_get_thread_id (notmuch_message_t *message) { _notmuch_message_ensure_metadata (message, message->thread_id); - if (!message->thread_id) + if (! message->thread_id) INTERNAL_ERROR ("Message with document ID of %u has no thread ID.\n", message->doc_id); return message->thread_id; @@ -604,7 +604,8 @@ _notmuch_message_add_reply (notmuch_message_t *message, } size_t -_notmuch_message_get_thread_depth (notmuch_message_t *message) { +_notmuch_message_get_thread_depth (notmuch_message_t *message) +{ return message->thread_depth; } @@ -618,7 +619,7 @@ _notmuch_message_label_depths (notmuch_message_t *message, notmuch_messages_valid (messages); notmuch_messages_move_to_next (messages)) { notmuch_message_t *child = notmuch_messages_get (messages); - _notmuch_message_label_depths (child, depth+1); + _notmuch_message_label_depths (child, depth + 1); } } @@ -730,7 +731,7 @@ _notmuch_message_remove_indexed_terms (notmuch_message_t *message) type_prefix = _find_prefix ("type"); /* Make sure we have the data to restore to Xapian*/ - _notmuch_message_ensure_metadata (message,NULL); + _notmuch_message_ensure_metadata (message, NULL); /* Empirically, it turns out to be faster to remove all the terms, * and add back the ones we want. */ @@ -754,7 +755,7 @@ _notmuch_message_remove_indexed_terms (notmuch_message_t *message) STRNCMP_LITERAL (tag, "signed") != 0 && STRNCMP_LITERAL (tag, "attachment") != 0) { std::string term = tag_prefix + tag; - message->doc.add_term(term); + message->doc.add_term (term); } } @@ -764,10 +765,10 @@ _notmuch_message_remove_indexed_terms (notmuch_message_t *message) for (list = notmuch_message_get_properties (message, "", false); notmuch_message_properties_valid (list); notmuch_message_properties_move_to_next (list)) { std::string term = property_prefix + - notmuch_message_properties_key(list) + "=" + - notmuch_message_properties_value(list); + notmuch_message_properties_key (list) + "=" + + notmuch_message_properties_value (list); - message->doc.add_term(term); + message->doc.add_term (term); } notmuch_message_properties_destroy (list); @@ -777,7 +778,8 @@ _notmuch_message_remove_indexed_terms (notmuch_message_t *message) /* Return true if p points at "new" or "cur". */ -static bool is_maildir (const char *p) +static bool +is_maildir (const char *p) { return strcmp (p, "cur") == 0 || strcmp (p, "new") == 0; } @@ -972,7 +974,7 @@ _notmuch_message_remove_filename (notmuch_message_t *message, status = _notmuch_database_filename_to_direntry ( local, message->notmuch, filename, NOTMUCH_FIND_LOOKUP, &direntry); - if (status || !direntry) + if (status || ! direntry) return status; /* Unlink this file from its parent directory. */ @@ -1041,7 +1043,7 @@ _notmuch_message_ensure_filename_list (notmuch_message_t *message) message->filename_list = _notmuch_string_list_create (message); node = message->filename_term_list->head; - if (!node) { + if (! node) { /* A message document created by an old version of notmuch * (prior to rename support) will have the filename in the * data of the document rather than as a file-direntry term. @@ -1108,8 +1110,7 @@ notmuch_message_get_filename (notmuch_message_t *message) return NULL; if (message->filename_list->head == NULL || - message->filename_list->head->string == NULL) - { + message->filename_list->head->string == NULL) { INTERNAL_ERROR ("message with no filename"); } @@ -1162,8 +1163,8 @@ notmuch_message_get_date (notmuch_message_t *message) try { value = message->doc.get_value (NOTMUCH_VALUE_TIMESTAMP); } catch (Xapian::Error &error) { - _notmuch_database_log(notmuch_message_get_database (message), "A Xapian exception occurred when reading date: %s\n", - error.get_msg().c_str()); + _notmuch_database_log (notmuch_message_get_database (message), "A Xapian exception occurred when reading date: %s\n", + error.get_msg ().c_str ()); message->notmuch->exception_reported = true; return 0; } @@ -1188,7 +1189,7 @@ notmuch_message_get_tags (notmuch_message_t *message) * possible to modify the message tags (which talloc_unlink's the * current list from the message) while still iterating because * the iterator will keep the current list alive. */ - if (!talloc_reference (message, message->tag_list)) + if (! talloc_reference (message, message->tag_list)) return NULL; return tags; @@ -1202,11 +1203,11 @@ _notmuch_message_get_author (notmuch_message_t *message) void _notmuch_message_set_author (notmuch_message_t *message, - const char *author) + const char *author) { if (message->author) - talloc_free(message->author); - message->author = talloc_strdup(message, author); + talloc_free (message->author); + message->author = talloc_strdup (message, author); return; } @@ -1339,8 +1340,8 @@ _notmuch_message_delete (notmuch_message_t *message) _notmuch_message_sync (ghost); } else if (private_status == NOTMUCH_PRIVATE_STATUS_SUCCESS) { /* this is deeply weird, and we should not have gotten - into this state. is there a better error message to - return here? */ + * into this state. is there a better error message to + * return here? */ status = NOTMUCH_STATUS_DUPLICATE_MESSAGE_ID; } @@ -1358,8 +1359,8 @@ _notmuch_message_delete (notmuch_message_t *message) message = notmuch_messages_get (messages); status = _notmuch_message_delete (message); if (status) /* we'll report the last failure we see; - * if there is more than one failure, we - * forget about previous ones */ + * if there is more than one failure, we + * forget about previous ones */ last_error = status; notmuch_message_destroy (message); notmuch_messages_move_to_next (messages); @@ -1535,7 +1536,7 @@ _notmuch_message_has_term (notmuch_message_t *message, Xapian::TermIterator i = message->doc.termlist_begin (); i.skip_to (term); if (i != message->doc.termlist_end () && - !strcmp ((*i).c_str (), term)) + ! strcmp ((*i).c_str (), term)) out = true; } catch (Xapian::Error &error) { status = NOTMUCH_PRIVATE_STATUS_XAPIAN_EXCEPTION; @@ -1635,8 +1636,7 @@ _filename_is_in_maildir (const char *filename) dir = slash + 1; if (STRNCMP_LITERAL (dir, "cur/") == 0 || - STRNCMP_LITERAL (dir, "new/") == 0) - { + STRNCMP_LITERAL (dir, "new/") == 0) { return dir; } @@ -1661,8 +1661,7 @@ _ensure_maildir_flags (notmuch_message_t *message, bool force) for (filenames = notmuch_message_get_filenames (message); notmuch_filenames_valid (filenames); - notmuch_filenames_move_to_next (filenames)) - { + notmuch_filenames_move_to_next (filenames)) { filename = notmuch_filenames_get (filenames); dir = _filename_is_in_maildir (filename); @@ -1712,11 +1711,10 @@ notmuch_message_maildir_flags_to_tags (notmuch_message_t *message) if (status) return status; - for (i = 0; i < ARRAY_SIZE(flag2tag); i++) { + for (i = 0; i < ARRAY_SIZE (flag2tag); i++) { if ((strchr (message->maildir_flags, flag2tag[i].flag) != NULL) ^ - flag2tag[i].inverse) - { + flag2tag[i].inverse) { status = notmuch_message_add_tag (message, flag2tag[i].tag); } else { status = notmuch_message_remove_tag (message, flag2tag[i].tag); @@ -1751,8 +1749,7 @@ _get_maildir_flag_actions (notmuch_message_t *message, /* First, find flags for all set tags. */ for (tags = notmuch_message_get_tags (message); notmuch_tags_valid (tags); - notmuch_tags_move_to_next (tags)) - { + notmuch_tags_move_to_next (tags)) { tag = notmuch_tags_get (tags); for (i = 0; i < ARRAY_SIZE (flag2tag); i++) { @@ -1802,7 +1799,7 @@ _get_maildir_flag_actions (notmuch_message_t *message, * non-ASCII ordering of flags), this function will return NULL * (meaning that renaming would not be safe and should not occur). */ -static char* +static char * _new_maildir_filename (void *ctx, const char *filename, const char *flags_to_set, @@ -1822,13 +1819,12 @@ _new_maildir_filename (void *ctx, info = strstr (filename, ":2,"); if (info == NULL) { - info = filename + strlen(filename); + info = filename + strlen (filename); } else { /* Loop through existing flags in filename. */ for (flags = info + 3, last_flag = 0; *flags; - last_flag = flag, flags++) - { + last_flag = flag, flags++) { flag = *flags; /* Original flags not in ASCII order. Abort. */ @@ -1836,7 +1832,7 @@ _new_maildir_filename (void *ctx, return NULL; /* Non-ASCII flag. Abort. */ - if (flag > sizeof(flag_map) - 1) + if (flag > sizeof (flag_map) - 1) return NULL; /* Repeated flag value. Abort. */ @@ -1870,7 +1866,7 @@ _new_maildir_filename (void *ctx, /* Messages in new/ without maildir info can be kept in new/ if no * flags have changed. */ dir = (char *) _filename_is_in_maildir (filename); - if (dir && STRNCMP_LITERAL (dir, "new/") == 0 && !*info && !flags_changed) + if (dir && STRNCMP_LITERAL (dir, "new/") == 0 && ! *info && ! flags_changed) return talloc_strdup (ctx, filename); filename_new = (char *) talloc_size (ctx, @@ -1885,8 +1881,7 @@ _new_maildir_filename (void *ctx, strcat (filename_new, ":2,"); s = filename_new + strlen (filename_new); - for (i = 0; i < sizeof (flag_map); i++) - { + for (i = 0; i < sizeof (flag_map); i++) { if (flag_map[i]) { *s = i; s++; @@ -1915,8 +1910,7 @@ notmuch_message_tags_to_maildir_flags (notmuch_message_t *message) for (filenames = notmuch_message_get_filenames (message); notmuch_filenames_valid (filenames); - notmuch_filenames_move_to_next (filenames)) - { + notmuch_filenames_move_to_next (filenames)) { filename = notmuch_filenames_get (filenames); if (! _filename_is_in_maildir (filename)) @@ -1978,8 +1972,7 @@ notmuch_message_remove_all_tags (notmuch_message_t *message) for (tags = notmuch_message_get_tags (message); notmuch_tags_valid (tags); - notmuch_tags_move_to_next (tags)) - { + notmuch_tags_move_to_next (tags)) { tag = notmuch_tags_get (tags); private_status = _notmuch_message_remove_term (message, "tag", tag); @@ -2057,8 +2050,8 @@ _notmuch_message_ensure_property_map (notmuch_message_t *message) const char *key; char *value; - value = strchr(node->string, '='); - if (!value) + value = strchr (node->string, '='); + if (! value) INTERNAL_ERROR ("malformed property term"); *value = '\0'; @@ -2105,7 +2098,7 @@ notmuch_message_reindex (notmuch_message_t *message, /* Save in case we need to delete message */ orig_thread_id = notmuch_message_get_thread_id (message); - if (!orig_thread_id) { + if (! orig_thread_id) { /* XXX TODO: make up new error return? */ INTERNAL_ERROR ("message without thread-id"); } @@ -2123,7 +2116,7 @@ notmuch_message_reindex (notmuch_message_t *message, private_status = _notmuch_message_remove_indexed_terms (message); if (private_status) { - ret = COERCE_STATUS(private_status, "error removing terms"); + ret = COERCE_STATUS (private_status, "error removing terms"); goto DONE; } @@ -2194,7 +2187,7 @@ notmuch_message_reindex (notmuch_message_t *message, _notmuch_message_sync (message); } - DONE: + DONE: if (message_file) _notmuch_message_file_close (message_file); diff --git a/lib/messages.c b/lib/messages.c index 7ddfaf26..eec0a162 100644 --- a/lib/messages.c +++ b/lib/messages.c @@ -117,7 +117,7 @@ _notmuch_messages_has_next (notmuch_messages_t *messages) return false; if (! messages->is_of_list_type) - INTERNAL_ERROR("_notmuch_messages_has_next not implemented for msets"); + INTERNAL_ERROR ("_notmuch_messages_has_next not implemented for msets"); return (messages->iterator->next != NULL); } @@ -183,7 +183,7 @@ notmuch_messages_collect_tags (notmuch_messages_t *messages) keys = g_hash_table_get_keys (htable); for (l = keys; l; l = l->next) { - _notmuch_string_list_append (tags, (char *)l->data); + _notmuch_string_list_append (tags, (char *) l->data); } g_list_free (keys); diff --git a/lib/notmuch-private.h b/lib/notmuch-private.h index 6fc5b366..9bdb68ab 100644 --- a/lib/notmuch-private.h +++ b/lib/notmuch-private.h @@ -60,7 +60,7 @@ NOTMUCH_BEGIN_DECLS # define DEBUG_QUERY 1 #endif -#define COMPILE_TIME_ASSERT(pred) ((void)sizeof(char[1 - 2*!(pred)])) +#define COMPILE_TIME_ASSERT(pred) ((void) sizeof (char[1 - 2 * ! (pred)])) #define STRNCMP_LITERAL(var, literal) \ strncmp ((var), (literal), sizeof (literal) - 1) @@ -69,11 +69,11 @@ NOTMUCH_BEGIN_DECLS #define _NOTMUCH_VALID_BIT(bit) \ ((bit) >= 0 && ((unsigned long) bit) < CHAR_BIT * sizeof (unsigned long long)) #define NOTMUCH_TEST_BIT(val, bit) \ - (_NOTMUCH_VALID_BIT(bit) ? !!((val) & (1ull << (bit))) : 0) + (_NOTMUCH_VALID_BIT (bit) ? ! ! ((val) & (1ull << (bit))) : 0) #define NOTMUCH_SET_BIT(valp, bit) \ - (_NOTMUCH_VALID_BIT(bit) ? (*(valp) |= (1ull << (bit))) : *(valp)) + (_NOTMUCH_VALID_BIT (bit) ? (*(valp) |= (1ull << (bit))) : *(valp)) #define NOTMUCH_CLEAR_BIT(valp, bit) \ - (_NOTMUCH_VALID_BIT(bit) ? (*(valp) &= ~(1ull << (bit))) : *(valp)) + (_NOTMUCH_VALID_BIT (bit) ? (*(valp) &= ~(1ull << (bit))) : *(valp)) #define unused(x) x __attribute__ ((unused)) @@ -83,12 +83,12 @@ NOTMUCH_BEGIN_DECLS /* these macros gain us a few percent of speed on gcc */ #if (__GNUC__ >= 3) /* the strange !! is to ensure that __builtin_expect() takes either 0 or 1 - as its first argument */ + * as its first argument */ #ifndef likely -#define likely(x) __builtin_expect(!!(x), 1) +#define likely(x) __builtin_expect (! ! (x), 1) #endif #ifndef unlikely -#define unlikely(x) __builtin_expect(!!(x), 0) +#define unlikely(x) __builtin_expect (! ! (x), 0) #endif #else #ifndef likely @@ -124,17 +124,17 @@ typedef enum { typedef enum _notmuch_private_status { /* First, copy all the public status values. */ - NOTMUCH_PRIVATE_STATUS_SUCCESS = NOTMUCH_STATUS_SUCCESS, - NOTMUCH_PRIVATE_STATUS_OUT_OF_MEMORY = NOTMUCH_STATUS_OUT_OF_MEMORY, - NOTMUCH_PRIVATE_STATUS_READ_ONLY_DATABASE = NOTMUCH_STATUS_READ_ONLY_DATABASE, - NOTMUCH_PRIVATE_STATUS_XAPIAN_EXCEPTION = NOTMUCH_STATUS_XAPIAN_EXCEPTION, - NOTMUCH_PRIVATE_STATUS_FILE_NOT_EMAIL = NOTMUCH_STATUS_FILE_NOT_EMAIL, - NOTMUCH_PRIVATE_STATUS_NULL_POINTER = NOTMUCH_STATUS_NULL_POINTER, - NOTMUCH_PRIVATE_STATUS_TAG_TOO_LONG = NOTMUCH_STATUS_TAG_TOO_LONG, - NOTMUCH_PRIVATE_STATUS_UNBALANCED_FREEZE_THAW = NOTMUCH_STATUS_UNBALANCED_FREEZE_THAW, + NOTMUCH_PRIVATE_STATUS_SUCCESS = NOTMUCH_STATUS_SUCCESS, + NOTMUCH_PRIVATE_STATUS_OUT_OF_MEMORY = NOTMUCH_STATUS_OUT_OF_MEMORY, + NOTMUCH_PRIVATE_STATUS_READ_ONLY_DATABASE = NOTMUCH_STATUS_READ_ONLY_DATABASE, + NOTMUCH_PRIVATE_STATUS_XAPIAN_EXCEPTION = NOTMUCH_STATUS_XAPIAN_EXCEPTION, + NOTMUCH_PRIVATE_STATUS_FILE_NOT_EMAIL = NOTMUCH_STATUS_FILE_NOT_EMAIL, + NOTMUCH_PRIVATE_STATUS_NULL_POINTER = NOTMUCH_STATUS_NULL_POINTER, + NOTMUCH_PRIVATE_STATUS_TAG_TOO_LONG = NOTMUCH_STATUS_TAG_TOO_LONG, + NOTMUCH_PRIVATE_STATUS_UNBALANCED_FREEZE_THAW = NOTMUCH_STATUS_UNBALANCED_FREEZE_THAW, /* Then add our own private values. */ - NOTMUCH_PRIVATE_STATUS_TERM_TOO_LONG = NOTMUCH_STATUS_LAST_STATUS, + NOTMUCH_PRIVATE_STATUS_TERM_TOO_LONG = NOTMUCH_STATUS_LAST_STATUS, NOTMUCH_PRIVATE_STATUS_NO_DOCUMENT_FOUND, NOTMUCH_PRIVATE_STATUS_BAD_PREFIX, @@ -150,14 +150,14 @@ typedef enum _notmuch_private_status { * Note that the function _internal_error does not return. Evaluating * to NOTMUCH_STATUS_SUCCESS is done purely to appease the compiler. */ -#define COERCE_STATUS(private_status, format, ...) \ - ((private_status >= (notmuch_private_status_t) NOTMUCH_STATUS_LAST_STATUS)\ - ? \ - _internal_error (format " (%s).\n", \ - ##__VA_ARGS__, \ - __location__), \ - (notmuch_status_t) NOTMUCH_PRIVATE_STATUS_SUCCESS \ - : \ +#define COERCE_STATUS(private_status, format, ...) \ + ((private_status >= (notmuch_private_status_t) NOTMUCH_STATUS_LAST_STATUS) \ + ? \ + _internal_error (format " (%s).\n", \ + ##__VA_ARGS__, \ + __location__), \ + (notmuch_status_t) NOTMUCH_PRIVATE_STATUS_SUCCESS \ + : \ (notmuch_status_t) private_status) /* Flags shared by various lookup functions. */ @@ -167,7 +167,7 @@ typedef enum _notmuch_find_flags { NOTMUCH_FIND_LOOKUP = 0, /* If set, create the necessary document (or documents) if they * are missing. Requires a read/write database. */ - NOTMUCH_FIND_CREATE = 1<<0, + NOTMUCH_FIND_CREATE = 1 << 0, } notmuch_find_flags_t; typedef struct _notmuch_doc_id_set notmuch_doc_id_set_t; @@ -185,7 +185,7 @@ _find_prefix (const char *name); /* Lookup a prefix value by name, including possibly user defined prefixes */ const char * -_notmuch_database_prefix (notmuch_database_t *notmuch, const char *name); +_notmuch_database_prefix (notmuch_database_t *notmuch, const char *name); char * _notmuch_message_id_compressed (void *ctx, const char *message_id); @@ -436,7 +436,7 @@ _notmuch_message_file_get_mime_message (notmuch_message_file_t *message, */ const char * _notmuch_message_file_get_header (notmuch_message_file_t *message, - const char *header); + const char *header); notmuch_status_t _notmuch_message_file_get_headers (notmuch_message_file_t *message_file, @@ -568,7 +568,7 @@ void _notmuch_message_remove_unprefixed_terms (notmuch_message_t *message); const char * -_notmuch_message_get_thread_id_only(notmuch_message_t *message); +_notmuch_message_get_thread_id_only (notmuch_message_t *message); size_t _notmuch_message_get_thread_depth (notmuch_message_t *message); @@ -621,10 +621,10 @@ void _notmuch_string_list_sort (notmuch_string_list_t *list); const notmuch_string_list_t * -_notmuch_message_get_references(notmuch_message_t *message); +_notmuch_message_get_references (notmuch_message_t *message); /* string-map.c */ -typedef struct _notmuch_string_map notmuch_string_map_t; +typedef struct _notmuch_string_map notmuch_string_map_t; typedef struct _notmuch_string_map_iterator notmuch_string_map_iterator_t; notmuch_string_map_t * _notmuch_string_map_create (const void *ctx); @@ -704,7 +704,7 @@ NOTMUCH_END_DECLS * template function for this to maintain type safety, and redefine * talloc_steal to use it. */ -#if !(__GNUC__ >= 3) +#if ! (__GNUC__ >= 3) template T * _notmuch_talloc_steal (const void *new_ctx, const T *ptr) { diff --git a/lib/notmuch.h b/lib/notmuch.h index 24708f3c..34666b88 100644 --- a/lib/notmuch.h +++ b/lib/notmuch.h @@ -57,18 +57,18 @@ NOTMUCH_BEGIN_DECLS * The library version number. This must agree with the soname * version in Makefile.local. */ -#define LIBNOTMUCH_MAJOR_VERSION 5 -#define LIBNOTMUCH_MINOR_VERSION 2 -#define LIBNOTMUCH_MICRO_VERSION 0 +#define LIBNOTMUCH_MAJOR_VERSION 5 +#define LIBNOTMUCH_MINOR_VERSION 2 +#define LIBNOTMUCH_MICRO_VERSION 0 #if defined (__clang_major__) && __clang_major__ >= 3 \ || defined (__GNUC__) && __GNUC__ >= 5 \ || defined (__GNUC__) && __GNUC__ == 4 && __GNUC_MINOR__ >= 5 -#define NOTMUCH_DEPRECATED(major,minor) \ +#define NOTMUCH_DEPRECATED(major, minor) \ __attribute__ ((deprecated ("function deprecated as of libnotmuch " #major "." #minor))) #else -#define NOTMUCH_DEPRECATED(major,minor) __attribute__ ((deprecated)) +#define NOTMUCH_DEPRECATED(major, minor) __attribute__ ((deprecated)) #endif @@ -95,8 +95,8 @@ NOTMUCH_BEGIN_DECLS * #endif * @endcode */ -#define LIBNOTMUCH_CHECK_VERSION(major, minor, micro) \ - (LIBNOTMUCH_MAJOR_VERSION > (major) || \ +#define LIBNOTMUCH_CHECK_VERSION(major, minor, micro) \ + (LIBNOTMUCH_MAJOR_VERSION > (major) || \ (LIBNOTMUCH_MAJOR_VERSION == (major) && LIBNOTMUCH_MINOR_VERSION > (minor)) || \ (LIBNOTMUCH_MAJOR_VERSION == (major) && LIBNOTMUCH_MINOR_VERSION == (minor) && \ LIBNOTMUCH_MICRO_VERSION >= (micro))) @@ -405,8 +405,8 @@ typedef void (*notmuch_compact_status_cb_t)(const char *message, void *closure); * 'closure' is passed verbatim to any callback invoked. */ notmuch_status_t -notmuch_database_compact (const char* path, - const char* backup_path, +notmuch_database_compact (const char *path, + const char *backup_path, notmuch_compact_status_cb_t status_cb, void *closure); @@ -467,8 +467,8 @@ notmuch_database_needs_upgrade (notmuch_database_t *database); */ notmuch_status_t notmuch_database_upgrade (notmuch_database_t *database, - void (*progress_notify) (void *closure, - double progress), + void (*progress_notify)(void *closure, + double progress), void *closure); /** @@ -525,7 +525,7 @@ notmuch_database_end_atomic (notmuch_database_t *notmuch); */ unsigned long notmuch_database_get_revision (notmuch_database_t *notmuch, - const char **uuid); + const char **uuid); /** * Retrieve a directory object from the database for 'path'. @@ -551,7 +551,7 @@ notmuch_database_get_revision (notmuch_database_t *notmuch, * directory not retrieved. * * NOTMUCH_STATUS_UPGRADE_REQUIRED: The caller must upgrade the - * database to use this function. + * database to use this function. */ notmuch_status_t notmuch_database_get_directory (notmuch_database_t *database, @@ -614,7 +614,7 @@ notmuch_database_get_directory (notmuch_database_t *database, * mode so no message can be added. * * NOTMUCH_STATUS_UPGRADE_REQUIRED: The caller must upgrade the - * database to use this function. + * database to use this function. * * @since libnotmuch 5.1 (notmuch 0.26) */ @@ -632,7 +632,7 @@ notmuch_database_index_file (notmuch_database_t *database, * use notmuch_database_index_file instead. * */ -NOTMUCH_DEPRECATED(5,1) +NOTMUCH_DEPRECATED (5, 1) notmuch_status_t notmuch_database_add_message (notmuch_database_t *database, const char *filename, @@ -664,7 +664,7 @@ notmuch_database_add_message (notmuch_database_t *database, * mode so no message can be removed. * * NOTMUCH_STATUS_UPGRADE_REQUIRED: The caller must upgrade the - * database to use this function. + * database to use this function. */ notmuch_status_t notmuch_database_remove_message (notmuch_database_t *database, @@ -722,7 +722,7 @@ notmuch_database_find_message (notmuch_database_t *database, * NOTMUCH_STATUS_XAPIAN_EXCEPTION: A Xapian exception occurred * * NOTMUCH_STATUS_UPGRADE_REQUIRED: The caller must upgrade the - * database to use this function. + * database to use this function. */ notmuch_status_t notmuch_database_find_message_by_filename (notmuch_database_t *notmuch, @@ -930,7 +930,7 @@ notmuch_query_search_threads (notmuch_query_t *query, * use notmuch_query_search_threads instead. * */ -NOTMUCH_DEPRECATED(5,0) +NOTMUCH_DEPRECATED (5, 0) notmuch_status_t notmuch_query_search_threads_st (notmuch_query_t *query, notmuch_threads_t **out); @@ -986,7 +986,7 @@ notmuch_query_search_messages (notmuch_query_t *query, * */ -NOTMUCH_DEPRECATED(5,0) +NOTMUCH_DEPRECATED (5, 0) notmuch_status_t notmuch_query_search_messages_st (notmuch_query_t *query, notmuch_messages_t **out); @@ -1082,7 +1082,7 @@ notmuch_query_count_messages (notmuch_query_t *query, unsigned int *count); * @deprecated Deprecated since libnotmuch 5.0 (notmuch 0.25). Please * use notmuch_query_count_messages instead. */ -NOTMUCH_DEPRECATED(5,0) +NOTMUCH_DEPRECATED (5, 0) notmuch_status_t notmuch_query_count_messages_st (notmuch_query_t *query, unsigned int *count); @@ -1100,7 +1100,7 @@ notmuch_query_count_messages_st (notmuch_query_t *query, unsigned int *count); * * NOTMUCH_STATUS_OUT_OF_MEMORY: Memory allocation failed. The value * of *count is not defined - + * * NOTMUCH_STATUS_SUCCESS: query completed successfully. * * NOTMUCH_STATUS_XAPIAN_EXCEPTION: a Xapian exception occurred. The @@ -1117,7 +1117,7 @@ notmuch_query_count_threads (notmuch_query_t *query, unsigned *count); * @deprecated Deprecated as of libnotmuch 5.0 (notmuch 0.25). Please * use notmuch_query_count_threads_st instead. */ -NOTMUCH_DEPRECATED(5,0) +NOTMUCH_DEPRECATED (5, 0) notmuch_status_t notmuch_query_count_threads_st (notmuch_query_t *query, unsigned *count); @@ -1499,7 +1499,7 @@ notmuch_message_set_flag (notmuch_message_t *message, * "date". */ time_t -notmuch_message_get_date (notmuch_message_t *message); +notmuch_message_get_date (notmuch_message_t *message); /** * Get the value of the specified header from 'message' as a UTF-8 string. diff --git a/lib/parse-time-vrp.cc b/lib/parse-time-vrp.cc index dd691494..45db9597 100644 --- a/lib/parse-time-vrp.cc +++ b/lib/parse-time-vrp.cc @@ -45,14 +45,14 @@ ParseTimeValueRangeProcessor::operator() (std::string &begin, std::string &end) if (time (&now) == (time_t) -1) return Xapian::BAD_VALUENO; - if (!begin.empty ()) { + if (! begin.empty ()) { if (parse_time_string (begin.c_str (), &t, &now, PARSE_TIME_ROUND_DOWN)) return Xapian::BAD_VALUENO; begin.assign (Xapian::sortable_serialise ((double) t)); } - if (!end.empty ()) { + if (! end.empty ()) { if (end == "!" && ! b.empty ()) end = b; @@ -67,12 +67,14 @@ ParseTimeValueRangeProcessor::operator() (std::string &begin, std::string &end) #if HAVE_XAPIAN_FIELD_PROCESSOR /* XXX TODO: is throwing an exception the right thing to do here? */ -Xapian::Query DateFieldProcessor::operator()(const std::string & str) { +Xapian::Query +DateFieldProcessor::operator() (const std::string & str) +{ time_t from, to, now; /* Use the same 'now' for begin and end. */ if (time (&now) == (time_t) -1) - throw Xapian::QueryParserError("Unable to get current time"); + throw Xapian::QueryParserError ("Unable to get current time"); if (parse_time_string (str.c_str (), &from, &now, PARSE_TIME_ROUND_DOWN)) throw Xapian::QueryParserError ("Didn't understand date specification '" + str + "'"); @@ -80,8 +82,8 @@ Xapian::Query DateFieldProcessor::operator()(const std::string & str) { if (parse_time_string (str.c_str (), &to, &now, PARSE_TIME_ROUND_UP_INCLUSIVE)) throw Xapian::QueryParserError ("Didn't understand date specification '" + str + "'"); - return Xapian::Query(Xapian::Query::OP_AND, - Xapian::Query(Xapian::Query::OP_VALUE_GE, 0, Xapian::sortable_serialise ((double) from)), - Xapian::Query(Xapian::Query::OP_VALUE_LE, 0, Xapian::sortable_serialise ((double) to))); + return Xapian::Query (Xapian::Query::OP_AND, + Xapian::Query (Xapian::Query::OP_VALUE_GE, 0, Xapian::sortable_serialise ((double) from)), + Xapian::Query (Xapian::Query::OP_VALUE_LE, 0, Xapian::sortable_serialise ((double) to))); } #endif diff --git a/lib/parse-time-vrp.h b/lib/parse-time-vrp.h index c024dba2..e6138d05 100644 --- a/lib/parse-time-vrp.h +++ b/lib/parse-time-vrp.h @@ -32,14 +32,16 @@ protected: public: ParseTimeValueRangeProcessor (Xapian::valueno slot_) - : valno(slot_) { } + : valno (slot_) + { + } Xapian::valueno operator() (std::string &begin, std::string &end); }; #if HAVE_XAPIAN_FIELD_PROCESSOR class DateFieldProcessor : public Xapian::FieldProcessor { - Xapian::Query operator()(const std::string & str); + Xapian::Query operator() (const std::string & str); }; #endif #endif /* NOTMUCH_PARSE_TIME_VRP_H */ diff --git a/lib/query-fp.h b/lib/query-fp.h index d6e4b313..8a8bde62 100644 --- a/lib/query-fp.h +++ b/lib/query-fp.h @@ -28,15 +28,17 @@ #if HAVE_XAPIAN_FIELD_PROCESSOR class QueryFieldProcessor : public Xapian::FieldProcessor { - protected: +protected: Xapian::QueryParser &parser; notmuch_database_t *notmuch; - public: +public: QueryFieldProcessor (Xapian::QueryParser &parser_, notmuch_database_t *notmuch_) - : parser(parser_), notmuch(notmuch_) { }; + : parser (parser_), notmuch (notmuch_) + { + }; - Xapian::Query operator()(const std::string & str); + Xapian::Query operator() (const std::string & str); }; #endif #endif /* NOTMUCH_QUERY_FP_H */ diff --git a/lib/query.cc b/lib/query.cc index 7fdf992d..792aba21 100644 --- a/lib/query.cc +++ b/lib/query.cc @@ -71,12 +71,14 @@ static bool _debug_query (void) { char *env = getenv ("NOTMUCH_DEBUG_QUERY"); + return (env && strcmp (env, "") != 0); } /* Explicit destructor call for placement new */ static int -_notmuch_query_destructor (notmuch_query_t *query) { +_notmuch_query_destructor (notmuch_query_t *query) +{ query->xapian_query.~Query(); query->terms.~set(); return 0; @@ -123,12 +125,12 @@ _notmuch_query_ensure_parsed (notmuch_query_t *query) try { query->xapian_query = query->notmuch->query_parser-> - parse_query (query->query_string, NOTMUCH_QUERY_PARSER_FLAGS); + parse_query (query->query_string, NOTMUCH_QUERY_PARSER_FLAGS); - /* Xapian doesn't support skip_to on terms from a query since - * they are unordered, so cache a copy of all terms in - * something searchable. - */ + /* Xapian doesn't support skip_to on terms from a query since + * they are unordered, so cache a copy of all terms in + * something searchable. + */ for (Xapian::TermIterator t = query->xapian_query.get_terms_begin (); t != query->xapian_query.get_terms_end (); ++t) @@ -137,7 +139,7 @@ _notmuch_query_ensure_parsed (notmuch_query_t *query) query->parsed = true; } catch (const Xapian::Error &error) { - if (!query->notmuch->exception_reported) { + if (! query->notmuch->exception_reported) { _notmuch_database_log (query->notmuch, "A Xapian exception occurred parsing query: %s\n", error.get_msg ().c_str ()); @@ -188,7 +190,7 @@ notmuch_query_add_tag_exclude (notmuch_query_t *query, const char *tag) return status; term = talloc_asprintf (query, "%s%s", _find_prefix ("tag"), tag); - if (query->terms.count(term) != 0) + if (query->terms.count (term) != 0) return NOTMUCH_STATUS_IGNORED; _notmuch_string_list_append (query->exclude_terms, term); @@ -236,7 +238,7 @@ notmuch_query_search_messages_st (notmuch_query_t *query, notmuch_status_t notmuch_query_search_messages (notmuch_query_t *query, - notmuch_messages_t **out) + notmuch_messages_t **out) { return _notmuch_query_search_documents (query, "mail", out); } @@ -278,8 +280,7 @@ _notmuch_query_search_documents (notmuch_query_t *query, Xapian::MSetIterator iterator; if (strcmp (query_string, "") == 0 || - strcmp (query_string, "*") == 0) - { + strcmp (query_string, "*") == 0) { final_query = mail_query; } else { final_query = Xapian::Query (Xapian::Query::OP_AND, @@ -291,15 +292,14 @@ _notmuch_query_search_documents (notmuch_query_t *query, exclude_query = _notmuch_exclude_tags (query); if (query->omit_excluded == NOTMUCH_EXCLUDE_TRUE || - query->omit_excluded == NOTMUCH_EXCLUDE_ALL) - { + query->omit_excluded == NOTMUCH_EXCLUDE_ALL) { final_query = Xapian::Query (Xapian::Query::OP_AND_NOT, final_query, exclude_query); } else { /* NOTMUCH_EXCLUDE_FLAG */ exclude_query = Xapian::Query (Xapian::Query::OP_AND, - exclude_query, final_query); + exclude_query, final_query); - enquire.set_weighting_scheme (Xapian::BoolWeight()); + enquire.set_weighting_scheme (Xapian::BoolWeight ()); enquire.set_query (exclude_query); mset = enquire.get_mset (0, notmuch->xapian_db->get_doccount ()); @@ -318,7 +318,7 @@ _notmuch_query_search_documents (notmuch_query_t *query, } - enquire.set_weighting_scheme (Xapian::BoolWeight()); + enquire.set_weighting_scheme (Xapian::BoolWeight ()); switch (query->sort) { case NOTMUCH_SORT_OLDEST_FIRST: @@ -354,10 +354,10 @@ _notmuch_query_search_documents (notmuch_query_t *query, } catch (const Xapian::Error &error) { _notmuch_database_log (notmuch, "A Xapian exception occurred performing query: %s\n", - error.get_msg().c_str()); + error.get_msg ().c_str ()); _notmuch_database_log_append (notmuch, - "Query string was: %s\n", - query->query_string); + "Query string was: %s\n", + query->query_string); notmuch->exception_reported = true; talloc_free (messages); @@ -408,8 +408,7 @@ _notmuch_mset_messages_get (notmuch_messages_t *messages) &status); if (message == NULL && - status == NOTMUCH_PRIVATE_STATUS_NO_DOCUMENT_FOUND) - { + status == NOTMUCH_PRIVATE_STATUS_NO_DOCUMENT_FOUND) { INTERNAL_ERROR ("a messages iterator contains a non-existent document ID.\n"); } @@ -439,8 +438,8 @@ _notmuch_doc_id_set_init (void *ctx, unsigned char *bitmap; for (unsigned int i = 0; i < arr->len; i++) - max = MAX(max, g_array_index (arr, unsigned int, i)); - bitmap = talloc_zero_array (ctx, unsigned char, DOCIDSET_WORD(max) + 1); + max = MAX (max, g_array_index (arr, unsigned int, i)); + bitmap = talloc_zero_array (ctx, unsigned char, DOCIDSET_WORD (max) + 1); if (bitmap == NULL) return false; @@ -450,7 +449,7 @@ _notmuch_doc_id_set_init (void *ctx, for (unsigned int i = 0; i < arr->len; i++) { unsigned int doc_id = g_array_index (arr, unsigned int, i); - bitmap[DOCIDSET_WORD(doc_id)] |= 1 << DOCIDSET_BIT(doc_id); + bitmap[DOCIDSET_WORD (doc_id)] |= 1 << DOCIDSET_BIT (doc_id); } return true; @@ -462,7 +461,7 @@ _notmuch_doc_id_set_contains (notmuch_doc_id_set_t *doc_ids, { if (doc_id >= doc_ids->bound) return false; - return doc_ids->bitmap[DOCIDSET_WORD(doc_id)] & (1 << DOCIDSET_BIT(doc_id)); + return doc_ids->bitmap[DOCIDSET_WORD (doc_id)] & (1 << DOCIDSET_BIT (doc_id)); } void @@ -470,7 +469,7 @@ _notmuch_doc_id_set_remove (notmuch_doc_id_set_t *doc_ids, unsigned int doc_id) { if (doc_id < doc_ids->bound) - doc_ids->bitmap[DOCIDSET_WORD(doc_id)] &= ~(1 << DOCIDSET_BIT(doc_id)); + doc_ids->bitmap[DOCIDSET_WORD (doc_id)] &= ~(1 << DOCIDSET_BIT (doc_id)); } /* Glib objects force use to use a talloc destructor as well, (but not @@ -489,7 +488,7 @@ _notmuch_threads_destructor (notmuch_threads_t *threads) notmuch_status_t notmuch_query_search_threads_st (notmuch_query_t *query, notmuch_threads_t **out) { - return notmuch_query_search_threads(query, out); + return notmuch_query_search_threads (query, out); } notmuch_status_t @@ -624,8 +623,7 @@ _notmuch_query_count_documents (notmuch_query_t *query, const char *type, unsign Xapian::MSet mset; if (strcmp (query_string, "") == 0 || - strcmp (query_string, "*") == 0) - { + strcmp (query_string, "*") == 0) { final_query = mail_query; } else { final_query = Xapian::Query (Xapian::Query::OP_AND, @@ -635,10 +633,10 @@ _notmuch_query_count_documents (notmuch_query_t *query, const char *type, unsign exclude_query = _notmuch_exclude_tags (query); final_query = Xapian::Query (Xapian::Query::OP_AND_NOT, - final_query, exclude_query); + final_query, exclude_query); - enquire.set_weighting_scheme(Xapian::BoolWeight()); - enquire.set_docid_order(Xapian::Enquire::ASCENDING); + enquire.set_weighting_scheme (Xapian::BoolWeight ()); + enquire.set_docid_order (Xapian::Enquire::ASCENDING); if (_debug_query ()) { fprintf (stderr, "Exclude query is:\n%s\n", @@ -657,12 +655,12 @@ _notmuch_query_count_documents (notmuch_query_t *query, const char *type, unsign mset = enquire.get_mset (0, 1, notmuch->xapian_db->get_doccount ()); - count = mset.get_matches_estimated(); + count = mset.get_matches_estimated (); } catch (const Xapian::Error &error) { _notmuch_database_log (notmuch, "A Xapian exception occurred performing query: %s\n", - error.get_msg().c_str()); + error.get_msg ().c_str ()); _notmuch_database_log_append (notmuch, "Query string was: %s\n", query->query_string); diff --git a/lib/regexp-fields.cc b/lib/regexp-fields.cc index 5d4cf80a..198eb32f 100644 --- a/lib/regexp-fields.cc +++ b/lib/regexp-fields.cc @@ -124,12 +124,13 @@ bool RegexpPostingSource::check (Xapian::docid did, unused (double min_wt)) { started_ = true; - if (!it_.check (did) || at_end ()) + if (! it_.check (did) || at_end ()) return false; return (regexec (®exp_, (*it_).c_str (), 0, NULL, 0) == 0); } -static inline Xapian::valueno _find_slot (std::string prefix) +static inline Xapian::valueno +_find_slot (std::string prefix) { if (prefix == "from") return NOTMUCH_VALUE_FROM; @@ -145,11 +146,11 @@ RegexpFieldProcessor::RegexpFieldProcessor (std::string prefix, notmuch_field_flag_t options_, Xapian::QueryParser &parser_, notmuch_database_t *notmuch_) - : slot (_find_slot (prefix)), - term_prefix (_find_prefix (prefix.c_str ())), - options (options_), - parser (parser_), - notmuch (notmuch_) + : slot (_find_slot (prefix)), + term_prefix (_find_prefix (prefix.c_str ())), + options (options_), + parser (parser_), + notmuch (notmuch_) { }; @@ -158,17 +159,17 @@ RegexpFieldProcessor::operator() (const std::string & str) { if (str.empty ()) { if (options & NOTMUCH_FIELD_PROBABILISTIC) { - return Xapian::Query(Xapian::Query::OP_AND_NOT, - Xapian::Query::MatchAll, - Xapian::Query (Xapian::Query::OP_WILDCARD, term_prefix)); + return Xapian::Query (Xapian::Query::OP_AND_NOT, + Xapian::Query::MatchAll, + Xapian::Query (Xapian::Query::OP_WILDCARD, term_prefix)); } else { return Xapian::Query (term_prefix); } } if (str.at (0) == '/') { - if (str.length() > 1 && str.at (str.size () - 1) == '/'){ - std::string regexp_str = str.substr(1,str.size () - 2); + if (str.length () > 1 && str.at (str.size () - 1) == '/') { + std::string regexp_str = str.substr (1, str.size () - 2); if (slot != Xapian::BAD_VALUENO) { RegexpPostingSource *postings = new RegexpPostingSource (slot, regexp_str); return Xapian::Query (postings->release ()); @@ -176,14 +177,14 @@ RegexpFieldProcessor::operator() (const std::string & str) std::vector terms; regex_t regexp; - compile_regex(regexp, regexp_str.c_str ()); + compile_regex (regexp, regexp_str.c_str ()); for (Xapian::TermIterator it = notmuch->xapian_db->allterms_begin (term_prefix); it != notmuch->xapian_db->allterms_end (); ++it) { - if (regexec (®exp, (*it).c_str () + term_prefix.size(), + if (regexec (®exp, (*it).c_str () + term_prefix.size (), 0, NULL, 0) == 0) - terms.push_back(*it); + terms.push_back (*it); } - return Xapian::Query (Xapian::Query::OP_OR, terms.begin(), terms.end()); + return Xapian::Query (Xapian::Query::OP_OR, terms.begin (), terms.end ()); } } else { throw Xapian::QueryParserError ("unmatched regex delimiter in '" + str + "'"); diff --git a/lib/regexp-fields.h b/lib/regexp-fields.h index d5f93445..97778a1d 100644 --- a/lib/regexp-fields.h +++ b/lib/regexp-fields.h @@ -35,7 +35,7 @@ */ class RegexpPostingSource : public Xapian::PostingSource { - protected: +protected: const Xapian::valueno slot_; regex_t regexp_; Xapian::Database db_; @@ -46,7 +46,7 @@ class RegexpPostingSource : public Xapian::PostingSource RegexpPostingSource (const RegexpPostingSource &); RegexpPostingSource &operator= (const RegexpPostingSource &); - public: +public: RegexpPostingSource (Xapian::valueno slot, const std::string ®exp); ~RegexpPostingSource (); void init (const Xapian::Database &db); @@ -62,20 +62,22 @@ class RegexpPostingSource : public Xapian::PostingSource class RegexpFieldProcessor : public Xapian::FieldProcessor { - protected: +protected: Xapian::valueno slot; std::string term_prefix; notmuch_field_flag_t options; Xapian::QueryParser &parser; notmuch_database_t *notmuch; - public: +public: RegexpFieldProcessor (std::string prefix, notmuch_field_flag_t options, Xapian::QueryParser &parser_, notmuch_database_t *notmuch_); - ~RegexpFieldProcessor () { }; + ~RegexpFieldProcessor () + { + }; - Xapian::Query operator()(const std::string & str); + Xapian::Query operator() (const std::string & str); }; #endif #endif /* NOTMUCH_REGEXP_FIELDS_H */ diff --git a/lib/sha1.c b/lib/sha1.c index cb55b49a..d1a76ee6 100644 --- a/lib/sha1.c +++ b/lib/sha1.c @@ -55,6 +55,7 @@ char * _notmuch_sha1_of_file (const char *filename) { FILE *file; + #define BLOCK_SIZE 4096 unsigned char block[BLOCK_SIZE]; size_t bytes_read; diff --git a/lib/string-list.c b/lib/string-list.c index 9c3ae7ef..f3dac675 100644 --- a/lib/string-list.c +++ b/lib/string-list.c @@ -67,8 +67,8 @@ _notmuch_string_list_append (notmuch_string_list_t *list, static int cmpnode (const void *pa, const void *pb) { - notmuch_string_node_t *a = *(notmuch_string_node_t * const *)pa; - notmuch_string_node_t *b = *(notmuch_string_node_t * const *)pb; + notmuch_string_node_t *a = *(notmuch_string_node_t *const *) pa; + notmuch_string_node_t *b = *(notmuch_string_node_t *const *) pb; return strcmp (a->string, b->string); } @@ -92,7 +92,7 @@ _notmuch_string_list_sort (notmuch_string_list_t *list) qsort (nodes, list->length, sizeof (*nodes), cmpnode); for (i = 0; i < list->length - 1; ++i) - nodes[i]->next = nodes[i+1]; + nodes[i]->next = nodes[i + 1]; nodes[i]->next = NULL; list->head = nodes[0]; list->tail = &nodes[i]->next; diff --git a/lib/thread-fp.h b/lib/thread-fp.h index 47c066c1..de837d3e 100644 --- a/lib/thread-fp.h +++ b/lib/thread-fp.h @@ -28,15 +28,17 @@ #if HAVE_XAPIAN_FIELD_PROCESSOR class ThreadFieldProcessor : public Xapian::FieldProcessor { - protected: +protected: Xapian::QueryParser &parser; notmuch_database_t *notmuch; - public: +public: ThreadFieldProcessor (Xapian::QueryParser &parser_, notmuch_database_t *notmuch_) - : parser(parser_), notmuch(notmuch_) { }; + : parser (parser_), notmuch (notmuch_) + { + }; - Xapian::Query operator()(const std::string & str); + Xapian::Query operator() (const std::string & str); }; #endif #endif /* NOTMUCH_THREAD_FP_H */ diff --git a/lib/thread.cc b/lib/thread.cc index fd0e1393..6073e45c 100644 --- a/lib/thread.cc +++ b/lib/thread.cc @@ -22,12 +22,12 @@ #include "database-private.h" #include -#include /* GHashTable */ +#include /* GHashTable */ #ifdef DEBUG_THREADING -#define THREAD_DEBUG(format, ...) fprintf(stderr, format " (%s).\n", ##__VA_ARGS__, __location__) +#define THREAD_DEBUG(format, ...) fprintf (stderr, format " (%s).\n", ##__VA_ARGS__, __location__) #else -#define THREAD_DEBUG(format, ...) do {} while (0) /* ignored */ +#define THREAD_DEBUG(format, ...) do {} while (0) /* ignored */ #endif struct _notmuch_thread { @@ -165,8 +165,8 @@ _resolve_thread_authors_string (notmuch_thread_t *thread) g_ptr_array_free (thread->matched_authors_array, true); thread->matched_authors_array = NULL; - if (!thread->authors) - thread->authors = talloc_strdup(thread, ""); + if (! thread->authors) + thread->authors = talloc_strdup (thread, ""); } /* clean up the ugly "Lastname, Firstname" format that some mail systems @@ -180,14 +180,14 @@ static char * _thread_cleanup_author (notmuch_thread_t *thread, const char *author, const char *from) { - char *clean_author,*test_author; + char *clean_author, *test_author; const char *comma; char *blank; - int fname,lname; + int fname, lname; if (author == NULL) return NULL; - clean_author = talloc_strdup(thread, author); + clean_author = talloc_strdup (thread, author); if (clean_author == NULL) return NULL; /* check if there's a comma in the name and that there's a @@ -196,34 +196,34 @@ _thread_cleanup_author (notmuch_thread_t *thread, * one character long ",\0"). * Otherwise just return the copy of the original author name that * we just made*/ - comma = strchr(author,','); - if (comma && strlen(comma) > 1) { + comma = strchr (author, ','); + if (comma && strlen (comma) > 1) { /* let's assemble what we think is the correct name */ lname = comma - author; /* Skip all the spaces after the comma */ - fname = strlen(author) - lname - 1; + fname = strlen (author) - lname - 1; comma += 1; while (*comma == ' ') { fname -= 1; comma += 1; } - strncpy(clean_author, comma, fname); + strncpy (clean_author, comma, fname); - *(clean_author+fname) = ' '; - strncpy(clean_author + fname + 1, author, lname); - *(clean_author+fname+1+lname) = '\0'; + *(clean_author + fname) = ' '; + strncpy (clean_author + fname + 1, author, lname); + *(clean_author + fname + 1 + lname) = '\0'; /* make a temporary copy and see if it matches the email */ - test_author = talloc_strdup(thread,clean_author); + test_author = talloc_strdup (thread, clean_author); - blank=strchr(test_author,' '); + blank = strchr (test_author, ' '); while (blank != NULL) { *blank = '.'; - blank=strchr(test_author,' '); + blank = strchr (test_author, ' '); } - if (strcasestr(from, test_author) == NULL) + if (strcasestr (from, test_author) == NULL) /* we didn't identify this as part of the email address - * so let's punt and return the original author */ + * so let's punt and return the original author */ strcpy (clean_author, author); } return clean_author; @@ -251,16 +251,14 @@ _thread_add_message (notmuch_thread_t *thread, if (omit_exclude != NOTMUCH_EXCLUDE_FALSE) { for (tags = notmuch_message_get_tags (message); notmuch_tags_valid (tags); - notmuch_tags_move_to_next (tags)) - { + notmuch_tags_move_to_next (tags)) { tag = notmuch_tags_get (tags); /* Is message excluded? */ for (notmuch_string_node_t *term = exclude_terms->head; term != NULL; - term = term->next) - { + term = term->next) { /* Check for an empty string, and then ignore initial 'K'. */ - if (*(term->string) && strcmp(tag, (term->string + 1)) == 0) { + if (*(term->string) && strcmp (tag, (term->string + 1)) == 0) { message_excluded = true; break; } @@ -309,8 +307,7 @@ _thread_add_message (notmuch_thread_t *thread, for (tags = notmuch_message_get_tags (message); notmuch_tags_valid (tags); - notmuch_tags_move_to_next (tags)) - { + notmuch_tags_move_to_next (tags)) { tag = notmuch_tags_get (tags); g_hash_table_insert (thread->tags, xstrdup (tag), NULL); } @@ -338,12 +335,12 @@ _thread_set_subject_from_message (notmuch_thread_t *thread, cleaned_subject = talloc_strndup (thread, subject + 4, - strlen(subject) - 4); + strlen (subject) - 4); } else { cleaned_subject = talloc_strdup (thread, subject); } - if (! EMPTY_STRING(cleaned_subject)) { + if (! EMPTY_STRING (cleaned_subject)) { if (thread->subject) talloc_free (thread->subject); @@ -373,17 +370,17 @@ _thread_add_matched_message (notmuch_thread_t *thread, if (date > thread->newest || ! thread->matched_messages) { thread->newest = date; - const char *cur_subject = notmuch_thread_get_subject(thread); - if (sort != NOTMUCH_SORT_OLDEST_FIRST || EMPTY_STRING(cur_subject)) + const char *cur_subject = notmuch_thread_get_subject (thread); + if (sort != NOTMUCH_SORT_OLDEST_FIRST || EMPTY_STRING (cur_subject)) _thread_set_subject_from_message (thread, message); } - if (!notmuch_message_get_flag (message, NOTMUCH_MESSAGE_FLAG_EXCLUDED)) + if (! notmuch_message_get_flag (message, NOTMUCH_MESSAGE_FLAG_EXCLUDED)) thread->matched_messages++; if (g_hash_table_lookup_extended (thread->message_hash, - notmuch_message_get_message_id (message), NULL, - (void **) &hashed_message)) { + notmuch_message_get_message_id (message), NULL, + (void **) &hashed_message)) { notmuch_message_set_flag (hashed_message, NOTMUCH_MESSAGE_FLAG_MATCH, 1); } @@ -392,15 +389,16 @@ _thread_add_matched_message (notmuch_thread_t *thread, } static bool -_parent_via_in_reply_to (notmuch_thread_t *thread, notmuch_message_t *message) { +_parent_via_in_reply_to (notmuch_thread_t *thread, notmuch_message_t *message) +{ notmuch_message_t *parent; const char *in_reply_to; in_reply_to = _notmuch_message_get_in_reply_to (message); - THREAD_DEBUG("checking message = %s in_reply_to=%s\n", - notmuch_message_get_message_id (message), in_reply_to); + THREAD_DEBUG ("checking message = %s in_reply_to=%s\n", + notmuch_message_get_message_id (message), in_reply_to); - if (in_reply_to && (! EMPTY_STRING(in_reply_to)) && + if (in_reply_to && (! EMPTY_STRING (in_reply_to)) && g_hash_table_lookup_extended (thread->message_hash, in_reply_to, NULL, (void **) &parent)) { @@ -420,32 +418,32 @@ _parent_or_toplevel (notmuch_thread_t *thread, notmuch_message_t *message) const notmuch_string_list_t *references = _notmuch_message_get_references (message); - THREAD_DEBUG("trying to reparent via references: %s\n", - notmuch_message_get_message_id (message)); + THREAD_DEBUG ("trying to reparent via references: %s\n", + notmuch_message_get_message_id (message)); for (notmuch_string_node_t *ref_node = references->head; ref_node; ref_node = ref_node->next) { - THREAD_DEBUG("checking reference=%s\n", ref_node->string); + THREAD_DEBUG ("checking reference=%s\n", ref_node->string); if ((g_hash_table_lookup_extended (thread->message_hash, ref_node->string, NULL, (void **) &new_parent))) { size_t new_depth = _notmuch_message_get_thread_depth (new_parent); - THREAD_DEBUG("got depth %lu\n", new_depth); - if (new_depth > max_depth || !parent) { - THREAD_DEBUG("adding at depth %lu parent=%s\n", new_depth, ref_node->string); + THREAD_DEBUG ("got depth %lu\n", new_depth); + if (new_depth > max_depth || ! parent) { + THREAD_DEBUG ("adding at depth %lu parent=%s\n", new_depth, ref_node->string); max_depth = new_depth; parent = new_parent; } } } if (parent) { - THREAD_DEBUG("adding reply %s to parent=%s\n", - notmuch_message_get_message_id (message), - notmuch_message_get_message_id (parent)); + THREAD_DEBUG ("adding reply %s to parent=%s\n", + notmuch_message_get_message_id (message), + notmuch_message_get_message_id (parent)); _notmuch_message_add_reply (parent, message); } else { - THREAD_DEBUG("adding as toplevel %s\n", - notmuch_message_get_message_id (message)); + THREAD_DEBUG ("adding as toplevel %s\n", + notmuch_message_get_message_id (message)); _notmuch_message_list_add_message (thread->toplevel_list, message); } } @@ -479,22 +477,21 @@ _resolve_thread_relationships (notmuch_thread_t *thread) */ if (first_node) { message = first_node->message; - THREAD_DEBUG("checking first message %s\n", - notmuch_message_get_message_id (message)); + THREAD_DEBUG ("checking first message %s\n", + notmuch_message_get_message_id (message)); - if (_notmuch_message_list_empty (maybe_toplevel_list) || + if (_notmuch_message_list_empty (maybe_toplevel_list) || ! _parent_via_in_reply_to (thread, message)) { - THREAD_DEBUG("adding first message as toplevel = %s\n", - notmuch_message_get_message_id (message)); + THREAD_DEBUG ("adding first message as toplevel = %s\n", + notmuch_message_get_message_id (message)); _notmuch_message_list_add_message (maybe_toplevel_list, message); } } for (notmuch_messages_t *messages = _notmuch_messages_create (maybe_toplevel_list); notmuch_messages_valid (messages); - notmuch_messages_move_to_next (messages)) - { + notmuch_messages_move_to_next (messages)) { notmuch_message_t *message = notmuch_messages_get (messages); _notmuch_message_label_depths (message, 0); } @@ -616,8 +613,7 @@ _notmuch_thread_create (void *ctx, for (; notmuch_messages_valid (messages); - notmuch_messages_move_to_next (messages)) - { + notmuch_messages_move_to_next (messages)) { unsigned int doc_id; message = notmuch_messages_get (messages); From 11f7e52651d5f16bac0df00a8f51cb141df0675a Mon Sep 17 00:00:00 2001 From: Daniel Kahn Gillmor Date: Mon, 10 Jun 2019 04:44:39 +0300 Subject: [PATCH 019/427] emacs: add keywords to notmuch-emacs-mua.desktop Debian's lintian has an informational alert desktop-entry-lacks-keywords-entry, which recommends including Keywords= in a .desktop file. I dug around a bit in /usr/share/applications/*.desktop to make sure that we covered the range of keywords other e-mail applications are using. If anyone has other suggestions for keywords, they can add them to this list. Signed-off-by: Daniel Kahn Gillmor --- emacs/notmuch-emacs-mua.desktop | 1 + 1 file changed, 1 insertion(+) diff --git a/emacs/notmuch-emacs-mua.desktop b/emacs/notmuch-emacs-mua.desktop index 0d9af2a4..752a1d7b 100644 --- a/emacs/notmuch-emacs-mua.desktop +++ b/emacs/notmuch-emacs-mua.desktop @@ -8,3 +8,4 @@ Icon=emblem-mail Terminal=false Type=Application Categories=Network;Email; +Keywords=Mail;E-mail;Email; From 6544a2e3054ee96d8d4efccc51b4bc9e09e23d6c Mon Sep 17 00:00:00 2001 From: David Bremner Date: Wed, 26 Jun 2019 12:23:36 -0400 Subject: [PATCH 020/427] test: provide machinery to make and use test_shims These can be used e.g. to override return values for functions, in place of the existing scripting of gdb. This prepends to LD_PRELOAD rather than clobbering it, thanks to a suggestion from Tomi Ollila. Signed-off-by: Daniel Kahn Gillmor --- test/test-lib.sh | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/test/test-lib.sh b/test/test-lib.sh index 616cb674..7f8a3a4d 100644 --- a/test/test-lib.sh +++ b/test/test-lib.sh @@ -95,6 +95,8 @@ TEST_EMACSCLIENT=${TEST_EMACSCLIENT:-emacsclient} TEST_GDB=${TEST_GDB:-gdb} TEST_CC=${TEST_CC:-cc} TEST_CFLAGS=${TEST_CFLAGS:-"-g -O0"} +TEST_SHIM_CFLAGS=${TEST_SHIM_CFLAGS:-"-fpic -shared"} +TEST_SHIM_LDFLAGS=${TEST_SHIM_LDFLAGS:-"-ldl"} # Protect ourselves from common misconfiguration to export # CDPATH into the environment @@ -1056,6 +1058,20 @@ test_C () { notmuch_dir_sanitize OUTPUT.stdout OUTPUT.stderr > OUTPUT } +make_shim () { + base_name="$1" + test_file="${base_name}.c" + shim_file="${base_name}.so" + cat > ${test_file} + ${TEST_CC} ${TEST_CFLAGS} ${TEST_SHIM_CFLAGS} -I${NOTMUCH_SRCDIR}/test -I${NOTMUCH_SRCDIR}/lib -o ${shim_file} ${test_file} ${TEST_SHIM_LDFLAGS} +} + +notmuch_with_shim () { + base_name="$1" + shift + shim_file="${base_name}.so" + LD_PRELOAD=./${shim_file}${LD_PRELOAD:+:$LD_PRELOAD} notmuch-shared "$@" +} # Creates a script that counts how much time it is executed and calls # notmuch. $notmuch_counter_command is set to the path to the From 1959a95d2538daa2a802b74699ecb2b42d997a2d Mon Sep 17 00:00:00 2001 From: David Bremner Date: Wed, 26 Jun 2019 12:23:37 -0400 Subject: [PATCH 021/427] test: replace use of gdb with LD_PRELOAD shims in T070-insert.sh This removes the dependency of this test script on gdb, and considerably speeds up the running of the tests. Signed-off-by: Daniel Kahn Gillmor --- test/T070-insert.sh | 48 ++++++++++++++++----------------------------- 1 file changed, 17 insertions(+), 31 deletions(-) diff --git a/test/T070-insert.sh b/test/T070-insert.sh index 48165caa..c8161e1e 100755 --- a/test/T070-insert.sh +++ b/test/T070-insert.sh @@ -2,8 +2,6 @@ test_description='"notmuch insert"' . $(dirname "$0")/test-lib.sh || exit 1 -test_require_external_prereq gdb - # subtests about file permissions assume that we're working with umask # 022 by default. umask 022 @@ -246,19 +244,19 @@ test_expect_code 1 "notmuch insert $gen_msg_filename 2>&1" notmuch config set new.tags $OLDCONFIG # DUPLICATE_MESSAGE_ID is not tested here, because it should actually pass. - -for code in OUT_OF_MEMORY XAPIAN_EXCEPTION FILE_NOT_EMAIL \ - READ_ONLY_DATABASE UPGRADE_REQUIRED PATH_ERROR; do -cat < index-file-$code.gdb -set breakpoint pending on -set logging file index-file-$code.log -set logging on -break notmuch_database_index_file -commands -return NOTMUCH_STATUS_$code -continue -end -run +# pregenerate all of the test shims +for code in FILE_NOT_EMAIL READ_ONLY_DATABASE UPGRADE_REQUIRED PATH_ERROR OUT_OF_MEMORY XAPIAN_EXCEPTION; do + make_shim shim-$code < +#include +notmuch_status_t +notmuch_database_index_file (notmuch_database_t *notmuch, + const char *filename, + notmuch_indexopts_t *indexopts, + notmuch_message_t **message_ret) +{ + return NOTMUCH_STATUS_$code; +} EOF done @@ -266,30 +264,18 @@ gen_insert_msg for code in FILE_NOT_EMAIL READ_ONLY_DATABASE UPGRADE_REQUIRED PATH_ERROR; do test_begin_subtest "EXIT_FAILURE when index_file returns $code" - test_expect_code 1 \ - "${TEST_GDB} --batch-silent --return-child-result \ - -ex 'set args insert < $gen_msg_filename' \ - -x index-file-$code.gdb notmuch" + test_expect_code 1 "notmuch_with_shim shim-$code insert < \"$gen_msg_filename\"" test_begin_subtest "success exit with --keep when index_file returns $code" - test_expect_code 0 \ - "${TEST_GDB} --batch-silent --return-child-result \ - -ex 'set args insert --keep < $gen_msg_filename' \ - -x index-file-$code.gdb notmuch" + test_expect_code 0 "notmuch_with_shim shim-$code insert --keep < \"$gen_msg_filename\"" done for code in OUT_OF_MEMORY XAPIAN_EXCEPTION ; do test_begin_subtest "EX_TEMPFAIL when index_file returns $code" - test_expect_code 75 \ - "${TEST_GDB} --batch-silent --return-child-result \ - -ex 'set args insert < $gen_msg_filename' \ - -x index-file-$code.gdb notmuch" + test_expect_code 75 "notmuch_with_shim shim-$code insert < \"$gen_msg_filename\"" test_begin_subtest "success exit with --keep when index_file returns $code" - test_expect_code 0 \ - "${TEST_GDB} --batch-silent --return-child-result \ - -ex 'set args insert --keep < $gen_msg_filename' \ - -x index-file-$code.gdb notmuch" + test_expect_code 0 "notmuch_with_shim shim-$code insert --keep < \"$gen_msg_filename\"" done test_done From bdc87f0d3ec41dd4744a89d1d19b2186d0647a56 Mon Sep 17 00:00:00 2001 From: Daniel Kahn Gillmor Date: Tue, 25 Jun 2019 13:55:45 -0400 Subject: [PATCH 022/427] test: run uncrustify This is the result of running: $ uncrustify --replace --config ../devel/uncrustify.cfg *.cc *.c *.h in the test directory. Signed-off-by: Daniel Kahn Gillmor --- test/arg-test.c | 68 +++++++++++++++++++++-------------------- test/ghost-report.cc | 10 +++--- test/hex-xcode.c | 20 ++++++------ test/make-db-version.cc | 3 +- test/notmuch-test.h | 8 ++--- test/parse-time.c | 58 +++++++++++++++++------------------ test/random-corpus.c | 2 +- test/smtp-dummy.c | 2 +- test/symbol-test.cc | 5 +-- 9 files changed, 91 insertions(+), 85 deletions(-) diff --git a/test/arg-test.c b/test/arg-test.c index a218f967..44477730 100644 --- a/test/arg-test.c +++ b/test/arg-test.c @@ -2,27 +2,29 @@ #include "command-line-arguments.h" -int main(int argc, char **argv){ +int +main (int argc, char **argv) +{ - int opt_index=1; + 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; + 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, kwb_set = false, - kw_set = false, string_set = false, pos1_set = false, pos2_set = false; + kw_set = false, string_set = false, pos1_set = false, pos2_set = false; notmuch_opt_desc_t parent_options[] = { { .opt_flags = &fl_val, .name = "flag", .present = &fl_set, .keywords = - (notmuch_keyword_t []){ { "one", 1 << 0}, - { "two", 1 << 1 }, - { "three", 1 << 2 }, - { 0, 0 } } }, + (notmuch_keyword_t []){ { "one", 1 << 0 }, + { "two", 1 << 1 }, + { "three", 1 << 2 }, + { 0, 0 } } }, { .opt_int = &int_val, .name = "int", .present = &int_set }, { } }; @@ -30,16 +32,16 @@ int main(int argc, char **argv){ notmuch_opt_desc_t options[] = { { .opt_bool = &bool_val, .name = "boolean", .present = &bool_set }, { .opt_keyword = &kw_val, .name = "keyword", .present = &kw_set, .keywords = - (notmuch_keyword_t []){ { "zero", 0 }, - { "one", 1 }, - { "two", 2 }, - { 0, 0 } } }, + (notmuch_keyword_t []){ { "zero", 0 }, + { "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 } } }, + (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 }, @@ -47,38 +49,38 @@ int main(int argc, char **argv){ { } }; - opt_index = parse_arguments(argc, argv, options, 1); + opt_index = parse_arguments (argc, argv, options, 1); if (opt_index < 0) return 1; if (bool_set) - printf("boolean %d\n", bool_val); + printf ("boolean %d\n", bool_val); if (kw_set) - printf("keyword %d\n", kw_val); + printf ("keyword %d\n", kw_val); if (kwb_set) - printf("boolkeyword %d\n", kwb_val); + printf ("boolkeyword %d\n", kwb_val); if (fl_set) - printf("flags %d\n", fl_val); + printf ("flags %d\n", fl_val); if (int_set) - printf("int %d\n", int_val); + printf ("int %d\n", int_val); if (string_set) - printf("string %s\n", string_val); + printf ("string %s\n", string_val); if (pos1_set) - printf("positional arg 1 %s\n", pos_arg1); + printf ("positional arg 1 %s\n", pos_arg1); if (pos2_set) - printf("positional arg 2 %s\n", pos_arg2); + printf ("positional arg 2 %s\n", pos_arg2); - for ( ; opt_index < argc ; opt_index ++) { - printf("non parsed arg %d = %s\n", opt_index, argv[opt_index]); + for (; opt_index < argc; opt_index++) { + printf ("non parsed arg %d = %s\n", opt_index, argv[opt_index]); } return 0; diff --git a/test/ghost-report.cc b/test/ghost-report.cc index 3e1b07c6..fad9a71d 100644 --- a/test/ghost-report.cc +++ b/test/ghost-report.cc @@ -2,13 +2,15 @@ #include #include -int main(int argc, char **argv) { +int +main (int argc, char **argv) +{ if (argc < 2) { std::cerr << "usage: ghost-report xapian-dir" << std::endl; - exit(1); + exit (1); } - Xapian::Database db(argv[1]); - std::cout << db.get_termfreq("Tghost") << std::endl; + Xapian::Database db (argv[1]); + std::cout << db.get_termfreq ("Tghost") << std::endl; } diff --git a/test/hex-xcode.c b/test/hex-xcode.c index 33046e9a..2e70f737 100644 --- a/test/hex-xcode.c +++ b/test/hex-xcode.c @@ -26,13 +26,13 @@ xcode (void *ctx, enum direction dir, char *in, char **buf_p, size_t *size_p) if (dir == ENCODE) status = hex_encode (ctx, in, buf_p, size_p); else - if (inplace) { - status = hex_decode_inplace (in); - *buf_p = in; - *size_p = strlen(in); - } else { - status = hex_decode (ctx, in, buf_p, size_p); - } + if (inplace) { + status = hex_decode_inplace (in); + *buf_p = in; + *size_p = strlen (in); + } else { + status = hex_decode (ctx, in, buf_p, size_p); + } if (status == HEX_SUCCESS) fputs (*buf_p, stdout); @@ -49,9 +49,9 @@ main (int argc, char **argv) notmuch_opt_desc_t options[] = { { .opt_keyword = &dir, .name = "direction", .keywords = - (notmuch_keyword_t []){ { "encode", ENCODE }, - { "decode", DECODE }, - { 0, 0 } } }, + (notmuch_keyword_t []){ { "encode", ENCODE }, + { "decode", DECODE }, + { 0, 0 } } }, { .opt_bool = &omit_newline, .name = "omit-newline" }, { .opt_bool = &inplace, .name = "in-place" }, { } diff --git a/test/make-db-version.cc b/test/make-db-version.cc index fa80cac9..78feaf72 100644 --- a/test/make-db-version.cc +++ b/test/make-db-version.cc @@ -8,7 +8,8 @@ #include -int main(int argc, char **argv) +int +main (int argc, char **argv) { if (argc != 4) { fprintf (stderr, "Usage: %s mailpath version features\n", argv[0]); diff --git a/test/notmuch-test.h b/test/notmuch-test.h index 45d03d67..df852da9 100644 --- a/test/notmuch-test.h +++ b/test/notmuch-test.h @@ -4,13 +4,13 @@ #include inline static void -expect0(int line, notmuch_status_t ret) +expect0 (int line, notmuch_status_t ret) { - if (ret) { + if (ret) { fprintf (stderr, "line %d: %d\n", line, ret); exit (1); - } + } } -#define EXPECT0(v) expect0(__LINE__, v); +#define EXPECT0(v) expect0 (__LINE__, v); #endif diff --git a/test/parse-time.c b/test/parse-time.c index 694761cf..6aac89b9 100644 --- a/test/parse-time.c +++ b/test/parse-time.c @@ -30,16 +30,16 @@ #define ARRAY_SIZE(a) (sizeof (a) / sizeof (a[0])) static const char *parse_time_error_strings[] = { - [PARSE_TIME_OK] = "OK", - [PARSE_TIME_ERR] = "ERR", - [PARSE_TIME_ERR_LIB] = "LIB", - [PARSE_TIME_ERR_ALREADYSET] = "ALREADYSET", - [PARSE_TIME_ERR_FORMAT] = "FORMAT", - [PARSE_TIME_ERR_DATEFORMAT] = "DATEFORMAT", - [PARSE_TIME_ERR_TIMEFORMAT] = "TIMEFORMAT", - [PARSE_TIME_ERR_INVALIDDATE] = "INVALIDDATE", - [PARSE_TIME_ERR_INVALIDTIME] = "INVALIDTIME", - [PARSE_TIME_ERR_KEYWORD] = "KEYWORD", + [PARSE_TIME_OK] = "OK", + [PARSE_TIME_ERR] = "ERR", + [PARSE_TIME_ERR_LIB] = "LIB", + [PARSE_TIME_ERR_ALREADYSET] = "ALREADYSET", + [PARSE_TIME_ERR_FORMAT] = "FORMAT", + [PARSE_TIME_ERR_DATEFORMAT] = "DATEFORMAT", + [PARSE_TIME_ERR_TIMEFORMAT] = "TIMEFORMAT", + [PARSE_TIME_ERR_INVALIDDATE] = "INVALIDDATE", + [PARSE_TIME_ERR_INVALIDTIME] = "INVALIDTIME", + [PARSE_TIME_ERR_KEYWORD] = "KEYWORD", }; static const char * @@ -66,7 +66,7 @@ concat_args (int start, int end, char *argv[]) len += strlen (argv[i]) + 1; p = malloc (len); - if (!p) + if (! p) return NULL; *p = 0; @@ -111,10 +111,10 @@ struct { const char *operator; int round; } operators[] = { - { "==>", PARSE_TIME_NO_ROUND }, - { "==_>", PARSE_TIME_ROUND_DOWN }, - { "==^>", PARSE_TIME_ROUND_UP_INCLUSIVE }, - { "==^^>", PARSE_TIME_ROUND_UP }, + { "==>", PARSE_TIME_NO_ROUND }, + { "==_>", PARSE_TIME_ROUND_DOWN }, + { "==^>", PARSE_TIME_ROUND_UP_INCLUSIVE }, + { "==^^>", PARSE_TIME_ROUND_UP }, }; static const char * @@ -145,7 +145,7 @@ get_operator (int round) const char *oper = NULL; unsigned int i; - for (i = 0; i < ARRAY_SIZE(operators); i++) { + for (i = 0; i < ARRAY_SIZE (operators); i++) { if (round == operators[i].round) { oper = operators[i].operator; break; @@ -172,10 +172,10 @@ parse_stdin (FILE *infile, time_t *ref, int round, const char *format) /* trail is trailing whitespace and (optional) comment */ trail = strchr (input, '#'); - if (!trail) + if (! trail) trail = input + len; - while (trail > input && isspace ((unsigned char) *(trail-1))) + while (trail > input && isspace ((unsigned char) *(trail - 1))) trail--; if (trail == input) { @@ -184,7 +184,7 @@ parse_stdin (FILE *infile, time_t *ref, int round, const char *format) } tmp = strdup (trail); - if (!tmp) { + if (! tmp) { fprintf (stderr, "strdup() failed\n"); continue; } @@ -201,8 +201,8 @@ parse_stdin (FILE *infile, time_t *ref, int round, const char *format) } r = parse_time_string (input, &t, ref, round); - if (!r) { - if (!localtime_r (&t, &tm)) { + if (! r) { + if (! localtime_r (&t, &tm)) { fprintf (stderr, "localtime_r() failed\n"); free (trail); continue; @@ -239,12 +239,12 @@ main (int argc, char *argv[]) char buf[1024]; const char *format = DEFAULT_FORMAT; struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "^", no_argument, NULL, 'u' }, - { "^^", no_argument, NULL, 'U' }, - { "_", no_argument, NULL, 'd' }, - { "format", required_argument, NULL, 'f' }, - { "ref", required_argument, NULL, 'r' }, + { "help", no_argument, NULL, 'h' }, + { "^", no_argument, NULL, 'u' }, + { "^^", no_argument, NULL, 'U' }, + { "_", no_argument, NULL, 'd' }, + { "format", required_argument, NULL, 'f' }, + { "ref", required_argument, NULL, 'r' }, { NULL, 0, NULL, 0 }, }; @@ -287,7 +287,7 @@ main (int argc, char *argv[]) return parse_stdin (stdin, nowp, round, format); argstr = concat_args (optind, argc, argv); - if (!argstr) + if (! argstr) return 1; r = parse_time_string (argstr, &result, nowp, round); @@ -304,7 +304,7 @@ main (int argc, char *argv[]) return r; } - if (!localtime_r (&result, &tm)) + if (! localtime_r (&result, &tm)) return 1; strftime (buf, sizeof (buf), format, &tm); diff --git a/test/random-corpus.c b/test/random-corpus.c index 9272afda..8ed7ff76 100644 --- a/test/random-corpus.c +++ b/test/random-corpus.c @@ -116,7 +116,7 @@ random_utf8_string (void *ctx, size_t char_count) /* stubs since we cannot link with notmuch.o */ const notmuch_opt_desc_t notmuch_shared_options[] = { - { } + { } }; const char *notmuch_requested_db_uuid = NULL; diff --git a/test/smtp-dummy.c b/test/smtp-dummy.c index a7c1fe4f..1c89e9af 100644 --- a/test/smtp-dummy.c +++ b/test/smtp-dummy.c @@ -275,7 +275,7 @@ main (int argc, char *argv[]) do_smtp_to_file (peer_file, output); - DONE: + DONE: if (output) fclose (output); if (peer_file) diff --git a/test/symbol-test.cc b/test/symbol-test.cc index 7454838b..9d73a571 100644 --- a/test/symbol-test.cc +++ b/test/symbol-test.cc @@ -3,7 +3,8 @@ #include #include -int main (int argc, char** argv) +int +main (int argc, char **argv) { notmuch_database_t *notmuch; char *message = NULL; @@ -22,7 +23,7 @@ int main (int argc, char** argv) try { (void) new Xapian::WritableDatabase (argv[2], Xapian::DB_OPEN); } catch (const Xapian::Error &error) { - printf("caught %s\n", error.get_msg().c_str()); + printf ("caught %s\n", error.get_msg ().c_str ()); return 0; } From 290eccc6402d53eb1ccf3d365927854104aff1ee Mon Sep 17 00:00:00 2001 From: Daniel Kahn Gillmor Date: Wed, 3 Jul 2019 00:31:19 -0400 Subject: [PATCH 023/427] notmuch-show: run uncrustify This is the result of running: $ uncrustify --replace --config devel/uncrustify.cfg *.c *.h In the top level source directory. I was using uncrustify 0.68.1+dfsg1-2. I do not know why these changes were not caught in 33382c2b5ba2537952a60ea378feff36961e4713 Signed-off-by: Daniel Kahn Gillmor --- notmuch-show.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/notmuch-show.c b/notmuch-show.c index 9779cfa5..21792a57 100644 --- a/notmuch-show.c +++ b/notmuch-show.c @@ -492,7 +492,7 @@ format_part_text (const void *ctx, sprinter_t *sp, mime_node_t *node, /* The disposition and content-type metadata are associated with * the envelope for message parts */ GMimeObject *meta = node->envelope_part ? ( - GMIME_OBJECT (node->envelope_part) ) : node->part ; + GMIME_OBJECT (node->envelope_part) ) : node->part; GMimeContentType *content_type = g_mime_object_get_content_type (meta); const bool leaf = GMIME_IS_PART (node->part); GMimeStream *stream = params->out_stream; @@ -515,7 +515,7 @@ format_part_text (const void *ctx, sprinter_t *sp, mime_node_t *node, const char *disposition = _get_disposition (meta); const char *cid = g_mime_object_get_content_id (meta); const char *filename = leaf ? ( - g_mime_part_get_filename (GMIME_PART (node->part)) ) : NULL ; + g_mime_part_get_filename (GMIME_PART (node->part)) ) : NULL; if (disposition && strcasecmp (disposition, GMIME_DISPOSITION_ATTACHMENT) == 0) @@ -688,7 +688,7 @@ format_part_sprinter (const void *ctx, sprinter_t *sp, mime_node_t *node, /* The disposition and content-type metadata are associated with * the envelope for message parts */ GMimeObject *meta = node->envelope_part ? ( - GMIME_OBJECT (node->envelope_part) ): node->part; + GMIME_OBJECT (node->envelope_part) ) : node->part; GMimeContentType *content_type = g_mime_object_get_content_type (meta); char *content_string; const char *disposition = _get_disposition (meta); @@ -965,7 +965,7 @@ show_message (void *ctx, } } } - DONE : + DONE: talloc_free (local); return status; } @@ -1320,7 +1320,7 @@ notmuch_show_command (notmuch_config_t *config, int argc, char *argv[]) ret = do_show (config, query, formatter, sprinter, ¶ms); } - DONE : + DONE: g_mime_stream_flush (params.out_stream); g_object_unref (params.out_stream); From b6e589f54f25594f6beb7b5ee9e884c75f3bd633 Mon Sep 17 00:00:00 2001 From: Tomi Ollila Date: Sat, 15 Jun 2019 17:28:44 +0300 Subject: [PATCH 024/427] test: aggregate-results.sh: count test files where all tests skipped Previously, when all tests were skipped on a test file, there were no indication of this in the final results aggregate-results.sh printed. Now count of the files where all tests were skipped is printed. --- test/aggregate-results.sh | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/test/aggregate-results.sh b/test/aggregate-results.sh index 05fb0a92..75400e6e 100755 --- a/test/aggregate-results.sh +++ b/test/aggregate-results.sh @@ -7,6 +7,7 @@ success=0 failed=0 broken=0 total=0 +all_skipped=0 for file do @@ -22,7 +23,10 @@ do broken) broken=$((broken + value)) ;; total) - total=$((total + value)) ;; + total=$((total + value)) + if [ "$value" -eq 0 ]; then + all_skipped=$((all_skipped + 1)) + fi esac done <"$file" done @@ -61,6 +65,10 @@ if [ "$skipped" -ne 0 ]; then pluralize_s "$skipped" echo "$skipped test$s skipped." fi +if [ "$all_skipped" -ne 0 ]; then + pluralize_s "$all_skipped" + echo "All tests in $all_skipped file$s skipped." +fi # Note that we currently do not consider skipped tests as failing the # build. From 17806ecc955ce0375146ea1df51eae061a72bef8 Mon Sep 17 00:00:00 2001 From: Ralph Seichter Date: Tue, 23 Jul 2019 22:48:23 +0200 Subject: [PATCH 025/427] notmuch-dump.c: Fix output file being closed twice Fixed: If the output file for a dump was non-writeable, gzclose_w() was called twice on the output file handle, resulting in SIGABRT. --- notmuch-dump.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/notmuch-dump.c b/notmuch-dump.c index 505c1469..65e02639 100644 --- a/notmuch-dump.c +++ b/notmuch-dump.c @@ -329,13 +329,15 @@ notmuch_database_dump (notmuch_database_t *notmuch, } } - if (gzclose_w (output) != Z_OK) { + ret = gzclose_w (output); + if (ret) { fprintf (stderr, "Error closing %s: %s\n", name_for_error, gzerror (output, NULL)); ret = EXIT_FAILURE; output = NULL; goto DONE; - } + } else + output = NULL; if (output_file_name) { ret = rename (tempname, output_file_name); From e13862f127394fd4addc5d2cf604b3af399c8377 Mon Sep 17 00:00:00 2001 From: Tomi Ollila Date: Mon, 26 Aug 2019 20:03:46 +0300 Subject: [PATCH 026/427] configure: fix out of tree build; check unsafe characters in srcdir While check for GMime session key extraction support... was made out of tree build compatible, related (and some unrelated) unsafe characters are now checked in notmuch source directory path. The known unsafe characters in NOTMUCH_SRCDIR are: - Single quote (') -- NOTMUCH_SRCDIR='${NOTMUCH_SRCDIR}' is written to sh.config in configure line 1328. - Double quote (") -- configure line 521 *now* writes "$srcdir" into generated c source file ($NOTMUCH_SRCDIR includes $srcdir). - Backslash (\) could also be problematic in configure line 521. - The added $ and ` are potentially unsafe -- inside double quotes in shell script those have special meaning. Other characters don't expand inside double quoted strings. --- configure | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/configure b/configure index 1e7b9f7a..ef81e71b 100755 --- a/configure +++ b/configure @@ -26,6 +26,11 @@ readonly DEFAULT_IFS="$IFS" srcdir=$(dirname "$0") NOTMUCH_SRCDIR=$(cd "$srcdir" && pwd) +case $NOTMUCH_SRCDIR in ( *\'* | *['\"`$']* ) + echo "Definitely unsafe characters in source path '$NOTMUCH_SRCDIR'". + exit 1 +esac + subdirs="util compat lib parse-time-string completion doc emacs" subdirs="${subdirs} performance-test test test/test-databases" subdirs="${subdirs} bindings" @@ -513,7 +518,7 @@ int main () { g_mime_init (); parser = g_mime_parser_new (); - g_mime_parser_init_with_stream (parser, g_mime_stream_file_open("test/corpora/crypto/basic-encrypted.eml", "r", &error)); + g_mime_parser_init_with_stream (parser, g_mime_stream_file_open("$srcdir/test/corpora/crypto/basic-encrypted.eml", "r", &error)); if (error) return !! fprintf (stderr, "failed to instantiate parser with test/corpora/crypto/basic-encrypted.eml\n"); body = GMIME_MULTIPART_ENCRYPTED(g_mime_message_get_mime_part (g_mime_parser_construct_message (parser, NULL))); @@ -533,7 +538,7 @@ EOF printf 'No.\nCould not make tempdir for testing session-key support.\n' errors=$((errors + 1)) elif ${CC} ${CFLAGS} ${gmime_cflags} _check_session_keys.c ${gmime_ldflags} -o _check_session_keys \ - && GNUPGHOME=${TEMP_GPG} gpg --batch --quiet --import < test/gnupg-secret-key.asc \ + && GNUPGHOME=${TEMP_GPG} gpg --batch --quiet --import < "$srcdir"/test/gnupg-secret-key.asc \ && SESSION_KEY=$(GNUPGHOME=${TEMP_GPG} ./_check_session_keys) \ && [ $SESSION_KEY = 9:0BACD64099D1468AB07C796F0C0AC4851948A658A15B34E803865E9FC635F2F5 ] then From dcfa2fc99667467011516a3223f7889c05955746 Mon Sep 17 00:00:00 2001 From: Daniel Kahn Gillmor Date: Thu, 29 Aug 2019 11:38:46 -0400 Subject: [PATCH 027/427] mime-node: split out _mime_node_set_up_part This is a code reorganization that should have no functional effect, but will make future changes simpler, because a future commit will reuse the _mime_node_set_up_part functionality without touching _mime_node_create. In the course of splitting out this function, I noticed a comment in the codebase that referred to an older name of _mime_node_create (message_part_create), where this functionality originally resided. I've fixed that comment to refer to the new function instead. Signed-off-by: Daniel Kahn Gillmor --- mime-node.c | 30 ++++++++++++++++++++++-------- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/mime-node.c b/mime-node.c index 3133ca44..d2125f90 100644 --- a/mime-node.c +++ b/mime-node.c @@ -264,14 +264,15 @@ node_decrypt_and_verify (mime_node_t *node, GMimeObject *part) g_error_free (err); } +static bool +_mime_node_set_up_part (mime_node_t *node, GMimeObject *part, int numchild); + static mime_node_t * _mime_node_create (mime_node_t *parent, GMimeObject *part, int numchild) { mime_node_t *node = talloc_zero (parent, mime_node_t); - notmuch_status_t status; /* Set basic node properties */ - node->part = part; node->ctx = parent->ctx; if (! talloc_reference (node, node->ctx)) { fprintf (stderr, "Out of memory.\n"); @@ -282,10 +283,24 @@ _mime_node_create (mime_node_t *parent, GMimeObject *part, int numchild) node->part_num = node->next_part_num = -1; node->next_child = 0; + if (_mime_node_set_up_part (node, part, numchild)) + return node; + talloc_free (node); + return NULL; +} + +/* associate a MIME part with a node. */ +static bool +_mime_node_set_up_part (mime_node_t *node, GMimeObject *part, int numchild) +{ + notmuch_status_t status; + /* Deal with the different types of parts */ if (GMIME_IS_PART (part)) { + node->part = part; node->nchildren = 0; } else if (GMIME_IS_MULTIPART (part)) { + node->part = part; node->nchildren = g_mime_multipart_get_count (GMIME_MULTIPART (part)); } else if (GMIME_IS_MESSAGE_PART (part)) { /* Promote part to an envelope and open it */ @@ -297,11 +312,10 @@ _mime_node_create (mime_node_t *parent, GMimeObject *part, int numchild) } else { fprintf (stderr, "Warning: Unknown mime part type: %s.\n", g_type_name (G_OBJECT_TYPE (part))); - talloc_free (node); - return NULL; + return false; } - /* Handle PGP/MIME parts */ + /* Handle PGP/MIME parts (by definition not cryptographic payload parts) */ if (GMIME_IS_MULTIPART_ENCRYPTED (part) && (node->ctx->crypto->decrypt != NOTMUCH_DECRYPT_FALSE)) { if (node->nchildren != 2) { /* this violates RFC 3156 section 4, so we won't bother with it. */ @@ -321,12 +335,12 @@ _mime_node_create (mime_node_t *parent, GMimeObject *part, int numchild) node_verify (node, part); } } else { - status = _notmuch_message_crypto_potential_payload (node->ctx->msg_crypto, part, parent ? parent->part : NULL, numchild); + status = _notmuch_message_crypto_potential_payload (node->ctx->msg_crypto, part, node->parent ? node->parent->part : NULL, numchild); if (status) fprintf (stderr, "Warning: failed to record potential crypto payload (%s).\n", notmuch_status_to_string (status)); } - return node; + return true; } mime_node_t * @@ -347,7 +361,7 @@ mime_node_child (mime_node_t *parent, int child) } else if (GMIME_IS_MESSAGE (parent->part)) { sub = g_mime_message_get_mime_part (GMIME_MESSAGE (parent->part)); } else { - /* This should have been caught by message_part_create */ + /* This should have been caught by _mime_node_set_up_part */ INTERNAL_ERROR ("Unexpected GMimeObject type: %s", g_type_name (G_OBJECT_TYPE (parent->part))); } From 1b29822cf55eb53e1d45a71c2a3e4a2c2a4574d1 Mon Sep 17 00:00:00 2001 From: Daniel Kahn Gillmor Date: Thu, 29 Aug 2019 11:38:47 -0400 Subject: [PATCH 028/427] repair: set up codebase for repair functionality This adds no functionality directly, but is a useful starting point for adding new repair functionality. Signed-off-by: Daniel Kahn Gillmor --- doc/man7/notmuch-properties.rst | 12 ++++++++++++ lib/notmuch-private.h | 1 + notmuch-client.h | 1 + util/Makefile.local | 1 + util/repair.c | 21 +++++++++++++++++++++ util/repair.h | 17 +++++++++++++++++ 6 files changed, 53 insertions(+) create mode 100644 util/repair.c create mode 100644 util/repair.h diff --git a/doc/man7/notmuch-properties.rst b/doc/man7/notmuch-properties.rst index 802e6763..2e610683 100644 --- a/doc/man7/notmuch-properties.rst +++ b/doc/man7/notmuch-properties.rst @@ -109,6 +109,18 @@ of its normal activity. example, an AES-128 key might be stashed in a notmuch property as: ``session-key=7:14B16AF65536C28AF209828DFE34C9E0``. +**index.repaired** + + Some messages arrive in forms that are confusing to view; they can + be mangled by mail transport agents, or the sending mail user + agent may structure them in a way that is confusing. If notmuch + knows how to both detect and repair such a problematic message, it + will do so during indexing. + + If it applies a message repair during indexing, it will use the + ``index.repaired`` property to note the type of repair(s) it + performed. + SEE ALSO ======== diff --git a/lib/notmuch-private.h b/lib/notmuch-private.h index 9bdb68ab..28ced3a2 100644 --- a/lib/notmuch-private.h +++ b/lib/notmuch-private.h @@ -53,6 +53,7 @@ NOTMUCH_BEGIN_DECLS #include "error_util.h" #include "string-util.h" #include "crypto.h" +#include "repair.h" #ifdef DEBUG # define DEBUG_DATABASE_SANITY 1 diff --git a/notmuch-client.h b/notmuch-client.h index d1b78017..74690054 100644 --- a/notmuch-client.h +++ b/notmuch-client.h @@ -52,6 +52,7 @@ #include "talloc-extra.h" #include "crypto.h" +#include "repair.h" #define unused(x) x ## _unused __attribute__ ((unused)) diff --git a/util/Makefile.local b/util/Makefile.local index 46f8af3a..f5d72f79 100644 --- a/util/Makefile.local +++ b/util/Makefile.local @@ -6,6 +6,7 @@ extra_cflags += -I$(srcdir)/$(dir) libnotmuch_util_c_srcs := $(dir)/xutil.c $(dir)/error_util.c $(dir)/hex-escape.c \ $(dir)/string-util.c $(dir)/talloc-extra.c $(dir)/zlib-extra.c \ $(dir)/util.c $(dir)/gmime-extra.c $(dir)/crypto.c \ + $(dir)/repair.c \ $(dir)/unicode-util.c libnotmuch_util_modules := $(libnotmuch_util_c_srcs:.c=.o) diff --git a/util/repair.c b/util/repair.c new file mode 100644 index 00000000..f91c1244 --- /dev/null +++ b/util/repair.c @@ -0,0 +1,21 @@ +/* notmuch - Not much of an email program, (just index and search) + * + * Copyright © 2019 Daniel Kahn Gillmor + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see https://www.gnu.org/licenses/ . + * + * Authors: Daniel Kahn Gillmor + */ + +#include "repair.h" diff --git a/util/repair.h b/util/repair.h new file mode 100644 index 00000000..70e2b7bc --- /dev/null +++ b/util/repair.h @@ -0,0 +1,17 @@ +#ifndef _REPAIR_H +#define _REPAIR_H + +#include "gmime-extra.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* This is a collection of message structure and message format repair + * techniques that are designed to improve the user experience of + * notmuch */ + +#ifdef __cplusplus +} +#endif +#endif From 27b25e45dc675af2e9ffeea014a54e34bfbdad83 Mon Sep 17 00:00:00 2001 From: Daniel Kahn Gillmor Date: Thu, 29 Aug 2019 11:38:48 -0400 Subject: [PATCH 029/427] test: avoid showing legacy-display parts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Enigmail generates a "legacy-display" part when it sends encrypted mail with a protected Subject: header. This part is intended to display the Subject for mail user agents that are capable of decryption, but do not know how to deal with embedded protected headers. This part is the first child of a two-part multipart/mixed cryptographic payload within a cryptographic envelope that includes encryption (that is, it is not just a cleartext signed message). It uses Content-Type: text/rfc822-headers. That is: A └┬╴multipart/encrypted B ├─╴application/pgp-encrypted C └┬╴application/octet-stream * ╤ D └┬╴multipart/mixed; protected-headers=v1 (cryptographic payload) E ├─╴text/rfc822-headers; protected-headers=v1 (legacy-display part) F └─╴… (actual message body) In discussions with jrollins, i've come to the conclusion that a legacy-display part should be stripped entirely from "notmuch show" and "notmuch reply" now that these tools can understand and interpret protected headers. You can tell when a message part is a protected header part this way: * is the payload (D) multipart/mixed with exactly two children? * is its first child (E) Content-Type: text/rfc822-headers? * does the first child (E) have the property protected-headers=v1? * do all the headers in the body of the first child (E) match the protected headers in the payload part (D) itself? If this is the case, and we already know how to deal with the protected header, then there is no reason to try to render the legacy-display part itself for the user. Furthermore, when indexing, if we are indexing properly, we should avoid indexing the text in E as part of the message body. 'notmuch reply' is an interesting case: the standard use of 'notmuch reply' will end up omitting all mention of protected Subject:. The right fix is for the replying MUA to be able to protect its headers, and for it to set them appropriately based on headers found in the original message. If a replying MUA is unable to protect headers, but still wants the user to be able to see the original header, a replying MUA that notices that the original message's subject differs from the proposed reply subject may choose to include the original's subject in the quoted/attributed text. (this would be a stopgap measure; it's not even clear that there is user demand for it) This test suite change indicates what we want to happen for this case (the tests are currently broken), and includes three additional TODO suggestions of subtle cases for anyone who wants to flesh out the test suite even further. (i believe all these cases should be already fixed by the rest of this series, but haven't had time to write the tests for the unusual cases) Signed-off-by: Daniel Kahn Gillmor --- test/T356-protected-headers.sh | 33 +++++++++++++++ .../protected-with-legacy-display.eml | 40 +++++++++++++++++++ 2 files changed, 73 insertions(+) create mode 100644 test/corpora/protected-headers/protected-with-legacy-display.eml diff --git a/test/T356-protected-headers.sh b/test/T356-protected-headers.sh index 4af018f3..43dfffe6 100755 --- a/test/T356-protected-headers.sh +++ b/test/T356-protected-headers.sh @@ -136,4 +136,37 @@ id:nested-rfc822-message@crypto.notmuchmail.org id:protected-header@crypto.notmuchmail.org id:subjectless-protected-header@crypto.notmuchmail.org' +test_begin_subtest "when rendering protected headers, avoid rendering legacy-display part" +test_subtest_known_broken +output=$(notmuch show --format=json id:protected-with-legacy-display@crypto.notmuchmail.org) +test_json_nodes <<<"$output" \ + 'subject:[0][0][0]["headers"]["Subject"]="Interrupting Cow"' \ + 'no_legacy_display:[0][0][0]["body"][0]["content"][1]["content-type"]="text/plain"' + +test_begin_subtest "when replying, avoid rendering legacy-display part" +test_subtest_known_broken +output=$(notmuch reply --format=json id:protected-with-legacy-display@crypto.notmuchmail.org) +test_json_nodes <<<"$output" \ + 'no_legacy_display:["original"]["body"][0]["content"][1]["content-type"]="text/plain"' + +test_begin_subtest "do not treat legacy-display part as body when indexing" +test_subtest_known_broken +output=$(notmuch search --output=messages body:interrupting) +test_expect_equal "$output" '' + +test_begin_subtest "identify message that had a legacy display part skipped during indexing" +test_subtest_known_broken +output=$(notmuch search --output=messages property:index.repaired=skip-protected-headers-legacy-display) +test_expect_equal "$output" id:protected-with-legacy-display@crypto.notmuchmail.org + +# TODO: test that a part that looks like a legacy-display in +# multipart/signed, but not encrypted, is indexed and not stripped. + +# TODO: test that a legacy-display in a decrypted subpart (not in the +# cryptographic payload) is indexed and not stripped. + +# TODO: test that a legacy-display inside multiple MIME layers that +# include an encryption layer (e.g. multipart/encrypted around +# multipart/signed) is stripped and not indexed. + test_done diff --git a/test/corpora/protected-headers/protected-with-legacy-display.eml b/test/corpora/protected-headers/protected-with-legacy-display.eml new file mode 100644 index 00000000..8c5dd251 --- /dev/null +++ b/test/corpora/protected-headers/protected-with-legacy-display.eml @@ -0,0 +1,40 @@ +From: test_suite@notmuchmail.org +To: test_suite@notmuchmail.org +Subject: Subject Unavailable +Date: Sat, 01 Jan 2000 12:00:00 +0000 +Message-Id: +MIME-Version: 1.0 +Content-Type: multipart/encrypted; boundary="=-=-="; + protocol="application/pgp-encrypted" + +--=-=-= +Content-Type: application/pgp-encrypted + +Version: 1 + +--=-=-= +Content-Type: application/octet-stream + +-----BEGIN PGP MESSAGE----- + +hIwDxE023q1UqxYBBACkgwtKOAP+UlKYDzYkZY+gDuMNKnHjWIvv2Cdnovy40QzL +5sbuib40y7orO+MqYMCWpoFtgBVsGiOUE3bZAg8n3Ji38/zVGwQveu6sh7SAy0Q9 +zFEvLhtajw17nPe+QH2UmIyfVikA57Mot13THq4i6C4ozVCyhyIltx+sNJkmw9Lp +AdQd+cgCMRSMbi++eRwIi4zgxKrfAoGOmdMiVzBrh3yZqnbI0rCxJIKu7gEWuQLT +7BuvN2bJUkPGLAUhUanFararVoD7WWOl67IlWFkyncES0PRskUf9coV68WZnYjsR +Y3LdLnha1sdMwUNeBKQ44XBd2e7mXbDSp1cSjTDf9euwB4m7uQFTLwoQ8Of+LmQD +KMHzjmucbkNAIpfAjcDusTA/oaaqUiEgGIgYYMDqG1CaaxdT55S7tMjW5yJryQmo +pg65jrUMgEn5XHZ+KI2OsCmwGdoBYNau8p1a2hsiKhHJmLUeEAu34gFI3hylIOC0 +0KC40d0zTSb0s7SZuTrD6vYgiXG9aFktHvAWFH0ATCts7qyiRN7k5jt7yWfRntE2 +UCexTGE3TH7aju+IqDPC1XsaKF4T3CVhdr8WmKCa+0VOaw7xHRGYnzq9y91GcaCx +8AcoZ3kYs+f2LIn+T667A0KKP4Z6OmLjCx3b1RvRUQYR9taruEMAQbIuAajiyTe9 +KfUrsUULZfInE50x+OneYvDhzoSgSJoHIK+18X/wo6YcyleJ9fZxCQ/vaXTDkAeF +ve7TFcbIqmJ4MHygXILHUuDwp7P4t/tIL7SZwja70P3digjsgoNZY29VTnU8uyIb +d6eOjgpeNVhRjDWxbUvhFD7i4rHCi/bbXFlW0cCXoiaVQBtYmiNysRoRZOv0h3TW +q/+/UmqkaQFnF3zp5sr87y+ValItgPWmb9Ds0lyAoSvQx35zVh8DFfH04m7hmsb7 +gcvemlPTAnQWkIMC3c/bZWgt8tNcG7tQeUMWd9n4281y/hApbm90x2NLzEqvVcRq +K0iIgVxbCHSKqGh4TtbIwpNhzSP+KHYkZ8h6+QUDRwGEV9QqZKg= +=2O0V +-----END PGP MESSAGE----- + +--=-=-=-- From 8b25643d08b9388ae9c38837a3dfd3dbc6b085cf Mon Sep 17 00:00:00 2001 From: Daniel Kahn Gillmor Date: Thu, 29 Aug 2019 11:38:49 -0400 Subject: [PATCH 030/427] util/crypto: _n_m_crypto_potential_payload: rename "payload" arg to "part" _notmuch_message_crypto_potential_payload is called on a GMimeObject while walking the MIME tree of a message to determine whether that object is the payload. It doesn't make sense to name the argument "payload" if it might not be the payload, so we rename it to "part" for clarity. This is a non-functional change, just semantic cleanup. Signed-off-by: Daniel Kahn Gillmor --- util/crypto.c | 16 ++++++++-------- util/crypto.h | 2 +- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/util/crypto.c b/util/crypto.c index 225f537a..3845ade8 100644 --- a/util/crypto.c +++ b/util/crypto.c @@ -136,13 +136,13 @@ _notmuch_message_crypto_potential_sig_list (_notmuch_message_crypto_t *msg_crypt notmuch_status_t -_notmuch_message_crypto_potential_payload (_notmuch_message_crypto_t *msg_crypto, GMimeObject *payload, GMimeObject *parent, int childnum) +_notmuch_message_crypto_potential_payload (_notmuch_message_crypto_t *msg_crypto, GMimeObject *part, GMimeObject *parent, int childnum) { const char *protected_headers = NULL; const char *forwarded = NULL; const char *subject = NULL; - if (! msg_crypto || ! payload) + if (! msg_crypto || ! part) return NOTMUCH_STATUS_NULL_POINTER; /* only fire on the first payload part encountered */ @@ -155,7 +155,7 @@ _notmuch_message_crypto_potential_payload (_notmuch_message_crypto_t *msg_crypto * https://tools.ietf.org/html/rfc1847#page-8) */ if (parent && GMIME_IS_MULTIPART_ENCRYPTED (parent) && childnum == GMIME_MULTIPART_ENCRYPTED_VERSION) { const char *enc_type = g_mime_object_get_content_type_parameter (parent, "protocol"); - GMimeContentType *ct = g_mime_object_get_content_type (payload); + GMimeContentType *ct = g_mime_object_get_content_type (part); if (ct && enc_type) { const char *part_type = g_mime_content_type_get_mime_type (ct); if (part_type && strcmp (part_type, enc_type) == 0) @@ -177,16 +177,16 @@ _notmuch_message_crypto_potential_payload (_notmuch_message_crypto_t *msg_crypto /* Consider a payload that uses Alexei Melinkov's forwarded="no" for * message/global or message/rfc822: * https://tools.ietf.org/html/draft-melnikov-smime-header-signing-05#section-4 */ - forwarded = g_mime_object_get_content_type_parameter (payload, "forwarded"); - if (GMIME_IS_MESSAGE_PART (payload) && forwarded && strcmp (forwarded, "no") == 0) { - GMimeMessage *message = g_mime_message_part_get_message (GMIME_MESSAGE_PART (payload)); + forwarded = g_mime_object_get_content_type_parameter (part, "forwarded"); + if (GMIME_IS_MESSAGE_PART (part) && forwarded && strcmp (forwarded, "no") == 0) { + GMimeMessage *message = g_mime_message_part_get_message (GMIME_MESSAGE_PART (part)); subject = g_mime_message_get_subject (message); /* FIXME: handle more than just Subject: at some point */ } else { /* Consider "memoryhole"-style protected headers as practiced by Enigmail and K-9 */ - protected_headers = g_mime_object_get_content_type_parameter (payload, "protected-headers"); + protected_headers = g_mime_object_get_content_type_parameter (part, "protected-headers"); if (protected_headers && strcasecmp ("v1", protected_headers) == 0) - subject = g_mime_object_get_header (payload, "Subject"); + subject = g_mime_object_get_header (part, "Subject"); /* FIXME: handle more than just Subject: at some point */ } diff --git a/util/crypto.h b/util/crypto.h index 11e8060a..c4411246 100644 --- a/util/crypto.h +++ b/util/crypto.h @@ -92,7 +92,7 @@ _notmuch_message_crypto_successful_decryption (_notmuch_message_crypto_t *msg_cr * when encountering a message part that is not part of the envelope. */ notmuch_status_t -_notmuch_message_crypto_potential_payload (_notmuch_message_crypto_t *msg_crypto, GMimeObject *payload, GMimeObject *parent, int childnum); +_notmuch_message_crypto_potential_payload (_notmuch_message_crypto_t *msg_crypto, GMimeObject *part, GMimeObject *parent, int childnum); #ifdef __cplusplus From ab0ae8b1c086ca3878f16ce40cc421eeb206c79e Mon Sep 17 00:00:00 2001 From: Daniel Kahn Gillmor Date: Thu, 29 Aug 2019 11:38:50 -0400 Subject: [PATCH 031/427] util/crypto: _n_m_crypto_potential_payload returns whether part is the payload Our _notmuch_message_crypto_potential_payload implementation could only return a failure if bad arguments were passed to it. It is an internal function, so if that happens it's an entirely internal bug for notmuch. It will be more useful for this function to return whether or not the part is in fact a cryptographic payload, so we dispense with the status return. If some future change suggests adding a status return back, there are only a handful of call sites, and no pressure to retain a stable API, so it could be changed easily. But for now, go with the simpler function. We will use this return value in future patches, to make different decisions based on whether a part is the cryptographic payload or not. But for now, we just leave the places where it gets invoked marked with (void) to show that the result is ignored. Signed-off-by: Daniel Kahn Gillmor --- lib/index.cc | 9 ++------- mime-node.c | 6 +----- util/crypto.c | 16 +++++++++------- util/crypto.h | 5 ++++- 4 files changed, 16 insertions(+), 20 deletions(-) diff --git a/lib/index.cc b/lib/index.cc index db3dd568..08cc84e2 100644 --- a/lib/index.cc +++ b/lib/index.cc @@ -407,7 +407,6 @@ _index_mime_part (notmuch_message_t *message, _notmuch_message_add_term (message, "tag", "encrypted"); for (i = 0; i < g_mime_multipart_get_count (multipart); i++) { - notmuch_status_t status; GMimeObject *child; if (GMIME_IS_MULTIPART_SIGNED (multipart)) { /* Don't index the signature, but index its content type. */ @@ -436,11 +435,7 @@ _index_mime_part (notmuch_message_t *message, continue; } child = g_mime_multipart_get_part (multipart, i); - status = _notmuch_message_crypto_potential_payload (msg_crypto, child, part, i); - if (status) - _notmuch_database_log (notmuch_message_get_database (message), - "Warning: failed to mark the potential cryptographic payload (%s).\n", - notmuch_status_to_string (status)); + (void) _notmuch_message_crypto_potential_payload (msg_crypto, child, part, i); _index_mime_part (message, indexopts, child, msg_crypto); } return; @@ -578,7 +573,7 @@ _index_encrypted_mime_part (notmuch_message_t *message, } g_object_unref (decrypt_result); } - status = _notmuch_message_crypto_potential_payload (msg_crypto, clear, GMIME_OBJECT (encrypted_data), GMIME_MULTIPART_ENCRYPTED_CONTENT); + _notmuch_message_crypto_potential_payload (msg_crypto, clear, GMIME_OBJECT (encrypted_data), GMIME_MULTIPART_ENCRYPTED_CONTENT); _index_mime_part (message, indexopts, clear, msg_crypto); g_object_unref (clear); diff --git a/mime-node.c b/mime-node.c index d2125f90..abb6dd84 100644 --- a/mime-node.c +++ b/mime-node.c @@ -293,8 +293,6 @@ _mime_node_create (mime_node_t *parent, GMimeObject *part, int numchild) static bool _mime_node_set_up_part (mime_node_t *node, GMimeObject *part, int numchild) { - notmuch_status_t status; - /* Deal with the different types of parts */ if (GMIME_IS_PART (part)) { node->part = part; @@ -335,9 +333,7 @@ _mime_node_set_up_part (mime_node_t *node, GMimeObject *part, int numchild) node_verify (node, part); } } else { - status = _notmuch_message_crypto_potential_payload (node->ctx->msg_crypto, part, node->parent ? node->parent->part : NULL, numchild); - if (status) - fprintf (stderr, "Warning: failed to record potential crypto payload (%s).\n", notmuch_status_to_string (status)); + (void) _notmuch_message_crypto_potential_payload (node->ctx->msg_crypto, part, node->parent ? node->parent->part : NULL, numchild); } return true; diff --git a/util/crypto.c b/util/crypto.c index 3845ade8..0bb6f526 100644 --- a/util/crypto.c +++ b/util/crypto.c @@ -20,6 +20,7 @@ #include "crypto.h" #include +#include "error_util.h" #define unused(x) x __attribute__ ((unused)) #define ARRAY_SIZE(arr) (sizeof (arr) / sizeof (arr[0])) @@ -135,19 +136,20 @@ _notmuch_message_crypto_potential_sig_list (_notmuch_message_crypto_t *msg_crypt } -notmuch_status_t +bool _notmuch_message_crypto_potential_payload (_notmuch_message_crypto_t *msg_crypto, GMimeObject *part, GMimeObject *parent, int childnum) { const char *protected_headers = NULL; const char *forwarded = NULL; const char *subject = NULL; - if (! msg_crypto || ! part) - return NOTMUCH_STATUS_NULL_POINTER; + if ((! msg_crypto) || (! part)) + INTERNAL_ERROR ("_notmuch_message_crypto_potential_payload() got NULL for %s\n", + msg_crypto? "part" : "msg_crypto"); /* only fire on the first payload part encountered */ if (msg_crypto->payload_encountered) - return NOTMUCH_STATUS_SUCCESS; + return false; /* the first child of multipart/encrypted that matches the * encryption protocol should be "control information" metadata, @@ -159,7 +161,7 @@ _notmuch_message_crypto_potential_payload (_notmuch_message_crypto_t *msg_crypto if (ct && enc_type) { const char *part_type = g_mime_content_type_get_mime_type (ct); if (part_type && strcmp (part_type, enc_type) == 0) - return NOTMUCH_STATUS_SUCCESS; + return false; } } @@ -169,7 +171,7 @@ _notmuch_message_crypto_potential_payload (_notmuch_message_crypto_t *msg_crypto * envelope: */ if ((msg_crypto->decryption_status != NOTMUCH_MESSAGE_DECRYPTED_FULL) && (msg_crypto->sig_list == NULL)) - return NOTMUCH_STATUS_SUCCESS; + return false; /* Verify that this payload has headers that are intended to be * exported to the larger message: */ @@ -196,7 +198,7 @@ _notmuch_message_crypto_potential_payload (_notmuch_message_crypto_t *msg_crypto msg_crypto->payload_subject = talloc_strdup (msg_crypto, subject); } - return NOTMUCH_STATUS_SUCCESS; + return true; } diff --git a/util/crypto.h b/util/crypto.h index c4411246..f8bda0d1 100644 --- a/util/crypto.h +++ b/util/crypto.h @@ -90,8 +90,11 @@ _notmuch_message_crypto_successful_decryption (_notmuch_message_crypto_t *msg_cr /* call potential_payload during a depth-first-search on a message * when encountering a message part that is not part of the envelope. + * + * Returns true if part is the root of the cryptographic payload of + * this message. */ -notmuch_status_t +bool _notmuch_message_crypto_potential_payload (_notmuch_message_crypto_t *msg_crypto, GMimeObject *part, GMimeObject *parent, int childnum); From ff3d873f0b80b023764fe9d3fc0609b81d82775e Mon Sep 17 00:00:00 2001 From: Daniel Kahn Gillmor Date: Thu, 29 Aug 2019 11:38:51 -0400 Subject: [PATCH 032/427] util/repair: add _notmuch_repair_crypto_payload_skip_legacy_display This is a utility function designed to make it easier to "fast-forward" past a legacy-display part associated with a cryptographic envelope, and show the user the intended message body. The bulk of the ugliness in here is in the test function _notmuch_crypto_payload_has_legacy_display, which tests all of the things we'd expect to be true in a a cryptographic payload that contains a legacy display part. Signed-off-by: Daniel Kahn Gillmor --- util/repair.c | 101 ++++++++++++++++++++++++++++++++++++++++++++++++++ util/repair.h | 17 +++++++++ 2 files changed, 118 insertions(+) diff --git a/util/repair.c b/util/repair.c index f91c1244..629e6f23 100644 --- a/util/repair.c +++ b/util/repair.c @@ -18,4 +18,105 @@ * Authors: Daniel Kahn Gillmor */ +#include #include "repair.h" + + +static bool +_notmuch_crypto_payload_has_legacy_display (GMimeObject *payload) +{ + GMimeMultipart *mpayload; + const char *protected_header_parameter; + GMimeTextPart *legacy_display; + char *legacy_display_header_text = NULL; + GMimeStream *stream = NULL; + GMimeParser *parser = NULL; + GMimeObject *legacy_header_object = NULL, *first; + GMimeHeaderList *legacy_display_headers = NULL, *protected_headers = NULL; + bool ret = false; + + if (! g_mime_content_type_is_type (g_mime_object_get_content_type (payload), + "multipart", "mixed")) + return false; + protected_header_parameter = g_mime_object_get_content_type_parameter (payload, "protected-headers"); + if ((! protected_header_parameter) || strcmp (protected_header_parameter, "v1")) + return false; + if (! GMIME_IS_MULTIPART (payload)) + return false; + mpayload = GMIME_MULTIPART (payload); + if (mpayload == NULL) + return false; + if (g_mime_multipart_get_count (mpayload) != 2) + return false; + first = g_mime_multipart_get_part (mpayload, 0); + if (! g_mime_content_type_is_type (g_mime_object_get_content_type (first), + "text", "rfc822-headers")) + return false; + protected_header_parameter = g_mime_object_get_content_type_parameter (first, "protected-headers"); + if ((! protected_header_parameter) || strcmp (protected_header_parameter, "v1")) + return false; + if (! GMIME_IS_TEXT_PART (first)) + return false; + + /* ensure that the headers in the first part all match the values + * found in the payload's own protected headers! if they don't, + * we should not treat this as a valid "legacy-display" part. + * + * Crafting a GMimeHeaderList object from the content of the + * text/rfc822-headers part is pretty clumsy; we should probably + * push something into GMime that makes this a one-shot + * operation. */ + if ((protected_headers = g_mime_object_get_header_list (payload), protected_headers) && + (legacy_display = GMIME_TEXT_PART (first), legacy_display) && + (legacy_display_header_text = g_mime_text_part_get_text (legacy_display), legacy_display_header_text) && + (stream = g_mime_stream_mem_new_with_buffer (legacy_display_header_text, strlen (legacy_display_header_text)), stream) && + (g_mime_stream_write (stream, "\r\n\r\n", 4) == 4) && + (g_mime_stream_seek (stream, 0, GMIME_STREAM_SEEK_SET) == 0) && + (parser = g_mime_parser_new_with_stream (stream), parser) && + (legacy_header_object = g_mime_parser_construct_part (parser, NULL), legacy_header_object) && + (legacy_display_headers = g_mime_object_get_header_list (legacy_header_object), legacy_display_headers)) { + /* walk through legacy_display_headers, comparing them against + * their values in the protected_headers: */ + ret = true; + for (int i = 0; i < g_mime_header_list_get_count (legacy_display_headers); i++) { + GMimeHeader *dh = g_mime_header_list_get_header_at (legacy_display_headers, i); + if (dh == NULL) { + ret = false; + goto DONE; + } + GMimeHeader *ph = g_mime_header_list_get_header (protected_headers, g_mime_header_get_name (dh)); + if (ph == NULL) { + ret = false; + goto DONE; + } + const char *dhv = g_mime_header_get_value (dh); + const char *phv = g_mime_header_get_value (ph); + if (dhv == NULL || phv == NULL || strcmp (dhv, phv)) { + ret = false; + goto DONE; + } + } + } + + DONE: + if (legacy_display_header_text) + g_free (legacy_display_header_text); + if (stream) + g_object_unref (stream); + if (parser) + g_object_unref (parser); + if (legacy_header_object) + g_object_unref (legacy_header_object); + + return ret; +} + +GMimeObject * +_notmuch_repair_crypto_payload_skip_legacy_display (GMimeObject *payload) +{ + if (_notmuch_crypto_payload_has_legacy_display (payload)) { + return g_mime_multipart_get_part (GMIME_MULTIPART (payload), 1); + } else { + return payload; + } +} diff --git a/util/repair.h b/util/repair.h index 70e2b7bc..9974d693 100644 --- a/util/repair.h +++ b/util/repair.h @@ -11,6 +11,23 @@ extern "C" { * techniques that are designed to improve the user experience of * notmuch */ +/* If payload is a cryptographic payload within an encrypted message, and + * it has a "legacy display" part, then we can skip over it and jump + * to the actual content, because notmuch already handles protected + * headers appropriately. + * + * This function either returns payload directly (if it does not have + * a "legacy display" part), or it returns a pointer to its + * content-bearing subpart, with the "legacy display" part and the + * surrounding multipart/mixed object bypassed. + * + * No new objects are created by calling this function, and the + * returned object will only be released when the original part is + * disposed of. + */ +GMimeObject * +_notmuch_repair_crypto_payload_skip_legacy_display (GMimeObject *payload); + #ifdef __cplusplus } #endif From c61e22d5cb9a7e5b03f9fcdb9911b4b4a641af2b Mon Sep 17 00:00:00 2001 From: Daniel Kahn Gillmor Date: Thu, 29 Aug 2019 11:38:52 -0400 Subject: [PATCH 033/427] cli/{show,reply}: skip over legacy-display parts Make use of the previous changes to fast-forward past any legacy-display parts during "notmuch show" and "notmuch reply". Signed-off-by: Daniel Kahn Gillmor --- mime-node.c | 11 ++++++++++- test/T356-protected-headers.sh | 2 -- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/mime-node.c b/mime-node.c index abb6dd84..599d3b65 100644 --- a/mime-node.c +++ b/mime-node.c @@ -333,7 +333,16 @@ _mime_node_set_up_part (mime_node_t *node, GMimeObject *part, int numchild) node_verify (node, part); } } else { - (void) _notmuch_message_crypto_potential_payload (node->ctx->msg_crypto, part, node->parent ? node->parent->part : NULL, numchild); + if (_notmuch_message_crypto_potential_payload (node->ctx->msg_crypto, part, node->parent ? node->parent->part : NULL, numchild) && + node->ctx->msg_crypto->decryption_status == NOTMUCH_MESSAGE_DECRYPTED_FULL) { + GMimeObject *clean_payload = _notmuch_repair_crypto_payload_skip_legacy_display (part); + if (clean_payload != part) { + /* only one layer of recursion is possible here + * because there can be only a single cryptographic + * payload: */ + return _mime_node_set_up_part (node, clean_payload, numchild); + } + } } return true; diff --git a/test/T356-protected-headers.sh b/test/T356-protected-headers.sh index 43dfffe6..867b8722 100755 --- a/test/T356-protected-headers.sh +++ b/test/T356-protected-headers.sh @@ -137,14 +137,12 @@ id:protected-header@crypto.notmuchmail.org id:subjectless-protected-header@crypto.notmuchmail.org' test_begin_subtest "when rendering protected headers, avoid rendering legacy-display part" -test_subtest_known_broken output=$(notmuch show --format=json id:protected-with-legacy-display@crypto.notmuchmail.org) test_json_nodes <<<"$output" \ 'subject:[0][0][0]["headers"]["Subject"]="Interrupting Cow"' \ 'no_legacy_display:[0][0][0]["body"][0]["content"][1]["content-type"]="text/plain"' test_begin_subtest "when replying, avoid rendering legacy-display part" -test_subtest_known_broken output=$(notmuch reply --format=json id:protected-with-legacy-display@crypto.notmuchmail.org) test_json_nodes <<<"$output" \ 'no_legacy_display:["original"]["body"][0]["content"][1]["content-type"]="text/plain"' From 9829533e92f84ff151279e5202e0420bc70a8863 Mon Sep 17 00:00:00 2001 From: Daniel Kahn Gillmor Date: Thu, 29 Aug 2019 11:38:53 -0400 Subject: [PATCH 034/427] index: avoid indexing legacy-display parts When we notice a legacy-display part during indexing, it makes more sense to avoid indexing it as part of the message body. Given that the protected subject will already be indexed, there is no need to index this part at all, so we skip over it. If this happens during indexing, we set a property on the message: index.repaired=skip-protected-headers-legacy-display Signed-off-by: Daniel Kahn Gillmor --- doc/man7/notmuch-properties.rst | 6 ++++++ lib/index.cc | 20 ++++++++++++++++---- test/T356-protected-headers.sh | 2 -- 3 files changed, 22 insertions(+), 6 deletions(-) diff --git a/doc/man7/notmuch-properties.rst b/doc/man7/notmuch-properties.rst index 2e610683..e2db2ef5 100644 --- a/doc/man7/notmuch-properties.rst +++ b/doc/man7/notmuch-properties.rst @@ -121,6 +121,12 @@ of its normal activity. ``index.repaired`` property to note the type of repair(s) it performed. + ``index.repaired=skip-protected-headers-legacy-display`` indicates + that when indexing the cleartext of an encrypted message, notmuch + skipped over a "legacy-display" text/rfc822-headers part that it + found in that message, since it was able to index the built-in + protected headers directly. + SEE ALSO ======== diff --git a/lib/index.cc b/lib/index.cc index 08cc84e2..1301d78a 100644 --- a/lib/index.cc +++ b/lib/index.cc @@ -435,8 +435,14 @@ _index_mime_part (notmuch_message_t *message, continue; } child = g_mime_multipart_get_part (multipart, i); - (void) _notmuch_message_crypto_potential_payload (msg_crypto, child, part, i); - _index_mime_part (message, indexopts, child, msg_crypto); + GMimeObject *toindex = child; + if (_notmuch_message_crypto_potential_payload (msg_crypto, child, part, i) && + msg_crypto->decryption_status == NOTMUCH_MESSAGE_DECRYPTED_FULL) { + toindex = _notmuch_repair_crypto_payload_skip_legacy_display (child); + if (toindex != child) + notmuch_message_add_property (message, "index.repaired", "skip-protected-headers-legacy-display"); + } + _index_mime_part (message, indexopts, toindex, msg_crypto); } return; } @@ -573,8 +579,14 @@ _index_encrypted_mime_part (notmuch_message_t *message, } g_object_unref (decrypt_result); } - _notmuch_message_crypto_potential_payload (msg_crypto, clear, GMIME_OBJECT (encrypted_data), GMIME_MULTIPART_ENCRYPTED_CONTENT); - _index_mime_part (message, indexopts, clear, msg_crypto); + GMimeObject *toindex = clear; + if (_notmuch_message_crypto_potential_payload (msg_crypto, clear, GMIME_OBJECT (encrypted_data), GMIME_MULTIPART_ENCRYPTED_CONTENT) && + msg_crypto->decryption_status == NOTMUCH_MESSAGE_DECRYPTED_FULL) { + toindex = _notmuch_repair_crypto_payload_skip_legacy_display (clear); + if (toindex != clear) + notmuch_message_add_property (message, "index.repaired", "skip-protected-headers-legacy-display"); + } + _index_mime_part (message, indexopts, toindex, msg_crypto); g_object_unref (clear); status = notmuch_message_add_property (message, "index.decryption", "success"); diff --git a/test/T356-protected-headers.sh b/test/T356-protected-headers.sh index 867b8722..925805df 100755 --- a/test/T356-protected-headers.sh +++ b/test/T356-protected-headers.sh @@ -148,12 +148,10 @@ test_json_nodes <<<"$output" \ 'no_legacy_display:["original"]["body"][0]["content"][1]["content-type"]="text/plain"' test_begin_subtest "do not treat legacy-display part as body when indexing" -test_subtest_known_broken output=$(notmuch search --output=messages body:interrupting) test_expect_equal "$output" '' test_begin_subtest "identify message that had a legacy display part skipped during indexing" -test_subtest_known_broken output=$(notmuch search --output=messages property:index.repaired=skip-protected-headers-legacy-display) test_expect_equal "$output" id:protected-with-legacy-display@crypto.notmuchmail.org From e0e55c5b6fd27a96601de95cfcec2b67d8a20c76 Mon Sep 17 00:00:00 2001 From: Tomi Ollila Date: Sun, 1 Sep 2019 23:09:46 +0300 Subject: [PATCH 035/427] configure: disallow whitespace in paths, extend checks to $PWD Whitespace in $NOTMUCH_SRCDIR (and $PWD) may work in builds, but definitely will not work in tests. It would be difficult to make tests support whitespace in test filename paths -- and fragile to maintain if done. So it is just easier and safer to disallow whitespace there. In case of out of tree build $NOTMUCH_SRCDIR differs from $PWD (current directory). Extend this whitespace, and also previously made unsafe characters check to $PWD too. --- configure | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/configure b/configure index ef81e71b..3c148e12 100755 --- a/configure +++ b/configure @@ -31,6 +31,22 @@ case $NOTMUCH_SRCDIR in ( *\'* | *['\"`$']* ) exit 1 esac +case $PWD in ( *\'* | *['\"`$']* ) + echo "Definitely unsafe characters in current directory '$PWD'". + exit 1 +esac + +# In case of whitespace, builds may work, tests definitely will not. +case $NOTMUCH_SRCDIR in ( *["$IFS"]* ) + echo "Whitespace in source path '$NOTMUCH_SRCDIR' not supported". + exit 1 +esac + +case $PWD in ( *["$IFS"]* ) + echo "Whitespace in current directory '$PWD' not supported". + exit 1 +esac + subdirs="util compat lib parse-time-string completion doc emacs" subdirs="${subdirs} performance-test test test/test-databases" subdirs="${subdirs} bindings" From cb522fb06ef534a485408de608984d1be6dbadfa Mon Sep 17 00:00:00 2001 From: Daniel Kahn Gillmor Date: Mon, 27 May 2019 22:46:53 -0400 Subject: [PATCH 036/427] test: add test for "Mixed-Up Mime" message mangling Some MTAs mangle e-mail messages in transit in ways that are repairable. Microsoft Exchange (in particular, the version running today on Office365's mailservers) appears to mangle multipart/encrypted messages in a way that makes them undecryptable by the recipient. I've documented this in section 4.1 "Mixed-up encryption" of draft -00 of https://tools.ietf.org/html/draft-dkg-openpgp-pgpmime-message-mangling Fortunately, it's possible to repair such a message, and notmuch can do that so that a user who receives an encrypted message from a user of office365.com can still decrypt the message. Enigmail already knows about this particular kind of mangling. It describes it as "broken PGP email format probably caused by an old Exchange server", and it tries to repair by directly changing the message held by the user. if this kind of repair goes wrong, the repair process can cause data loss (https://sourceforge.net/p/enigmail/bugs/987/, yikes). The tests introduced here are currently broken. In subsequent patches, i'll introduce a non-destructive form of repair for notmuch so that notmuch users can read mail that has been mangled in this way, and the tests will succeed. Signed-off-by: Daniel Kahn Gillmor --- test/T351-pgpmime-mangling.sh | 36 ++++++++++++++++++++++++++++++ test/corpora/mangling/mixed-up.eml | 33 +++++++++++++++++++++++++++ 2 files changed, 69 insertions(+) create mode 100755 test/T351-pgpmime-mangling.sh create mode 100644 test/corpora/mangling/mixed-up.eml diff --git a/test/T351-pgpmime-mangling.sh b/test/T351-pgpmime-mangling.sh new file mode 100755 index 00000000..f65b8a24 --- /dev/null +++ b/test/T351-pgpmime-mangling.sh @@ -0,0 +1,36 @@ +#!/usr/bin/env bash + +test_description='PGP/MIME message mangling' +. $(dirname "$0")/test-lib.sh || exit 1 + +add_gnupg_home +add_email_corpus mangling + +bodytext='["body"][0]["content"][1]["content"]="The password is \"abcd1234!\", please do not tell anyone.\n"' + +test_begin_subtest "show 'Mixed-Up' mangled PGP/MIME message correctly" +test_subtest_known_broken +output=$(notmuch show --format=json --decrypt=true id:mixed-up@mangling.notmuchmail.org) +test_json_nodes <<<"$output" \ + 'body:[0][0][0]'"$bodytext" + +test_begin_subtest "reply to 'Mixed-Up' mangled PGP/MIME message correctly" +test_subtest_known_broken +output=$(notmuch reply --format=json --decrypt=true id:mixed-up@mangling.notmuchmail.org) +test_json_nodes <<<"$output" \ + 'body:["original"]'"$bodytext" + +test_begin_subtest "repaired 'Mixed-up' messages can be found with index.repaired=mixedup" +test_subtest_known_broken +output=$(notmuch search --output=messages property:index.repaired=mixedup) +test_expect_equal "$output" id:mixed-up@mangling.notmuchmail.org + +test_begin_subtest "index cleartext of 'Mixed-Up' mangled PGP/MIME message" +test_expect_success 'notmuch reindex --decrypt=true id:mixed-up@mangling.notmuchmail.org' + +test_begin_subtest "search cleartext of 'Mixed-Up' mangled PGP/MIME message" +test_subtest_known_broken +output=$(notmuch search --output=messages body:password) +test_expect_equal "$output" id:mixed-up@mangling.notmuchmail.org + +test_done diff --git a/test/corpora/mangling/mixed-up.eml b/test/corpora/mangling/mixed-up.eml new file mode 100644 index 00000000..a09f6191 --- /dev/null +++ b/test/corpora/mangling/mixed-up.eml @@ -0,0 +1,33 @@ +From: test_suite@notmuchmail.org +To: test_suite@notmuchmail.org +Subject: Here is the password +Date: Sat, 01 Jan 2000 12:00:00 +0000 +Message-ID: +MIME-Version: 1.0 +Content-Type: multipart/mixed; boundary="=-=-=" + +--=-=-= +Content-Type: text/plain; charset="us-ascii" +Content-Transfer-Encoding: quoted-printable + + +--=-=-= +Content-Type: application/pgp-encrypted +Content-Transfer-Encoding: base64 + +VmVyc2lvbjogMQ0K + +--=-=-= +Content-Type: application/octet-stream +Content-Transfer-Encoding: base64 + +LS0tLS1CRUdJTiBQR1AgTUVTU0FHRS0tLS0tDQoNCmhJd0R4RTAyM3ExVXF4WUJCQUNwNzBlN0tQ +eTlPWWFoZUlya0x6bWhxMWxScW15NTFhTDFqQkwwSy9xTjdyZksNCkJaRUcxY1I4amVMalRGZFBL +UExWS0pJODByN0ZnS0kweXd2V3ZsNlIxYUUxVHk1Qm5WWFQ5WHpDckVIN2ZxQ2wNClNLSzgyRXZv +bFhUb2hBWkhVcmg2SzY2ZVFRVFRJQUMxbjdCMEE4aEVyemtnYU00K3NlTjNMbHZlelQ2VExOS00N +CkFUcHFzRWJNMk1WckdndzBiM29Vc0dHQVBFdDJNbWpORVlzcmlLbnF3dDZkSkRaYy8vWHloamdN +UWF5aUQ4ZGENCk4xZ1Qzb3FndS9nS0NwQlpEWXpIZjlPdFZpMlVubEZEV3k2cnJNWkxqV0RuSXY0 +dmU5UG4vcW9sd0hWanpkSjENClpmak5DNXQwejNYQURLR3JqTjl3dXRyNHFtN1NUVzFySEFYSFA2 +OFRRVHhJMHFnSktqUFhOS1dFdzZnPQ0KPXBKRzQNCi0tLS0tRU5EIFBHUCBNRVNTQUdFLS0tLS0N +Cg== +--=-=-=-- From 67666538b3d8e0d10d2778b55d0cd2549a73b274 Mon Sep 17 00:00:00 2001 From: Daniel Kahn Gillmor Date: Tue, 28 May 2019 01:45:12 -0400 Subject: [PATCH 037/427] util/repair: identify and repair "Mixed Up" mangled messages Implement a functional identification and repair process for "Mixed Up" MIME messages as described in https://tools.ietf.org/html/draft-dkg-openpgp-pgpmime-message-mangling-00#section-4.1 The detection test is not entirely complete, in that it does not verify the contents of the latter two message subparts, but this is probably safe to skip, because those two parts are unlikely to be readable anyway, and the only part we are effectively omitting (the first subpart) is guaranteed to be empty anyway, so its removal can be reversed if you want to do so. I've left FIXMEs in the code so that anyone excited about adding these additional checks can see where to put them in. Signed-off-by: Daniel Kahn Gillmor --- util/repair.c | 84 +++++++++++++++++++++++++++++++++++++++++++++++++++ util/repair.h | 10 ++++++ 2 files changed, 94 insertions(+) diff --git a/util/repair.c b/util/repair.c index 629e6f23..9fba97b7 100644 --- a/util/repair.c +++ b/util/repair.c @@ -120,3 +120,87 @@ _notmuch_repair_crypto_payload_skip_legacy_display (GMimeObject *payload) return payload; } } + +/* see + * https://tools.ietf.org/html/draft-dkg-openpgp-pgpmime-message-mangling-00#section-4.1.1 */ +static bool +_notmuch_is_mixed_up_mangled (GMimeObject *part) +{ + GMimeMultipart *mpart = NULL; + GMimeObject *parts[3] = {NULL, NULL, NULL}; + GMimeContentType *type = NULL; + char *prelude_string = NULL; + bool prelude_is_empty; + + if (part == NULL) + return false; + type = g_mime_object_get_content_type (part); + if (type == NULL) + return false; + if (! g_mime_content_type_is_type (type, "multipart", "mixed")) + return false; + if (! GMIME_IS_MULTIPART (part)) /* probably impossible */ + return false; + mpart = GMIME_MULTIPART (part); + if (mpart == NULL) + return false; + if (g_mime_multipart_get_count (mpart) != 3) + return false; + parts[0] = g_mime_multipart_get_part (mpart, 0); + if (! g_mime_content_type_is_type (g_mime_object_get_content_type (parts[0]), + "text", "plain")) + return false; + if (! GMIME_IS_TEXT_PART (parts[0])) + return false; + parts[1] = g_mime_multipart_get_part (mpart, 1); + if (! g_mime_content_type_is_type (g_mime_object_get_content_type (parts[1]), + "application", "pgp-encrypted")) + return false; + parts[2] = g_mime_multipart_get_part (mpart, 2); + if (! g_mime_content_type_is_type (g_mime_object_get_content_type (parts[2]), + "application", "octet-stream")) + return false; + + /* Is parts[0] length 0? */ + prelude_string = g_mime_text_part_get_text (GMIME_TEXT_PART (parts[0])); + prelude_is_empty = (prelude_string[0] == '\0'); + g_free (prelude_string); + if (! prelude_is_empty) + return false; + + /* FIXME: after decoding and stripping whitespace, is parts[1] + * subpart just "Version: 1" ? */ + + /* FIXME: can we determine that parts[2] subpart is *only* PGP + * encrypted data? I tried g_mime_part_get_openpgp_data () but + * found https://github.com/jstedfast/gmime/issues/60 */ + + return true; +} + + +/* see + * https://tools.ietf.org/html/draft-dkg-openpgp-pgpmime-message-mangling-00#section-4.1.2 */ +GMimeObject * +_notmuch_repair_mixed_up_mangled (GMimeObject *part) +{ + GMimeMultipart *mpart = NULL, *mpart_ret = NULL; + GMimeObject *ret = NULL; + + if (! _notmuch_is_mixed_up_mangled (part)) + return NULL; + mpart = GMIME_MULTIPART (part); + ret = GMIME_OBJECT (g_mime_multipart_encrypted_new ()); + if (ret == NULL) + return NULL; + mpart_ret = GMIME_MULTIPART (ret); + if (mpart_ret == NULL) { + g_object_unref (ret); + return NULL; + } + g_mime_object_set_content_type_parameter (ret, "protocol", "application/pgp-encrypted"); + + g_mime_multipart_insert (mpart_ret, 0, g_mime_multipart_get_part (mpart, 1)); + g_mime_multipart_insert (mpart_ret, 1, g_mime_multipart_get_part (mpart, 2)); + return ret; +} diff --git a/util/repair.h b/util/repair.h index 9974d693..492f5a20 100644 --- a/util/repair.h +++ b/util/repair.h @@ -25,9 +25,19 @@ extern "C" { * returned object will only be released when the original part is * disposed of. */ + GMimeObject * _notmuch_repair_crypto_payload_skip_legacy_display (GMimeObject *payload); +/* Detecting and repairing "Mixed-Up MIME mangling". see + * https://tools.ietf.org/html/draft-dkg-openpgp-pgpmime-message-mangling-00#section-4.1 + * If this returns NULL, the message was probably not "Mixed up". If + * it returns non-NULL, then there is a newly-allocated MIME part that + * represents the repaired version. The caller is responsible for + * ensuring that any returned object is freed with g_object_unref. */ +GMimeObject * +_notmuch_repair_mixed_up_mangled (GMimeObject *part); + #ifdef __cplusplus } #endif From 4b1a8fd18364a6e1f1a6e987c64aae760505999e Mon Sep 17 00:00:00 2001 From: Daniel Kahn Gillmor Date: Tue, 28 May 2019 14:42:26 -0400 Subject: [PATCH 038/427] index: repair "Mixed Up" messages before indexing. When encountering a message that has been mangled in the "mixed up" way by an intermediate MTA, notmuch should instead repair it and index the repaired form. When it does this, it also associates the index.repaired=mixedup property with the message. If a problem is found with this repair process, or an improved repair process is proposed later, this should make it easy for people to reindex the relevant message. The property will also hopefully make it easier to diagnose this particular problem in the future. Signed-off-by: Daniel Kahn Gillmor --- doc/man7/notmuch-properties.rst | 6 ++++++ lib/index.cc | 22 +++++++++++++++++----- test/T351-pgpmime-mangling.sh | 2 -- 3 files changed, 23 insertions(+), 7 deletions(-) diff --git a/doc/man7/notmuch-properties.rst b/doc/man7/notmuch-properties.rst index e2db2ef5..a7d91d67 100644 --- a/doc/man7/notmuch-properties.rst +++ b/doc/man7/notmuch-properties.rst @@ -127,6 +127,12 @@ of its normal activity. found in that message, since it was able to index the built-in protected headers directly. + ``index.repaired=mixedup`` indicates the repair of a "Mixed Up" + encrypted PGP/MIME message, a mangling typically produced by + Microsoft's Exchange MTA. See + https://tools.ietf.org/html/draft-dkg-openpgp-pgpmime-message-mangling + for more information. + SEE ALSO ======== diff --git a/lib/index.cc b/lib/index.cc index 1301d78a..158ba5cf 100644 --- a/lib/index.cc +++ b/lib/index.cc @@ -387,11 +387,20 @@ _index_mime_part (notmuch_message_t *message, GMimeContentType *content_type; char *body; const char *charset; + GMimeObject *repaired_part = NULL; if (! part) { _notmuch_database_log (notmuch_message_get_database (message), "Warning: Not indexing empty mime part.\n"); - return; + goto DONE; + } + + repaired_part = _notmuch_repair_mixed_up_mangled (part); + if (repaired_part) { + /* This was likely "Mixed Up" in transit! We will instead use + * the more likely-to-be-correct variant. */ + notmuch_message_add_property (message, "index.repaired", "mixedup"); + part = repaired_part; } _index_content_type (message, part); @@ -444,7 +453,7 @@ _index_mime_part (notmuch_message_t *message, } _index_mime_part (message, indexopts, toindex, msg_crypto); } - return; + goto DONE; } if (GMIME_IS_MESSAGE_PART (part)) { @@ -454,14 +463,14 @@ _index_mime_part (notmuch_message_t *message, _index_mime_part (message, indexopts, g_mime_message_get_mime_part (mime_message), msg_crypto); - return; + goto DONE; } if (! (GMIME_IS_PART (part))) { _notmuch_database_log (notmuch_message_get_database (message), "Warning: Not indexing unknown mime part: %s.\n", g_type_name (G_OBJECT_TYPE (part))); - return; + goto DONE; } disposition = g_mime_object_get_content_disposition (part); @@ -475,7 +484,7 @@ _index_mime_part (notmuch_message_t *message, /* XXX: Would be nice to call out to something here to parse * the attachment into text and then index that. */ - return; + goto DONE; } byte_array = g_byte_array_new (); @@ -521,6 +530,9 @@ _index_mime_part (notmuch_message_t *message, free (body); } + DONE: + if (repaired_part) + g_object_unref (repaired_part); } /* descend (if desired) into the cleartext part of an encrypted MIME diff --git a/test/T351-pgpmime-mangling.sh b/test/T351-pgpmime-mangling.sh index f65b8a24..4555f937 100755 --- a/test/T351-pgpmime-mangling.sh +++ b/test/T351-pgpmime-mangling.sh @@ -21,7 +21,6 @@ test_json_nodes <<<"$output" \ 'body:["original"]'"$bodytext" test_begin_subtest "repaired 'Mixed-up' messages can be found with index.repaired=mixedup" -test_subtest_known_broken output=$(notmuch search --output=messages property:index.repaired=mixedup) test_expect_equal "$output" id:mixed-up@mangling.notmuchmail.org @@ -29,7 +28,6 @@ test_begin_subtest "index cleartext of 'Mixed-Up' mangled PGP/MIME message" test_expect_success 'notmuch reindex --decrypt=true id:mixed-up@mangling.notmuchmail.org' test_begin_subtest "search cleartext of 'Mixed-Up' mangled PGP/MIME message" -test_subtest_known_broken output=$(notmuch search --output=messages body:password) test_expect_equal "$output" id:mixed-up@mangling.notmuchmail.org From 23bcd003637f091c88f7d0a601d5fee82bc8e936 Mon Sep 17 00:00:00 2001 From: Daniel Kahn Gillmor Date: Tue, 28 May 2019 14:46:48 -0400 Subject: [PATCH 039/427] cli/{show,reply}: use repaired form of "Mixed Up" mangled messages When showing or replying to a message that has been mangled in transit by an MTA in the "Mixed up" way, notmuch should instead use the repaired form of the message. Tracking the repaired GMimeObject for the lifetime of the mime_node so that it is cleaned up properly is probably the trickiest part of this patch, but the choices here are based on the idea that the mime_node_context is the memory manager for the whole mime_node tree in the first place, so new GMimeObject tree created on-the-fly during message parsing should be disposed of in the same place. Signed-off-by: Daniel Kahn Gillmor --- mime-node.c | 22 ++++++++++++++++++++++ test/T351-pgpmime-mangling.sh | 2 -- 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/mime-node.c b/mime-node.c index 599d3b65..d4996a33 100644 --- a/mime-node.c +++ b/mime-node.c @@ -36,6 +36,9 @@ typedef struct mime_node_context { GMimeMessage *mime_message; _notmuch_message_crypto_t *msg_crypto; + /* repaired/unmangled parts that will need to be cleaned up */ + GSList *repaired_parts; + /* Context provided by the caller. */ _notmuch_crypto_t *crypto; } mime_node_context_t; @@ -52,9 +55,21 @@ _mime_node_context_free (mime_node_context_t *res) if (res->stream) g_object_unref (res->stream); + if (res->repaired_parts) + g_slist_free_full (res->repaired_parts, g_object_unref); + return 0; } +/* keep track of objects that need to be destroyed when the mime node + * context goes away. */ +static void +_mime_node_context_track_repaired_part (mime_node_context_t *ctx, GMimeObject *part) +{ + if (part) + ctx->repaired_parts = g_slist_prepend (ctx->repaired_parts, part); +} + const _notmuch_message_crypto_t * mime_node_get_message_crypto_status (mime_node_t *node) { @@ -298,6 +313,13 @@ _mime_node_set_up_part (mime_node_t *node, GMimeObject *part, int numchild) node->part = part; node->nchildren = 0; } else if (GMIME_IS_MULTIPART (part)) { + GMimeObject *repaired_part = _notmuch_repair_mixed_up_mangled (part); + if (repaired_part) { + /* This was likely "Mixed Up" in transit! We replace it + * with the more likely-to-be-correct variant. */ + _mime_node_context_track_repaired_part (node->ctx, repaired_part); + part = repaired_part; + } node->part = part; node->nchildren = g_mime_multipart_get_count (GMIME_MULTIPART (part)); } else if (GMIME_IS_MESSAGE_PART (part)) { diff --git a/test/T351-pgpmime-mangling.sh b/test/T351-pgpmime-mangling.sh index 4555f937..71a68c05 100755 --- a/test/T351-pgpmime-mangling.sh +++ b/test/T351-pgpmime-mangling.sh @@ -9,13 +9,11 @@ add_email_corpus mangling bodytext='["body"][0]["content"][1]["content"]="The password is \"abcd1234!\", please do not tell anyone.\n"' test_begin_subtest "show 'Mixed-Up' mangled PGP/MIME message correctly" -test_subtest_known_broken output=$(notmuch show --format=json --decrypt=true id:mixed-up@mangling.notmuchmail.org) test_json_nodes <<<"$output" \ 'body:[0][0][0]'"$bodytext" test_begin_subtest "reply to 'Mixed-Up' mangled PGP/MIME message correctly" -test_subtest_known_broken output=$(notmuch reply --format=json --decrypt=true id:mixed-up@mangling.notmuchmail.org) test_json_nodes <<<"$output" \ 'body:["original"]'"$bodytext" From 7eb9615b30274033cc0c828244569c709906c40b Mon Sep 17 00:00:00 2001 From: Daniel Kahn Gillmor Date: Sun, 15 Sep 2019 14:02:03 -0400 Subject: [PATCH 040/427] Drop devel/printmimestructure (it is in mailscripts 0.11) mailscripts 0.11 now ships a derivative of devel/printmimestructure called email-print-mime-structure. Maintenance for that utility will happen in mailscripts from now on, so we should not track an independent copy of it in notmuch's source tree. See https://bugs.debian.org/939993 for more details about the adoption. Signed-off-by: Daniel Kahn Gillmor --- devel/printmimestructure | 69 ---------------------------------------- 1 file changed, 69 deletions(-) delete mode 100755 devel/printmimestructure diff --git a/devel/printmimestructure b/devel/printmimestructure deleted file mode 100755 index 70e0a5c0..00000000 --- a/devel/printmimestructure +++ /dev/null @@ -1,69 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -# Author: Daniel Kahn Gillmor -# License: GPLv3+ - -# This script reads a MIME message from stdin and produces a treelike -# representation on it stdout. - -# Example: -# -# 0 dkg@alice:~$ printmimestructure < 'Maildir/cur/1269025522.M338697P12023.monkey,S=6459,W=6963:2,Sa' -# └┬╴multipart/signed 6546 bytes -# ├─╴text/plain inline 895 bytes -# └─╴application/pgp-signature inline [signature.asc] 836 bytes -# 0 dkg@alice:~$ - - -# If you want to number the parts, i suggest piping the output through -# something like "cat -n" - -from __future__ import print_function - -import email -import sys - -def print_part(z, prefix): - fname = '' if z.get_filename() is None else ' [' + z.get_filename() + ']' - cset = '' if z.get_charset() is None else ' (' + z.get_charset() + ')' - disp = z.get_params(None, header='Content-Disposition') - if (disp is None): - disposition = '' - else: - disposition = '' - for d in disp: - if d[0] in [ 'attachment', 'inline' ]: - disposition = ' ' + d[0] - if z.is_multipart(): - nbytes = len(z.as_string()) - else: - nbytes = len(z.get_payload()) - - print('{}{}{}{}{} {:d} bytes'.format( - prefix, - z.get_content_type(), - cset, - disposition, - fname, - nbytes, - )) - -def test(z, prefix=''): - if (z.is_multipart()): - print_part(z, prefix+'┬╴') - if prefix.endswith('└'): - prefix = prefix.rpartition('└')[0] + ' ' - if prefix.endswith('├'): - prefix = prefix.rpartition('├')[0] + '│' - parts = z.get_payload() - i = 0 - while (i < parts.__len__()-1): - test(parts[i], prefix + '├') - i += 1 - test(parts[i], prefix + '└') - # FIXME: show epilogue? - else: - print_part(z, prefix+'─╴') - -test(email.message_from_file(sys.stdin), '└') From bb843f63fc0469c2660d5d0a7b49602fcfb2c879 Mon Sep 17 00:00:00 2001 From: Jakub Wilk Date: Wed, 20 Nov 2019 11:46:39 +0100 Subject: [PATCH 041/427] python: make some docstrings raw Fixes: notmuch/message.py:57: DeprecationWarning: invalid escape sequence \s notmuch/query.py:155: DeprecationWarning: invalid escape sequence \. notmuch/messages.py:89: DeprecationWarning: invalid escape sequence \s with Python >= 3.6. --- bindings/python/notmuch/message.py | 2 +- bindings/python/notmuch/messages.py | 2 +- bindings/python/notmuch/query.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/bindings/python/notmuch/message.py b/bindings/python/notmuch/message.py index 6e32b5f7..e71dbe3e 100644 --- a/bindings/python/notmuch/message.py +++ b/bindings/python/notmuch/message.py @@ -46,7 +46,7 @@ import sys class Message(Python3StringMixIn): - """Represents a single Email message + r"""Represents a single Email message Technically, this wraps the underlying *notmuch_message_t* structure. A user will usually not create these objects themselves diff --git a/bindings/python/notmuch/messages.py b/bindings/python/notmuch/messages.py index cae5da50..3801c666 100644 --- a/bindings/python/notmuch/messages.py +++ b/bindings/python/notmuch/messages.py @@ -32,7 +32,7 @@ from .tag import Tags from .message import Message class Messages(object): - """Represents a list of notmuch messages + r"""Represents a list of notmuch messages This object provides an iterator over a list of notmuch messages (Technically, it provides a wrapper for the underlying diff --git a/bindings/python/notmuch/query.py b/bindings/python/notmuch/query.py index cc70e2aa..bdc18790 100644 --- a/bindings/python/notmuch/query.py +++ b/bindings/python/notmuch/query.py @@ -140,7 +140,7 @@ class Query(object): _search_threads.restype = c_uint def search_threads(self): - """Execute a query for threads + r"""Execute a query for threads Execute a query for threads, returning a :class:`Threads` iterator. The returned threads are owned by the query and as such, will only be From a1139fb5ec3a202229513e5202ea7cce72f9384a Mon Sep 17 00:00:00 2001 From: David Edmondson Date: Mon, 2 Dec 2019 10:48:05 +0000 Subject: [PATCH 042/427] emacs: A prefix argument kills rather than browsing URLs In `notmuch-show', the "B" key (notmuch-show-browse-urls) will kill the URL if called with a prefix argument rather than browsing directly. --- emacs/notmuch-show.el | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/emacs/notmuch-show.el b/emacs/notmuch-show.el index e13ca3d7..ef2bf1e0 100644 --- a/emacs/notmuch-show.el +++ b/emacs/notmuch-show.el @@ -2540,12 +2540,16 @@ message." (push (match-string-no-properties 0) urls)) (reverse urls)))) -(defun notmuch-show-browse-urls () - "Offer to browse any URLs in the current message." - (interactive) - (let ((urls (notmuch-show--gather-urls))) +(defun notmuch-show-browse-urls (&optional kill) + "Offer to browse any URLs in the current message. +With a prefix argument, copy the URL to the kill ring rather than +browsing." + (interactive "P") + (let ((urls (notmuch-show--gather-urls)) + (prompt (if kill "Copy URL to kill ring: " "Browse URL: ")) + (fn (if kill #'kill-new #'browse-url))) (if urls - (browse-url (completing-read "Browse URL: " (cdr urls) nil nil (car urls))) + (funcall fn (completing-read prompt (cdr urls) nil nil (car urls))) (message "No URLs found.")))) (provide 'notmuch-show) From 5f9ea4d2908a597acaf0b809b6f27fa74b70520b Mon Sep 17 00:00:00 2001 From: William Casarin Date: Wed, 13 Nov 2019 14:57:52 -0800 Subject: [PATCH 043/427] emacs: bind M-RET to notmuch-tree-from-search-thread This is an unbound function that is quite useful. It opens a selected thread in notmuch-tree from the current search query. Signed-off-by: William Casarin --- emacs/notmuch.el | 1 + 1 file changed, 1 insertion(+) diff --git a/emacs/notmuch.el b/emacs/notmuch.el index 773d1206..0d68d123 100644 --- a/emacs/notmuch.el +++ b/emacs/notmuch.el @@ -188,6 +188,7 @@ there will be called at other points of notmuch execution." (define-key map "-" 'notmuch-search-remove-tag) (define-key map "+" 'notmuch-search-add-tag) (define-key map (kbd "RET") 'notmuch-search-show-thread) + (define-key map (kbd "M-RET") 'notmuch-tree-from-search-thread) (define-key map "Z" 'notmuch-tree-from-search-current-query) map) "Keymap for \"notmuch search\" buffers.") From 83c2d158983875bf77a9b7662894df585b61741c Mon Sep 17 00:00:00 2001 From: Floris Bruynooghe Date: Tue, 8 Oct 2019 23:03:12 +0200 Subject: [PATCH 044/427] Introduce CFFI-based python bindings This introduces CFFI-based Python3-only bindings. The bindings aim at: - Better performance on pypy - Easier to use Python-C interface - More "pythonic" - The API should not allow invalid operations - Use native object protocol where possible - Memory safety; whatever you do from python, it should not coredump. --- AUTHORS | 1 + bindings/python-cffi/MANIFEST.in | 2 + bindings/python-cffi/notdb/__init__.py | 62 ++ bindings/python-cffi/notdb/_base.py | 238 +++++++ bindings/python-cffi/notdb/_build.py | 302 +++++++++ bindings/python-cffi/notdb/_database.py | 705 ++++++++++++++++++++ bindings/python-cffi/notdb/_errors.py | 112 ++++ bindings/python-cffi/notdb/_message.py | 691 +++++++++++++++++++ bindings/python-cffi/notdb/_query.py | 83 +++ bindings/python-cffi/notdb/_tags.py | 338 ++++++++++ bindings/python-cffi/notdb/_thread.py | 190 ++++++ bindings/python-cffi/setup.py | 22 + bindings/python-cffi/tests/conftest.py | 142 ++++ bindings/python-cffi/tests/test_base.py | 116 ++++ bindings/python-cffi/tests/test_database.py | 326 +++++++++ bindings/python-cffi/tests/test_message.py | 226 +++++++ bindings/python-cffi/tests/test_tags.py | 177 +++++ bindings/python-cffi/tests/test_thread.py | 102 +++ bindings/python-cffi/tox.ini | 16 + 19 files changed, 3851 insertions(+) create mode 100644 bindings/python-cffi/MANIFEST.in create mode 100644 bindings/python-cffi/notdb/__init__.py create mode 100644 bindings/python-cffi/notdb/_base.py create mode 100644 bindings/python-cffi/notdb/_build.py create mode 100644 bindings/python-cffi/notdb/_database.py create mode 100644 bindings/python-cffi/notdb/_errors.py create mode 100644 bindings/python-cffi/notdb/_message.py create mode 100644 bindings/python-cffi/notdb/_query.py create mode 100644 bindings/python-cffi/notdb/_tags.py create mode 100644 bindings/python-cffi/notdb/_thread.py create mode 100644 bindings/python-cffi/setup.py create mode 100644 bindings/python-cffi/tests/conftest.py create mode 100644 bindings/python-cffi/tests/test_base.py create mode 100644 bindings/python-cffi/tests/test_database.py create mode 100644 bindings/python-cffi/tests/test_message.py create mode 100644 bindings/python-cffi/tests/test_tags.py create mode 100644 bindings/python-cffi/tests/test_thread.py create mode 100644 bindings/python-cffi/tox.ini diff --git a/AUTHORS b/AUTHORS index 5fe5006f..6a05b441 100644 --- a/AUTHORS +++ b/AUTHORS @@ -28,3 +28,4 @@ ideas, inspiration, testing or feedback): Martin Krafft Keith Packard Jamey Sharp +Google LLC diff --git a/bindings/python-cffi/MANIFEST.in b/bindings/python-cffi/MANIFEST.in new file mode 100644 index 00000000..9ef81f24 --- /dev/null +++ b/bindings/python-cffi/MANIFEST.in @@ -0,0 +1,2 @@ +include MANIFEST.in +include tox.ini diff --git a/bindings/python-cffi/notdb/__init__.py b/bindings/python-cffi/notdb/__init__.py new file mode 100644 index 00000000..67051df5 --- /dev/null +++ b/bindings/python-cffi/notdb/__init__.py @@ -0,0 +1,62 @@ +"""Pythonic API to the notmuch database. + +Creating Objects +================ + +Only the :class:`Database` object is meant to be created by the user. +All other objects should be created from this initial object. Users +should consider their signatures implementation details. + +Errors +====== + +All errors occuring due to errors from the underlying notmuch database +are subclasses of the :exc:`NotmuchError`. Due to memory management +it is possible to try and use an object after it has been freed. In +this case a :exc:`ObjectDestoryedError` will be raised. + +Memory Management +================= + +Libnotmuch uses a hierarchical memory allocator, this means all +objects have a strict parent-child relationship and when the parent is +freed all the children are freed as well. This has some implications +for these Python bindings as parent objects need to be kept alive. +This is normally schielded entirely from the user however and the +Python objects automatically make sure the right references are kept +alive. It is however the reason the :class:`BaseObject` exists as it +defines the API all Python objects need to implement to work +correctly. + +Collections and Containers +========================== + +Libnotmuch exposes nearly all collections of things as iterators only. +In these python bindings they have sometimes been exposed as +:class:`collections.abc.Container` instances or subclasses of this +like :class:`collections.abc.Set` or :class:`collections.abc.Mapping` +etc. This gives a more natural API to work with, e.g. being able to +treat tags as sets. However it does mean that the +:meth:`__contains__`, :meth:`__len__` and frieds methods on these are +usually more and essentially O(n) rather than O(1) as you might +usually expect from Python containers. +""" + +from notdb import _capi +from notdb._base import * +from notdb._database import * +from notdb._errors import * +from notdb._message import * +from notdb._tags import * +from notdb._thread import * + + +NOTMUCH_TAG_MAX = _capi.lib.NOTMUCH_TAG_MAX +del _capi + + +# Re-home all the objects to the package. This leaves __qualname__ intact. +for x in locals().copy().values(): + if hasattr(x, '__module__'): + x.__module__ = __name__ +del x diff --git a/bindings/python-cffi/notdb/_base.py b/bindings/python-cffi/notdb/_base.py new file mode 100644 index 00000000..acb64413 --- /dev/null +++ b/bindings/python-cffi/notdb/_base.py @@ -0,0 +1,238 @@ +import abc +import collections.abc + +from notdb import _capi as capi +from notdb import _errors as errors + + +__all__ = ['NotmuchObject', 'BinString'] + + +class NotmuchObject(metaclass=abc.ABCMeta): + """Base notmuch object syntax. + + This base class exists to define the memory management handling + required to use the notmuch library. It is meant as an interface + definition rather than a base class, though you can use it as a + base class to ensure you don't forget part of the interface. It + only concerns you if you are implementing this package itself + rather then using it. + + libnotmuch uses a hierarchical memory allocator, where freeing the + memory of a parent object also frees the memory of all child + objects. To make this work seamlessly in Python this package + keeps references to parent objects which makes them stay alive + correctly under normal circumstances. When an object finally gets + deleted the :meth:`__del__` method will be called to free the + memory. + + However during some peculiar situations, e.g. interpreter + shutdown, it is possible for the :meth:`__del__` method to have + been called, whele there are still references to an object. This + could result in child objects asking their memeory to be freed + after the parent has already freed the memory, making things + rather unhappy as double frees are not taken lightly in C. To + handle this case all objects need to follow the same protocol to + destroy themselves, see :meth:`destroy`. + + Once an object has been destroyed trying to use it should raise + the :exc:`ObjectDestroyedError` exception. For this see also the + convenience :class:`MemoryPointer` descriptor in this module which + can be used as a pointer to libnotmuch memory. + """ + + @abc.abstractmethod + def __init__(self, parent, *args, **kwargs): + """Create a new object. + + Other then for the toplevel :class:`Database` object + constructors are only ever called by internal code and not by + the user. Per convention their signature always takes the + parent object as first argument. Feel free to make the rest + of the signature match the object's requirement. The object + needs to keep a reference to the parent, so it can check the + parent is still alive. + """ + + @property + @abc.abstractmethod + def alive(self): + """Whether the object is still alive. + + This indicates whether the object is still alive. The first + thing this needs to check is whether the parent object is + still alive, if it is not then this object can not be alive + either. If the parent is alive then it depends on whether the + memory for this object has been freed yet or not. + """ + + def __del__(self): + self._destroy() + + @abc.abstractmethod + def _destroy(self): + """Destroy the object, freeing all memory. + + This method needs to destory the object on the + libnotmuch-level. It must ensure it's not been destroyed by + it's parent object yet before doing so. It also must be + idempotent. + """ + + +class MemoryPointer: + """Data Descriptor to handle accessing libnotmuch pointers. + + Most :class:`NotmuchObject` instances will have one or more CFFI + pointers to C-objects. Once an object is destroyed this pointer + should no longer be used and a :exc:`ObjectDestroyedError` + exception should be raised on trying to access it. This + descriptor simplifies implementing this, allowing the creation of + an attribute which can be assigned to, but when accessed when the + stored value is *None* it will raise the + :exc:`ObjectDestroyedError` exception:: + + class SomeOjb: + _ptr = MemoryPointer() + + def __init__(self, ptr): + self._ptr = ptr + + def destroy(self): + somehow_free(self._ptr) + self._ptr = None + + def do_something(self): + return some_libnotmuch_call(self._ptr) + """ + + def __get__(self, instance, owner): + try: + val = getattr(instance, self.attr_name, None) + except AttributeError: + # We're not on 3.6+ and self.attr_name does not exist + self.__set_name__(instance, 'dummy') + val = getattr(instance, self.attr_name, None) + if val is None: + raise errors.ObjectDestroyedError() + return val + + def __set__(self, instance, value): + try: + setattr(instance, self.attr_name, value) + except AttributeError: + # We're not on 3.6+ and self.attr_name does not exist + self.__set_name__(instance, 'dummy') + setattr(instance, self.attr_name, value) + + def __set_name__(self, instance, name): + self.attr_name = '_memptr_{}_{:x}'.format(name, id(instance)) + + +class BinString(str): + """A str subclass with binary data. + + Most data in libnotmuch should be valid ASCII or valid UTF-8. + However since it is a C library these are represented as + bytestrings intead which means on an API level we can not + guarantee that decoding this to UTF-8 will both succeed and be + lossless. This string type converts bytes to unicode in a lossy + way, but also makes the raw bytes available. + + This object is a normal unicode string for most intents and + purposes, but you can get the original bytestring back by calling + ``bytes()`` on it. + """ + + def __new__(cls, data, encoding='utf-8', errors='ignore'): + if not isinstance(data, bytes): + data = bytes(data, encoding=encoding) + strdata = str(data, encoding=encoding, errors=errors) + inst = super().__new__(cls, strdata) + inst._bindata = data + return inst + + @classmethod + def from_cffi(cls, cdata): + """Create a new string from a CFFI cdata pointer.""" + return cls(capi.ffi.string(cdata)) + + def __bytes__(self): + return self._bindata + + +class NotmuchIter(NotmuchObject, collections.abc.Iterator): + """An iterator for libnotmuch iterators. + + It is tempting to use a generator function instead, but this would + not correctly respect the :class:`NotmuchObject` memory handling + protocol and in some unsuspecting cornercases cause memory + trouble. You probably want to sublcass this in order to wrap the + value returned by :meth:`__next__`. + + :param parent: The parent object. + :type parent: NotmuchObject + :param iter_p: The CFFI pointer to the C iterator. + :type iter_p: cffi.cdata + :param fn_destory: The CFFI notmuch_*_destroy function. + :param fn_valid: The CFFI notmuch_*_valid function. + :param fn_get: The CFFI notmuch_*_get function. + :param fn_next: The CFFI notmuch_*_move_to_next function. + """ + _iter_p = MemoryPointer() + + def __init__(self, parent, iter_p, + *, fn_destroy, fn_valid, fn_get, fn_next): + self._parent = parent + self._iter_p = iter_p + self._fn_destroy = fn_destroy + self._fn_valid = fn_valid + self._fn_get = fn_get + self._fn_next = fn_next + + def __del__(self): + self._destroy() + + @property + def alive(self): + if not self._parent.alive: + return False + try: + self._iter_p + except errors.ObjectDestroyedError: + return False + else: + return True + + def _destroy(self): + if self.alive: + try: + self._fn_destroy(self._iter_p) + except errors.ObjectDestroyedError: + pass + self._iter_p = None + + def __iter__(self): + """Return the iterator itself. + + Note that as this is an iterator and not a container this will + not return a new iterator. Thus any elements already consumed + will not be yielded by the :meth:`__next__` method anymore. + """ + return self + + def __next__(self): + if not self._fn_valid(self._iter_p): + self._destroy() + raise StopIteration() + obj_p = self._fn_get(self._iter_p) + self._fn_next(self._iter_p) + return obj_p + + def __repr__(self): + try: + self._iter_p + except errors.ObjectDestroyedError: + return '' + else: + return '' diff --git a/bindings/python-cffi/notdb/_build.py b/bindings/python-cffi/notdb/_build.py new file mode 100644 index 00000000..6be7e5b1 --- /dev/null +++ b/bindings/python-cffi/notdb/_build.py @@ -0,0 +1,302 @@ +import cffi + + +ffibuilder = cffi.FFI() +ffibuilder.set_source( + 'notdb._capi', + r""" + #include + #include + #include + + #if LIBNOTMUCH_MAJOR_VERSION < 5 + #error libnotmuch version not supported by notdb + #endif + """, + include_dirs=['../../lib'], + library_dirs=['../../lib'], + libraries=['notmuch'], +) +ffibuilder.cdef( + r""" + void free(void *ptr); + typedef int... time_t; + + #define LIBNOTMUCH_MAJOR_VERSION ... + #define LIBNOTMUCH_MINOR_VERSION ... + #define LIBNOTMUCH_MICRO_VERSION ... + + #define NOTMUCH_TAG_MAX ... + + typedef enum _notmuch_status { + NOTMUCH_STATUS_SUCCESS = 0, + NOTMUCH_STATUS_OUT_OF_MEMORY, + NOTMUCH_STATUS_READ_ONLY_DATABASE, + NOTMUCH_STATUS_XAPIAN_EXCEPTION, + NOTMUCH_STATUS_FILE_ERROR, + NOTMUCH_STATUS_FILE_NOT_EMAIL, + NOTMUCH_STATUS_DUPLICATE_MESSAGE_ID, + NOTMUCH_STATUS_NULL_POINTER, + NOTMUCH_STATUS_TAG_TOO_LONG, + NOTMUCH_STATUS_UNBALANCED_FREEZE_THAW, + NOTMUCH_STATUS_UNBALANCED_ATOMIC, + NOTMUCH_STATUS_UNSUPPORTED_OPERATION, + NOTMUCH_STATUS_UPGRADE_REQUIRED, + NOTMUCH_STATUS_PATH_ERROR, + NOTMUCH_STATUS_ILLEGAL_ARGUMENT, + NOTMUCH_STATUS_LAST_STATUS + } notmuch_status_t; + typedef enum { + NOTMUCH_DATABASE_MODE_READ_ONLY = 0, + NOTMUCH_DATABASE_MODE_READ_WRITE + } notmuch_database_mode_t; + typedef int notmuch_bool_t; + typedef enum _notmuch_message_flag { + NOTMUCH_MESSAGE_FLAG_MATCH, + NOTMUCH_MESSAGE_FLAG_EXCLUDED, + NOTMUCH_MESSAGE_FLAG_GHOST, + } notmuch_message_flag_t; + typedef enum { + NOTMUCH_SORT_OLDEST_FIRST, + NOTMUCH_SORT_NEWEST_FIRST, + NOTMUCH_SORT_MESSAGE_ID, + NOTMUCH_SORT_UNSORTED + } notmuch_sort_t; + typedef enum { + NOTMUCH_EXCLUDE_FLAG, + NOTMUCH_EXCLUDE_TRUE, + NOTMUCH_EXCLUDE_FALSE, + NOTMUCH_EXCLUDE_ALL + } notmuch_exclude_t; + + // These are fully opaque types for us, we only ever use pointers. + typedef struct _notmuch_database notmuch_database_t; + typedef struct _notmuch_query notmuch_query_t; + typedef struct _notmuch_threads notmuch_threads_t; + typedef struct _notmuch_thread notmuch_thread_t; + typedef struct _notmuch_messages notmuch_messages_t; + typedef struct _notmuch_message notmuch_message_t; + typedef struct _notmuch_tags notmuch_tags_t; + typedef struct _notmuch_string_map_iterator notmuch_message_properties_t; + typedef struct _notmuch_directory notmuch_directory_t; + typedef struct _notmuch_filenames notmuch_filenames_t; + typedef struct _notmuch_config_list notmuch_config_list_t; + + const char * + notmuch_status_to_string (notmuch_status_t status); + + notmuch_status_t + notmuch_database_create_verbose (const char *path, + notmuch_database_t **database, + char **error_message); + notmuch_status_t + notmuch_database_create (const char *path, notmuch_database_t **database); + notmuch_status_t + notmuch_database_open_verbose (const char *path, + notmuch_database_mode_t mode, + notmuch_database_t **database, + char **error_message); + notmuch_status_t + notmuch_database_open (const char *path, + notmuch_database_mode_t mode, + notmuch_database_t **database); + notmuch_status_t + notmuch_database_close (notmuch_database_t *database); + notmuch_status_t + notmuch_database_destroy (notmuch_database_t *database); + const char * + notmuch_database_get_path (notmuch_database_t *database); + unsigned int + notmuch_database_get_version (notmuch_database_t *database); + notmuch_bool_t + notmuch_database_needs_upgrade (notmuch_database_t *database); + notmuch_status_t + notmuch_database_begin_atomic (notmuch_database_t *notmuch); + notmuch_status_t + notmuch_database_end_atomic (notmuch_database_t *notmuch); + unsigned long + notmuch_database_get_revision (notmuch_database_t *notmuch, + const char **uuid); + notmuch_status_t + notmuch_database_add_message (notmuch_database_t *database, + const char *filename, + notmuch_message_t **message); + notmuch_status_t + notmuch_database_remove_message (notmuch_database_t *database, + const char *filename); + notmuch_status_t + notmuch_database_find_message (notmuch_database_t *database, + const char *message_id, + notmuch_message_t **message); + notmuch_status_t + notmuch_database_find_message_by_filename (notmuch_database_t *notmuch, + const char *filename, + notmuch_message_t **message); + notmuch_tags_t * + notmuch_database_get_all_tags (notmuch_database_t *db); + + notmuch_query_t * + notmuch_query_create (notmuch_database_t *database, + const char *query_string); + const char * + notmuch_query_get_query_string (const notmuch_query_t *query); + notmuch_database_t * + notmuch_query_get_database (const notmuch_query_t *query); + void + notmuch_query_set_omit_excluded (notmuch_query_t *query, + notmuch_exclude_t omit_excluded); + void + notmuch_query_set_sort (notmuch_query_t *query, notmuch_sort_t sort); + notmuch_sort_t + notmuch_query_get_sort (const notmuch_query_t *query); + notmuch_status_t + notmuch_query_add_tag_exclude (notmuch_query_t *query, const char *tag); + notmuch_status_t + notmuch_query_search_threads (notmuch_query_t *query, + notmuch_threads_t **out); + notmuch_status_t + notmuch_query_search_messages (notmuch_query_t *query, + notmuch_messages_t **out); + notmuch_status_t + notmuch_query_count_messages (notmuch_query_t *query, unsigned int *count); + notmuch_status_t + notmuch_query_count_threads (notmuch_query_t *query, unsigned *count); + void + notmuch_query_destroy (notmuch_query_t *query); + + notmuch_bool_t + notmuch_threads_valid (notmuch_threads_t *threads); + notmuch_thread_t * + notmuch_threads_get (notmuch_threads_t *threads); + void + notmuch_threads_move_to_next (notmuch_threads_t *threads); + void + notmuch_threads_destroy (notmuch_threads_t *threads); + + const char * + notmuch_thread_get_thread_id (notmuch_thread_t *thread); + notmuch_messages_t * + notmuch_message_get_replies (notmuch_message_t *message); + int + notmuch_thread_get_total_messages (notmuch_thread_t *thread); + notmuch_messages_t * + notmuch_thread_get_toplevel_messages (notmuch_thread_t *thread); + notmuch_messages_t * + notmuch_thread_get_messages (notmuch_thread_t *thread); + int + notmuch_thread_get_matched_messages (notmuch_thread_t *thread); + const char * + notmuch_thread_get_authors (notmuch_thread_t *thread); + const char * + notmuch_thread_get_subject (notmuch_thread_t *thread); + time_t + notmuch_thread_get_oldest_date (notmuch_thread_t *thread); + time_t + notmuch_thread_get_newest_date (notmuch_thread_t *thread); + notmuch_tags_t * + notmuch_thread_get_tags (notmuch_thread_t *thread); + void + notmuch_thread_destroy (notmuch_thread_t *thread); + + notmuch_bool_t + notmuch_messages_valid (notmuch_messages_t *messages); + notmuch_message_t * + notmuch_messages_get (notmuch_messages_t *messages); + void + notmuch_messages_move_to_next (notmuch_messages_t *messages); + void + notmuch_messages_destroy (notmuch_messages_t *messages); + notmuch_tags_t * + notmuch_messages_collect_tags (notmuch_messages_t *messages); + + const char * + notmuch_message_get_message_id (notmuch_message_t *message); + const char * + notmuch_message_get_thread_id (notmuch_message_t *message); + const char * + notmuch_message_get_filename (notmuch_message_t *message); + notmuch_filenames_t * + notmuch_message_get_filenames (notmuch_message_t *message); + notmuch_bool_t + notmuch_message_get_flag (notmuch_message_t *message, + notmuch_message_flag_t flag); + void + notmuch_message_set_flag (notmuch_message_t *message, + notmuch_message_flag_t flag, + notmuch_bool_t value); + time_t + notmuch_message_get_date (notmuch_message_t *message); + const char * + notmuch_message_get_header (notmuch_message_t *message, + const char *header); + notmuch_tags_t * + notmuch_message_get_tags (notmuch_message_t *message); + notmuch_status_t + notmuch_message_add_tag (notmuch_message_t *message, const char *tag); + notmuch_status_t + notmuch_message_remove_tag (notmuch_message_t *message, const char *tag); + notmuch_status_t + notmuch_message_remove_all_tags (notmuch_message_t *message); + notmuch_status_t + notmuch_message_maildir_flags_to_tags (notmuch_message_t *message); + notmuch_status_t + notmuch_message_tags_to_maildir_flags (notmuch_message_t *message); + notmuch_status_t + notmuch_message_freeze (notmuch_message_t *message); + notmuch_status_t + notmuch_message_thaw (notmuch_message_t *message); + notmuch_status_t + notmuch_message_get_property (notmuch_message_t *message, + const char *key, const char **value); + notmuch_status_t + notmuch_message_add_property (notmuch_message_t *message, + const char *key, const char *value); + notmuch_status_t + notmuch_message_remove_property (notmuch_message_t *message, + const char *key, const char *value); + notmuch_status_t + notmuch_message_remove_all_properties (notmuch_message_t *message, + const char *key); + notmuch_message_properties_t * + notmuch_message_get_properties (notmuch_message_t *message, + const char *key, notmuch_bool_t exact); + notmuch_bool_t + notmuch_message_properties_valid (notmuch_message_properties_t + *properties); + void + notmuch_message_properties_move_to_next (notmuch_message_properties_t + *properties); + const char * + notmuch_message_properties_key (notmuch_message_properties_t *properties); + const char * + notmuch_message_properties_value (notmuch_message_properties_t + *properties); + void + notmuch_message_properties_destroy (notmuch_message_properties_t + *properties); + void + notmuch_message_destroy (notmuch_message_t *message); + + notmuch_bool_t + notmuch_tags_valid (notmuch_tags_t *tags); + const char * + notmuch_tags_get (notmuch_tags_t *tags); + void + notmuch_tags_move_to_next (notmuch_tags_t *tags); + void + notmuch_tags_destroy (notmuch_tags_t *tags); + + notmuch_bool_t + notmuch_filenames_valid (notmuch_filenames_t *filenames); + const char * + notmuch_filenames_get (notmuch_filenames_t *filenames); + void + notmuch_filenames_move_to_next (notmuch_filenames_t *filenames); + void + notmuch_filenames_destroy (notmuch_filenames_t *filenames); + """ +) + + +if __name__ == '__main__': + ffibuilder.compile(verbose=True) diff --git a/bindings/python-cffi/notdb/_database.py b/bindings/python-cffi/notdb/_database.py new file mode 100644 index 00000000..d414082a --- /dev/null +++ b/bindings/python-cffi/notdb/_database.py @@ -0,0 +1,705 @@ +import collections +import configparser +import enum +import functools +import os +import pathlib +import weakref + +import notdb._base as base +import notdb._capi as capi +import notdb._errors as errors +import notdb._message as message +import notdb._query as querymod +import notdb._tags as tags + + +__all__ = ['Database', 'AtomicContext', 'DbRevision'] + + +def _config_pathname(): + """Return the path of the configuration file. + + :rtype: pathlib.Path + """ + cfgfname = os.getenv('NOTMUCH_CONFIG', '~/.notmuch-config') + return pathlib.Path(os.path.expanduser(cfgfname)) + + +class Mode(enum.Enum): + READ_ONLY = capi.lib.NOTMUCH_DATABASE_MODE_READ_ONLY + READ_WRITE = capi.lib.NOTMUCH_DATABASE_MODE_READ_WRITE + + +class QuerySortOrder(enum.Enum): + OLDEST_FIRST = capi.lib.NOTMUCH_SORT_OLDEST_FIRST + NEWEST_FIRST = capi.lib.NOTMUCH_SORT_NEWEST_FIRST + MESSAGE_ID = capi.lib.NOTMUCH_SORT_MESSAGE_ID + UNSORTED = capi.lib.NOTMUCH_SORT_UNSORTED + + +class QueryExclude(enum.Enum): + TRUE = capi.lib.NOTMUCH_EXCLUDE_TRUE + FLAG = capi.lib.NOTMUCH_EXCLUDE_FLAG + FALSE = capi.lib.NOTMUCH_EXCLUDE_FALSE + ALL = capi.lib.NOTMUCH_EXCLUDE_ALL + + +class Database(base.NotmuchObject): + """Toplevel access to notmuch. + + A :class:`Database` can be opened read-only or read-write. + Modifications are not atomic by default, use :meth:`begin_atomic` + for atomic updates. If the underlying database has been modified + outside of this class a :exc:`XapianError` will be raised and the + instance must be closed and a new one created. + + You can use an instance of this class as a context-manager. + + :cvar MODE: The mode a database can be opened with, an enumeration + of ``READ_ONLY`` and ``READ_WRITE`` + :cvar SORT: The sort order for search results, ``OLDEST_FIRST``, + ``NEWEST_FIRST``, ``MESSAGE_ID`` or ``UNSORTED``. + :cvar EXCLUDE: Which messages to exclude from queries, ``TRUE``, + ``FLAG``, ``FALSE`` or ``ALL``. See the query documentation + for details. + :cvar AddedMessage: A namedtuple ``(msg, dup)`` used by + :meth:`add` as return value. + :cvar STR_MODE_MAP: A map mapping strings to :attr:`MODE` items. + This is used to implement the ``ro`` and ``rw`` string + variants. + + :ivar closed: Boolean indicating if the database is closed or + still open. + + :param path: The directory of where the database is stored. If + ``None`` the location will be read from the user's + configuration file, respecting the ``NOTMUCH_CONFIG`` + environment variable if set. + :type path: str, bytes, os.PathLike or pathlib.Path + :param mode: The mode to open the database in. One of + :attr:`MODE.READ_ONLY` OR :attr:`MODE.READ_WRITE`. For + convenience you can also use the strings ``ro`` for + :attr:`MODE.READ_ONLY` and ``rw`` for :attr:`MODE.READ_WRITE`. + :type mode: :attr:`MODE` or str. + + :raises KeyError: if an unknown mode string is used. + :raises OSError: or subclasses if the configuration file can not + be opened. + :raises configparser.Error: or subclasses if the configuration + file can not be parsed. + :raises NotmuchError: or subclasses for other failures. + """ + + MODE = Mode + SORT = QuerySortOrder + EXCLUDE = QueryExclude + AddedMessage = collections.namedtuple('AddedMessage', ['msg', 'dup']) + _db_p = base.MemoryPointer() + STR_MODE_MAP = { + 'ro': MODE.READ_ONLY, + 'rw': MODE.READ_WRITE, + } + + def __init__(self, path=None, mode=MODE.READ_ONLY): + if isinstance(mode, str): + mode = self.STR_MODE_MAP[mode] + self.mode = mode + if path is None: + path = self.default_path() + if not hasattr(os, 'PathLike') and isinstance(path, pathlib.Path): + path = bytes(path) + db_pp = capi.ffi.new('notmuch_database_t **') + cmsg = capi.ffi.new('char**') + ret = capi.lib.notmuch_database_open_verbose(os.fsencode(path), + mode.value, db_pp, cmsg) + if cmsg[0]: + msg = capi.ffi.string(cmsg[0]).decode(errors='replace') + capi.lib.free(cmsg[0]) + else: + msg = None + if ret != capi.lib.NOTMUCH_STATUS_SUCCESS: + raise errors.NotmuchError(ret, msg) + self._db_p = db_pp[0] + self.closed = False + + @classmethod + def create(cls, path=None): + """Create and open database in READ_WRITE mode. + + This is creates a new notmuch database and returns an opened + instance in :attr:`MODE.READ_WRITE` mode. + + :param path: The directory of where the database is stored. If + ``None`` the location will be read from the user's + configuration file, respecting the ``NOTMUCH_CONFIG`` + environment variable if set. + :type path: str, bytes or os.PathLike + + :raises OSError: or subclasses if the configuration file can not + be opened. + :raises configparser.Error: or subclasses if the configuration + file can not be parsed. + :raises NotmuchError: if the config file does not have the + database.path setting. + :raises FileError: if the database already exists. + + :returns: The newly created instance. + """ + if path is None: + path = cls.default_path() + if not hasattr(os, 'PathLike') and isinstance(path, pathlib.Path): + path = bytes(path) + db_pp = capi.ffi.new('notmuch_database_t **') + cmsg = capi.ffi.new('char**') + ret = capi.lib.notmuch_database_create_verbose(os.fsencode(path), + db_pp, cmsg) + if cmsg[0]: + msg = capi.ffi.string(cmsg[0]).decode(errors='replace') + capi.lib.free(cmsg[0]) + else: + msg = None + if ret != capi.lib.NOTMUCH_STATUS_SUCCESS: + raise errors.NotmuchError(ret, msg) + + # Now close the db and let __init__ open it. Inefficient but + # creating is not a hot loop while this allows us to have a + # clean API. + ret = capi.lib.notmuch_database_destroy(db_pp[0]) + if ret != capi.lib.NOTMUCH_STATUS_SUCCESS: + raise errors.NotmuchError(ret) + return cls(path, cls.MODE.READ_WRITE) + + @staticmethod + def default_path(cfg_path=None): + """Return the path of the user's default database. + + This reads the user's configuration file and returns the + default path of the database. + + :param cfg_path: The pathname of the notmuch configuration file. + If not specified tries to use the pathname provided in the + :env:`NOTMUCH_CONFIG` environment variable and falls back + to :file:`~/.notmuch-config. + :type cfg_path: str, bytes, os.PathLike or pathlib.Path. + + :returns: The path of the database, which does not necessarily + exists. + :rtype: pathlib.Path + :raises OSError: or subclasses if the configuration file can not + be opened. + :raises configparser.Error: or subclasses if the configuration + file can not be parsed. + :raises NotmuchError if the config file does not have the + database.path setting. + """ + if not cfg_path: + cfg_path = _config_pathname() + if not hasattr(os, 'PathLike') and isinstance(cfg_path, pathlib.Path): + cfg_path = bytes(cfg_path) + parser = configparser.ConfigParser() + with open(cfg_path) as fp: + parser.read_file(fp) + try: + return pathlib.Path(parser.get('database', 'path')) + except configparser.Error: + raise errors.NotmuchError( + 'No database.path setting in {}'.format(cfg_path)) + + def __del__(self): + self._destroy() + + @property + def alive(self): + try: + self._db_p + except errors.ObjectDestroyedError: + return False + else: + return True + + def _destroy(self): + try: + ret = capi.lib.notmuch_database_destroy(self._db_p) + except errors.ObjectDestroyedError: + ret = capi.lib.NOTMUCH_STATUS_SUCCESS + else: + self._db_p = None + if ret != capi.lib.NOTMUCH_STATUS_SUCCESS: + raise errors.NotmuchError(ret) + + def close(self): + """Close the notmuch database. + + Once closed most operations will fail. This can still be + useful however to explicitly close a database which is opened + read-write as this would otherwise stop other processes from + reading the database while it is open. + + :raises ObjectDestroyedError: if used after destroyed. + """ + ret = capi.lib.notmuch_database_close(self._db_p) + if ret != capi.lib.NOTMUCH_STATUS_SUCCESS: + raise errors.NotmuchError(ret) + self.closed = True + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_value, traceback): + self.close() + + @property + def path(self): + """The pathname of the notmuch database. + + This is returned as a :class:`pathlib.Path` instance. + + :raises ObjectDestroyedError: if used after destoryed. + """ + try: + return self._cache_path + except AttributeError: + ret = capi.lib.notmuch_database_get_path(self._db_p) + self._cache_path = pathlib.Path(os.fsdecode(capi.ffi.string(ret))) + return self._cache_path + + @property + def version(self): + """The database format version. + + This is a positive integer. + + :raises ObjectDestroyedError: if used after destoryed. + """ + try: + return self._cache_version + except AttributeError: + ret = capi.lib.notmuch_database_get_version(self._db_p) + self._cache_version = ret + return ret + + @property + def needs_upgrade(self): + """Whether the database should be upgraded. + + If *True* the database can be upgraded using :meth:`upgrade`. + Not doing so may result in some operations raising + :exc:`UpgradeRequiredError`. + + A read-only database will never be upgradable. + + :raises ObjectDestroyedError: if used after destoryed. + """ + ret = capi.lib.notmuch_database_needs_upgrade(self._db_p) + return bool(ret) + + def upgrade(self, progress_cb=None): + """Upgrade the database to the latest version. + + Upgrade the database, optionally with a progress callback + which should be a callable which will be called with a + floating point number in the range of [0.0 .. 1.0]. + """ + raise NotImplementedError + + def atomic(self): + """Return a context manager to perform atomic operations. + + The returned context manager can be used to perform atomic + operations on the database. + + .. note:: Unlinke a traditional RDBMS transaction this does + not imply durability, it only ensures the changes are + performed atomically. + + :raises ObjectDestroyedError: if used after destoryed. + """ + ctx = AtomicContext(self, '_db_p') + return ctx + + def revision(self): + """The currently committed revision in the database. + + Returned as a ``(revision, uuid)`` namedtuple. + + :raises ObjectDestroyedError: if used after destoryed. + """ + raw_uuid = capi.ffi.new('char**') + rev = capi.lib.notmuch_database_get_revision(self._db_p, raw_uuid) + return DbRevision(rev, capi.ffi.string(raw_uuid[0])) + + def get_directory(self, path): + raise NotImplementedError + + def add(self, filename, *, sync_flags=False): + """Add a message to the database. + + Add a new message to the notmuch database. The message is + referred to by the pathname of the maildir file. If the + message ID of the new message already exists in the database, + this adds ``pathname`` to the list of list of files for the + existing message. + + :param filename: The path of the file containing the message. + :type filename: str, bytes, os.PathLike or pathlib.Path. + :param sync_flags: Whether to sync the known maildir flags to + notmuch tags. See :meth:`Message.flags_to_tags` for + details. + + :returns: A tuple where the first item is the newly inserted + messages as a :class:`Message` instance, and the second + item is a boolean indicating if the message inserted was a + duplicate. This is the namedtuple ``AddedMessage(msg, + dup)``. + :rtype: Database.AddedMessage + + If an exception is raised, no message was added. + + :raises XapianError: A Xapian exception occurred. + :raises FileError: The file referred to by ``pathname`` could + not be opened. + :raises FileNotEmailError: The file referreed to by + ``pathname`` is not recognised as an email message. + :raises ReadOnlyDatabaseError: The database is opened in + READ_ONLY mode. + :raises UpgradeRequiredError: The database must be upgraded + first. + :raises ObjectDestroyedError: if used after destoryed. + """ + if not hasattr(os, 'PathLike') and isinstance(filename, pathlib.Path): + filename = bytes(filename) + msg_pp = capi.ffi.new('notmuch_message_t **') + ret = capi.lib.notmuch_database_add_message(self._db_p, + os.fsencode(filename), + msg_pp) + ok = [capi.lib.NOTMUCH_STATUS_SUCCESS, + capi.lib.NOTMUCH_STATUS_DUPLICATE_MESSAGE_ID] + if ret not in ok: + raise errors.NotmuchError(ret) + msg = message.Message(self, msg_pp[0], db=self) + if sync_flags: + msg.tags.from_maildir_flags() + return self.AddedMessage( + msg, ret == capi.lib.NOTMUCH_STATUS_DUPLICATE_MESSAGE_ID) + + def remove(self, filename): + """Remove a message from the notmuch database. + + Removing a message which is not in the database is just a + silent nop-operation. + + :param filename: The pathname of the file containing the + message to be removed. + :type filename: str, bytes, os.PathLike or pathlib.Path. + + :returns: True if the message is still in the database. This + can happen when multiple files contain the same message ID. + The true/false distinction is fairly arbitrary, but think + of it as ``dup = db.remove_message(name); if dup: ...``. + :rtype: bool + + :raises XapianError: A Xapian exception ocurred. + :raises ReadOnlyDatabaseError: The database is opened in + READ_ONLY mode. + :raises UpgradeRequiredError: The database must be upgraded + first. + :raises ObjectDestroyedError: if used after destoryed. + """ + if not hasattr(os, 'PathLike') and isinstance(filename, pathlib.Path): + filename = bytes(filename) + ret = capi.lib.notmuch_database_remove_message(self._db_p, + os.fsencode(filename)) + ok = [capi.lib.NOTMUCH_STATUS_SUCCESS, + capi.lib.NOTMUCH_STATUS_DUPLICATE_MESSAGE_ID] + if ret not in ok: + raise errors.NotmuchError(ret) + if ret == capi.lib.NOTMUCH_STATUS_DUPLICATE_MESSAGE_ID: + return True + else: + return False + + def find(self, msgid): + """Return the message matching the given message ID. + + If a message with the given message ID is found a + :class:`Message` instance is returned. Otherwise a + :exc:`LookupError` is raised. + + :param msgid: The message ID to look for. + :type msgid: str + + :returns: The message instance. + :rtype: Message + + :raises LookupError: If no message was found. + :raises OutOfMemoryError: When there is no memory to allocate + the message instance. + :raises XapianError: A Xapian exception ocurred. + :raises ObjectDestroyedError: if used after destoryed. + """ + msg_pp = capi.ffi.new('notmuch_message_t **') + ret = capi.lib.notmuch_database_find_message(self._db_p, + msgid.encode(), msg_pp) + if ret != capi.lib.NOTMUCH_STATUS_SUCCESS: + raise errors.NotmuchError(ret) + msg_p = msg_pp[0] + if msg_p == capi.ffi.NULL: + raise LookupError + msg = message.Message(self, msg_p, db=self) + return msg + + def get(self, filename): + """Return the :class:`Message` given a pathname. + + If a message with the given pathname exists in the database + return the :class:`Message` instance for the message. + Otherwise raise a :exc:`LookupError` exception. + + :param filename: The pathname of the message. + :type filename: str, bytes, os.PathLike or pathlib.Path + + :returns: The message instance. + :rtype: Message + + :raises LookupError: If no message was found. This is also + a subclass of :exc:`KeyError`. + :raises OutOfMemoryError: When there is no memory to allocate + the message instance. + :raises XapianError: A Xapian exception ocurred. + :raises ObjectDestroyedError: if used after destoryed. + """ + if not hasattr(os, 'PathLike') and isinstance(filename, pathlib.Path): + filename = bytes(filename) + msg_pp = capi.ffi.new('notmuch_message_t **') + ret = capi.lib.notmuch_database_find_message_by_filename( + self._db_p, os.fsencode(filename), msg_pp) + if ret != capi.lib.NOTMUCH_STATUS_SUCCESS: + raise errors.NotmuchError(ret) + msg_p = msg_pp[0] + if msg_p == capi.ffi.NULL: + raise LookupError + msg = message.Message(self, msg_p, db=self) + return msg + + @property + def tags(self): + """Return an immutable set with all tags used in this database. + + This returns an immutable set-like object implementing the + collections.abc.Set Abstract Base Class. Due to the + underlying libnotmuch implementation some operations have + different performance characteristics then plain set objects. + Mainly any lookup operation is O(n) rather then O(1). + + Normal usage treats tags as UTF-8 encoded unicode strings so + they are exposed to Python as normal unicode string objects. + If you need to handle tags stored in libnotmuch which are not + valid unicode do check the :class:`ImmutableTagSet` docs for + how to handle this. + + :rtype: ImmutableTagSet + + :raises ObjectDestroyedError: if used after destoryed. + """ + try: + ref = self._cached_tagset + except AttributeError: + tagset = None + else: + tagset = ref() + if tagset is None: + tagset = tags.ImmutableTagSet( + self, '_db_p', capi.lib.notmuch_database_get_all_tags) + self._cached_tagset = weakref.ref(tagset) + return tagset + + def _create_query(self, query, *, + omit_excluded=EXCLUDE.TRUE, + sort=SORT.UNSORTED, # Check this default + exclude_tags=None): + """Create an internal query object. + + :raises OutOfMemoryError: if no memory is available to + allocate the query. + """ + if isinstance(query, str): + query = query.encode('utf-8') + query_p = capi.lib.notmuch_query_create(self._db_p, query) + if query_p == capi.ffi.NULL: + raise errors.OutOfMemoryError() + capi.lib.notmuch_query_set_omit_excluded(query_p, omit_excluded.value) + capi.lib.notmuch_query_set_sort(query_p, sort.value) + if exclude_tags is not None: + for tag in exclude_tags: + if isinstance(tag, str): + tag = str.encode('utf-8') + capi.lib.notmuch_query_add_tag_exclude(query_p, tag) + return querymod.Query(self, query_p) + + def messages(self, query, *, + omit_excluded=EXCLUDE.TRUE, + sort=SORT.UNSORTED, # Check this default + exclude_tags=None): + """Search the database for messages. + + :returns: An iterator over the messages found. + :rtype: MessageIter + + :raises OutOfMemoryError: if no memory is available to + allocate the query. + :raises ObjectDestroyedError: if used after destoryed. + """ + query = self._create_query(query, + omit_excluded=omit_excluded, + sort=sort, + exclude_tags=exclude_tags) + return query.messages() + + def count_messages(self, query, *, + omit_excluded=EXCLUDE.TRUE, + sort=SORT.UNSORTED, # Check this default + exclude_tags=None): + """Search the database for messages. + + :returns: An iterator over the messages found. + :rtype: MessageIter + + :raises ObjectDestroyedError: if used after destoryed. + """ + query = self._create_query(query, + omit_excluded=omit_excluded, + sort=sort, + exclude_tags=exclude_tags) + return query.count_messages() + + def threads(self, query, *, + omit_excluded=EXCLUDE.TRUE, + sort=SORT.UNSORTED, # Check this default + exclude_tags=None): + query = self._create_query(query, + omit_excluded=omit_excluded, + sort=sort, + exclude_tags=exclude_tags) + return query.threads() + + def count_threads(self, query, *, + omit_excluded=EXCLUDE.TRUE, + sort=SORT.UNSORTED, # Check this default + exclude_tags=None): + query = self._create_query(query, + omit_excluded=omit_excluded, + sort=sort, + exclude_tags=exclude_tags) + return query.count_threads() + + def status_string(self): + raise NotImplementedError + + def __repr__(self): + return 'Database(path={self.path}, mode={self.mode})'.format(self=self) + + +class AtomicContext: + """Context manager for atomic support. + + This supports the notmuch_database_begin_atomic and + notmuch_database_end_atomic API calls. The object can not be + directly instantiated by the user, only via ``Database.atomic``. + It does keep a reference to the :class:`Database` instance to keep + the C memory alive. + + :raises XapianError: When this is raised at enter time the atomic + section is not active. When it is raised at exit time the + atomic section is still active and you may need to try using + :meth:`force_end`. + :raises ObjectDestroyedError: if used after destoryed. + """ + + def __init__(self, db, ptr_name): + self._db = db + self._ptr = lambda: getattr(db, ptr_name) + + def __del__(self): + self._destroy() + + @property + def alive(self): + return self.parent.alive + + def _destroy(self): + pass + + def __enter__(self): + ret = capi.lib.notmuch_database_begin_atomic(self._ptr()) + if ret != capi.lib.NOTMUCH_STATUS_SUCCESS: + raise errors.NotmuchError(ret) + return self + + def __exit__(self, exc_type, exc_value, traceback): + ret = capi.lib.notmuch_database_end_atomic(self._ptr()) + if ret != capi.lib.NOTMUCH_STATUS_SUCCESS: + raise errors.NotmuchError(ret) + + def force_end(self): + """Force ending the atomic section. + + This can only be called once __exit__ has been called. It + will attept to close the atomic section (again). This is + useful if the original exit raised an exception and the atomic + section is still open. But things are pretty ugly by now. + + :raises XapianError: If exiting fails, the atomic section is + not ended. + :raises UnbalancedAtomicError: If the database was currently + not in an atomic section. + :raises ObjectDestroyedError: if used after destoryed. + """ + ret = capi.lib.notmuch_database_end_atomic(self._ptr()) + if ret != capi.lib.NOTMUCH_STATUS_SUCCESS: + raise errors.NotmuchError(ret) + + +@functools.total_ordering +class DbRevision: + """A database revision. + + The database revision number increases monotonically with each + commit to the database. Which means user-visible changes can be + ordered. This object is sortable with other revisions. It + carries the UUID of the database to ensure it is only ever + compared with revisions from the same database. + """ + + def __init__(self, rev, uuid): + self._rev = rev + self._uuid = uuid + + @property + def rev(self): + """The revision number, a positive integer.""" + return self._rev + + @property + def uuid(self): + """The UUID of the database, consider this opaque.""" + return self._uuid + + def __eq__(self, other): + if isinstance(other, self.__class__): + if self.uuid != other.uuid: + return False + return self.rev == other.rev + else: + return NotImplemented + + def __lt__(self, other): + if self.__class__ is other.__class__: + if self.uuid != other.uuid: + return False + return self.rev < other.rev + else: + return NotImplemented + + def __repr__(self): + return 'DbRevision(rev={self.rev}, uuid={self.uuid})'.format(self=self) diff --git a/bindings/python-cffi/notdb/_errors.py b/bindings/python-cffi/notdb/_errors.py new file mode 100644 index 00000000..924e722f --- /dev/null +++ b/bindings/python-cffi/notdb/_errors.py @@ -0,0 +1,112 @@ +from notdb import _capi as capi + + +class NotmuchError(Exception): + """Base exception for errors originating from the notmuch library. + + Usually this will have two attributes: + + :status: This is a numeric status code corresponding to the error + code in the notmuch library. This is normally fairly + meaningless, it can also often be ``None``. This exists mostly + to easily create new errors from notmuch status codes and + should not normally be used by users. + + :message: A user-facing message for the error. This can + occasionally also be ``None``. Usually you'll want to call + ``str()`` on the error object instead to get a sensible + message. + """ + + @classmethod + def exc_type(cls, status): + """Return correct exception type for notmuch status.""" + types = { + capi.lib.NOTMUCH_STATUS_OUT_OF_MEMORY: + OutOfMemoryError, + capi.lib.NOTMUCH_STATUS_READ_ONLY_DATABASE: + ReadOnlyDatabaseError, + capi.lib.NOTMUCH_STATUS_XAPIAN_EXCEPTION: + XapianError, + capi.lib.NOTMUCH_STATUS_FILE_ERROR: + FileError, + capi.lib.NOTMUCH_STATUS_FILE_NOT_EMAIL: + FileNotEmailError, + capi.lib.NOTMUCH_STATUS_DUPLICATE_MESSAGE_ID: + DuplicateMessageIdError, + capi.lib.NOTMUCH_STATUS_NULL_POINTER: + NullPointerError, + capi.lib.NOTMUCH_STATUS_TAG_TOO_LONG: + TagTooLongError, + capi.lib.NOTMUCH_STATUS_UNBALANCED_FREEZE_THAW: + UnbalancedFreezeThawError, + capi.lib.NOTMUCH_STATUS_UNBALANCED_ATOMIC: + UnbalancedAtomicError, + capi.lib.NOTMUCH_STATUS_UNSUPPORTED_OPERATION: + UnsupportedOperationError, + capi.lib.NOTMUCH_STATUS_UPGRADE_REQUIRED: + UpgradeRequiredError, + capi.lib.NOTMUCH_STATUS_PATH_ERROR: + PathError, + capi.lib.NOTMUCH_STATUS_ILLEGAL_ARGUMENT: + IllegalArgumentError, + } + return types[status] + + def __new__(cls, *args, **kwargs): + """Return the correct subclass based on status.""" + # This is simplistic, but the actual __init__ will fail if the + # signature is wrong anyway. + if args: + status = args[0] + else: + status = kwargs.get('status', None) + if status and cls == NotmuchError: + exc = cls.exc_type(status) + return exc.__new__(exc, *args, **kwargs) + else: + return super().__new__(cls) + + def __init__(self, status=None, message=None): + self.status = status + self.message = message + + def __str__(self): + if self.message: + return self.message + elif self.status: + return capi.lib.notmuch_status_to_string(self.status) + else: + return 'Unknown error' + + +class OutOfMemoryError(NotmuchError): pass +class ReadOnlyDatabaseError(NotmuchError): pass +class XapianError(NotmuchError): pass +class FileError(NotmuchError): pass +class FileNotEmailError(NotmuchError): pass +class DuplicateMessageIdError(NotmuchError): pass +class NullPointerError(NotmuchError): pass +class TagTooLongError(NotmuchError): pass +class UnbalancedFreezeThawError(NotmuchError): pass +class UnbalancedAtomicError(NotmuchError): pass +class UnsupportedOperationError(NotmuchError): pass +class UpgradeRequiredError(NotmuchError): pass +class PathError(NotmuchError): pass +class IllegalArgumentError(NotmuchError): pass + + +class ObjectDestroyedError(NotmuchError): + """The object has already been destoryed and it's memory freed. + + This occurs when :meth:`destroy` has been called on the object but + you still happen to have access to the object. This should not + normally occur since you should never call :meth:`destroy` by + hand. + """ + + def __str__(self): + if self.message: + return self.message + else: + return 'Memory already freed' diff --git a/bindings/python-cffi/notdb/_message.py b/bindings/python-cffi/notdb/_message.py new file mode 100644 index 00000000..9b2b037f --- /dev/null +++ b/bindings/python-cffi/notdb/_message.py @@ -0,0 +1,691 @@ +import collections +import contextlib +import os +import pathlib +import weakref + +import notdb._base as base +import notdb._capi as capi +import notdb._errors as errors +import notdb._tags as tags + + +__all__ = ['Message'] + + +class Message(base.NotmuchObject): + """An email message stored in the notmuch database. + + This should not be directly created, instead it will be returned + by calling methods on :class:`Database`. A message keeps a + reference to the database object since the database object can not + be released while the message is in use. + + Note that this represents a message in the notmuch database. For + full email functionality you may want to use the :mod:`email` + package from Python's standard library. You could e.g. create + this as such:: + + notmuch_msg = db.get_message(msgid) # or from a query + parser = email.parser.BytesParser(policy=email.policy.default) + with notmuch_msg.path.open('rb) as fp: + email_msg = parser.parse(fp) + + Most commonly the functionality provided by notmuch is sufficient + to read email however. + + Messages are considered equal when they have the same message ID. + This is how libnotmuch treats messages as well, the + :meth:`pathnames` function returns multiple results for + duplicates. + + :param parent: The parent object. This is probably one off a + :class:`Database`, :class:`Thread` or :class:`Query`. + :type parent: NotmuchObject + :param db: The database instance this message is associated with. + This could be the same as the parent. + :type db: Database + :param msg_p: The C pointer to the ``notmuch_message_t``. + :type msg_p: + + :param dup: Whether the message was a duplicate on insertion. + + :type dup: None or bool + """ + _msg_p = base.MemoryPointer() + + def __init__(self, parent, msg_p, *, db): + self._parent = parent + self._msg_p = msg_p + self._db = db + + @property + def alive(self): + if not self._parent.alive: + return False + try: + self._msg_p + except errors.ObjectDestroyedError: + return False + else: + return True + + def __del__(self): + self._destroy() + + def _destroy(self): + if self.alive: + capi.lib.notmuch_message_destroy(self._msg_p) + self._msg_p = None + + @property + def messageid(self): + """The message ID as a string. + + The message ID is decoded with the ignore error handler. This + is fine as long as the message ID is well formed. If it is + not valid ASCII then this will be lossy. So if you need to be + able to write the exact same message ID back you should use + :attr:`messageidb`. + + Note that notmuch will decode the message ID value and thus + strip off the surrounding ``<`` and ``>`` characters. This is + different from Python's :mod:`email` package behaviour which + leaves these characters in place. + + :returns: The message ID. + :rtype: :class:`BinString`, this is a normal str but calling + bytes() on it will return the original bytes used to create + it. + + :raises ObjectDestroyedError: if used after destoryed. + """ + ret = capi.lib.notmuch_message_get_message_id(self._msg_p) + return base.BinString(capi.ffi.string(ret)) + + @property + def threadid(self): + """The thread ID. + + The thread ID is decoded with the surrogateescape error + handler so that it is possible to reconstruct the original + thread ID if it is not valid UTF-8. + + :returns: The thread ID. + :rtype: :class:`BinString`, this is a normal str but calling + bytes() on it will return the original bytes used to create + it. + + :raises ObjectDestroyedError: if used after destoryed. + """ + ret = capi.lib.notmuch_message_get_thread_id(self._msg_p) + return base.BinString(capi.ffi.string(ret)) + + @property + def path(self): + """A pathname of the message as a pathlib.Path instance. + + If multiple files in the database contain the same message ID + this will be just one of the files, chosen at random. + + :raises ObjectDestroyedError: if used after destoryed. + """ + ret = capi.lib.notmuch_message_get_filename(self._msg_p) + return pathlib.Path(os.fsdecode(capi.ffi.string(ret))) + + @property + def pathb(self): + """A pathname of the message as a bytes object. + + See :attr:`path` for details, this is the same but does return + the path as a bytes object which is faster but less convenient. + + :raises ObjectDestroyedError: if used after destoryed. + """ + ret = capi.lib.notmuch_message_get_filename(self._msg_p) + return capi.ffi.string(ret) + + def filenames(self): + """Return an iterator of all files for this message. + + If multiple files contained the same message ID they will all + be returned here. The files are returned as intances of + :class:`pathlib.Path`. + + :returns: Iterator yielding :class:`pathlib.Path` instances. + :rtype: iter + + :raises ObjectDestroyedError: if used after destoryed. + """ + fnames_p = capi.lib.notmuch_message_get_filenames(self._msg_p) + return PathIter(self, fnames_p) + + def filenamesb(self): + """Return an iterator of all files for this message. + + This is like :meth:`pathnames` but the files are returned as + byte objects instead. + + :returns: Iterator yielding :class:`bytes` instances. + :rtype: iter + + :raises ObjectDestroyedError: if used after destoryed. + """ + fnames_p = capi.lib.notmuch_message_get_filenames(self._msg_p) + return FilenamesIter(self, fnames_p) + + @property + def ghost(self): + """Indicates whether this message is a ghost message. + + A ghost message if a message which we know exists, but it has + no files or content associated with it. This can happen if + it was referenced by some other message. Only the + :attr:`messageid` and :attr:`threadid` attributes are valid + for it. + + :raises ObjectDestroyedError: if used after destoryed. + """ + ret = capi.lib.notmuch_message_get_flag( + self._msg_p, capi.lib.NOTMUCH_MESSAGE_FLAG_GHOST) + return bool(ret) + + @property + def excluded(self): + """Indicates whether this message was excluded from the query. + + When a message is created from a search, sometimes messages + that where excluded by the search query could still be + returned by it, e.g. because they are part of a thread + matching the query. the :meth:`Database.query` method allows + these messages to be flagged, which results in this property + being set to *True*. + + :raises ObjectDestroyedError: if used after destoryed. + """ + ret = capi.lib.notmuch_message_get_flag( + self._msg_p, capi.lib.NOTMUCH_MESSAGE_FLAG_EXCLUDED) + return bool(ret) + + @property + def date(self): + """The message date as an integer. + + The time the message was sent as an integer number of seconds + since the *epoch*, 1 Jan 1970. This is derived from the + message's header, you can get the original header value with + :meth:`header`. + + :raises ObjectDestroyedError: if used after destoryed. + """ + return capi.lib.notmuch_message_get_date(self._msg_p) + + def header(self, name): + """Return the value of the named header. + + Returns the header from notmuch, some common headers are + stored in the database, others are read from the file. + Headers are returned with their newlines stripped and + collapsed concatenated together if they occur multiple times. + You may be better off using the standard library email + package's ``email.message_from_file(msg.path.open())`` if that + is not sufficient for you. + + :param header: Case-insensitive header name to retrieve. + :type header: str or bytes + + :returns: The header value, an empty string if the header is + not present. + :rtype: str + + :raises LookupError: if the header is not present. + :raises NullPointerError: For unexpected notmuch errors. + :raises ObjectDestroyedError: if used after destoryed. + """ + # The returned is supposedly guaranteed to be UTF-8. Header + # names must be ASCII as per RFC x822. + if isinstance(name, str): + name = name.encode('ascii') + ret = capi.lib.notmuch_message_get_header(self._msg_p, name) + if ret == capi.ffi.NULL: + raise errors.NullPointerError() + hdr = capi.ffi.string(ret) + if not hdr: + raise LookupError + return hdr.decode(encoding='utf-8') + + @property + def tags(self): + """The tags associated with the message. + + This behaves as a set. But removing and adding items to the + set removes and adds them to the message in the database. + + :raises ReadOnlyDatabaseError: When manipulating tags on a + database opened in read-only mode. + :raises ObjectDestroyedError: if used after destoryed. + """ + try: + ref = self._cached_tagset + except AttributeError: + tagset = None + else: + tagset = ref() + if tagset is None: + tagset = tags.MutableTagSet( + self, '_msg_p', capi.lib.notmuch_message_get_tags) + self._cached_tagset = weakref.ref(tagset) + return tagset + + @contextlib.contextmanager + def frozen(self): + """Context manager to freeze the message state. + + This allows you to perform atomic tag updates:: + + with msg.frozen(): + msg.tags.clear() + msg.tags.add('foo') + + Using This would ensure the message never ends up with no tags + applied at all. + + It is safe to nest calls to this context manager. + + :raises ReadOnlyDatabaseError: if the database is opened in + read-only mode. + :raises UnbalancedFreezeThawError: if you somehow managed to + call __exit__ of this context manager more than once. Why + did you do that? + :raises ObjectDestroyedError: if used after destoryed. + """ + ret = capi.lib.notmuch_message_freeze(self._msg_p) + if ret != capi.lib.NOTMUCH_STATUS_SUCCESS: + raise errors.NotmuchError(ret) + self._frozen = True + try: + yield + except Exception: + # Only way to "rollback" these changes is to destroy + # ourselves and re-create. Behold. + msgid = self.messageid + self._destroy() + with contextlib.suppress(Exception): + new = self._db.find(msgid) + self._msg_p = new._msg_p + new._msg_p = None + del new + raise + else: + ret = capi.lib.notmuch_message_thaw(self._msg_p) + if ret != capi.lib.NOTMUCH_STATUS_SUCCESS: + raise errors.NotmuchError(ret) + self._frozen = False + + @property + def properties(self): + """A map of arbitrary key-value pairs associated with the message. + + Be aware that properties may be used by other extensions to + store state in. So delete or modify with care. + + The properties map is somewhat special. It is essentially a + multimap-like structure where each key can have multiple + values. Therefore accessing a single item using + :meth:`PropertiesMap.get` or :meth:`PropertiesMap.__getitem__` + will only return you the *first* item if there are multiple + and thus are only recommended if you know there to be only one + value. + + Instead the map has an additional :meth:`PropertiesMap.all` + method which can be used to retrieve all properties of a given + key. This method also allows iterating of a a subset of the + keys starting with a given prefix. + """ + try: + ref = self._cached_props + except AttributeError: + props = None + else: + props = ref() + if props is None: + props = PropertiesMap(self, '_msg_p') + self._cached_props = weakref.ref(props) + return props + + def replies(self): + """Return an iterator of all replies to this message. + + This method will only work if the message was created from a + thread. Otherwise it will yield no results. + + :returns: An iterator yielding :class:`Message` instances. + :rtype: MessageIter + """ + # The notmuch_messages_valid call accepts NULL and this will + # become an empty iterator, raising StopIteration immediately. + # Hence no return value checking here. + msgs_p = capi.lib.notmuch_message_get_replies(self._msg_p) + return MessageIter(self, msgs_p, db=self._db) + + def __hash__(self): + return hash(self.messageid) + + def __eq__(self, other): + if isinstance(other, self.__class__): + return self.messageid == other.messageid + + +class FilenamesIter(base.NotmuchIter): + """Iterator for binary filenames objects.""" + + def __init__(self, parent, iter_p): + super().__init__(parent, iter_p, + fn_destroy=capi.lib.notmuch_filenames_destroy, + fn_valid=capi.lib.notmuch_filenames_valid, + fn_get=capi.lib.notmuch_filenames_get, + fn_next=capi.lib.notmuch_filenames_move_to_next) + + def __next__(self): + fname = super().__next__() + return capi.ffi.string(fname) + + +class PathIter(FilenamesIter): + """Iterator for pathlib.Path objects.""" + + def __next__(self): + fname = super().__next__() + return pathlib.Path(os.fsdecode(fname)) + + +class PropertiesMap(base.NotmuchObject, collections.abc.MutableMapping): + """A mutable mapping to manage properties. + + Both keys and values of properties are supposed to be UTF-8 + strings in libnotmuch. However since the uderlying API uses + bytestrings you can use either str or bytes to represent keys and + all returned keys and values use :class:`BinString`. + + Also be aware that ``iter(this_map)`` will return duplicate keys, + while the :class:`collections.abc.KeysView` returned by + :meth:`keys` is a :class:`collections.abc.Set` subclass. This + means the former will yield duplicate keys while the latter won't. + It also means ``len(list(iter(this_map)))`` could be different + than ``len(this_map.keys())``. ``len(this_map)`` will correspond + with the lenght of the default iterator. + + Be aware that libnotmuch exposes all of this as iterators, so + quite a few operations have O(n) performance instead of the usual + O(1). + """ + Property = collections.namedtuple('Property', ['key', 'value']) + _marker = object() + + def __init__(self, msg, ptr_name): + self._msg = msg + self._ptr = lambda: getattr(msg, ptr_name) + + @property + def alive(self): + if not self._msg.alive: + return False + try: + self._ptr + except errors.ObjectDestroyedError: + return False + else: + return True + + def _destroy(self): + pass + + def __iter__(self): + """Return an iterator which iterates over the keys. + + Be aware that a single key may have multiple values associated + with it, if so it will appear multiple times here. + """ + iter_p = capi.lib.notmuch_message_get_properties(self._ptr(), b'', 0) + return PropertiesKeyIter(self, iter_p) + + def __len__(self): + iter_p = capi.lib.notmuch_message_get_properties(self._ptr(), b'', 0) + it = base.NotmuchIter( + self, iter_p, + fn_destroy=capi.lib.notmuch_message_properties_destroy, + fn_valid=capi.lib.notmuch_message_properties_valid, + fn_get=capi.lib.notmuch_message_properties_key, + fn_next=capi.lib.notmuch_message_properties_move_to_next, + ) + return len(list(it)) + + def __getitem__(self, key): + """Return **the first** peroperty associated with a key.""" + if isinstance(key, str): + key = key.encode('utf-8') + value_pp = capi.ffi.new('char**') + ret = capi.lib.notmuch_message_get_property(self._ptr(), key, value_pp) + if ret != capi.lib.NOTMUCH_STATUS_SUCCESS: + raise errors.NotmuchError(ret) + if value_pp[0] == capi.ffi.NULL: + raise KeyError + return base.BinString.from_cffi(value_pp[0]) + + def keys(self): + """Return a :class:`collections.abc.KeysView` for this map. + + Even when keys occur multiple times this is a subset of set() + so will only contain them once. + """ + return collections.abc.KeysView({k: None for k in self}) + + def items(self): + """Return a :class:`collections.abc.ItemsView` for this map. + + The ItemsView treats a ``(key, value)`` pair as unique, so + dupcliate ``(key, value)`` pairs will be merged together. + However duplicate keys with different values will be returned. + """ + items = set() + props_p = capi.lib.notmuch_message_get_properties(self._ptr(), b'', 0) + while capi.lib.notmuch_message_properties_valid(props_p): + key = capi.lib.notmuch_message_properties_key(props_p) + value = capi.lib.notmuch_message_properties_value(props_p) + items.add((base.BinString.from_cffi(key), + base.BinString.from_cffi(value))) + capi.lib.notmuch_message_properties_move_to_next(props_p) + capi.lib.notmuch_message_properties_destroy(props_p) + return PropertiesItemsView(items) + + def values(self): + """Return a :class:`collecions.abc.ValuesView` for this map. + + All unique property values are included in the view. + """ + values = set() + props_p = capi.lib.notmuch_message_get_properties(self._ptr(), b'', 0) + while capi.lib.notmuch_message_properties_valid(props_p): + value = capi.lib.notmuch_message_properties_value(props_p) + values.add(base.BinString.from_cffi(value)) + capi.lib.notmuch_message_properties_move_to_next(props_p) + capi.lib.notmuch_message_properties_destroy(props_p) + return PropertiesValuesView(values) + + def __setitem__(self, key, value): + """Add a key-value pair to the properties. + + You may prefer to use :meth:`add` for clarity since this + method usually implies implicit overwriting of an existing key + if it exists, while for properties this is not the case. + """ + self.add(key, value) + + def add(self, key, value): + """Add a key-value pair to the properties.""" + if isinstance(key, str): + key = key.encode('utf-8') + if isinstance(value, str): + value = value.encode('utf-8') + ret = capi.lib.notmuch_message_add_property(self._ptr(), key, value) + if ret != capi.lib.NOTMUCH_STATUS_SUCCESS: + raise errors.NotmuchError(ret) + + def __delitem__(self, key): + """Remove all properties with this key.""" + if isinstance(key, str): + key = key.encode('utf-8') + ret = capi.lib.notmuch_message_remove_all_properties(self._ptr(), key) + if ret != capi.lib.NOTMUCH_STATUS_SUCCESS: + raise errors.NotmuchError(ret) + + def remove(self, key, value): + """Remove a key-value pair from the properties.""" + if isinstance(key, str): + key = key.encode('utf-8') + if isinstance(value, str): + value = value.encode('utf-8') + ret = capi.lib.notmuch_message_remove_property(self._ptr(), key, value) + if ret != capi.lib.NOTMUCH_STATUS_SUCCESS: + raise errors.NotmuchError(ret) + + def pop(self, key, default=_marker): + try: + value = self[key] + except KeyError: + if default is self._marker: + raise + else: + return default + else: + self.remove(key, value) + return value + + def popitem(self): + try: + key = next(iter(self)) + except StopIteration: + raise KeyError + value = self.pop(key) + return (key, value) + + def clear(self): + ret = capi.lib.notmuch_message_remove_all_properties(self._ptr(), + capi.ffi.NULL) + if ret != capi.lib.NOTMUCH_STATUS_SUCCESS: + raise errors.NotmuchError(ret) + + def getall(self, prefix='', *, exact=False): + """Return an iterator yielding all properties for a given key prefix. + + The returned iterator yields all peroperties which start with + a given key prefix as ``(key, value)`` namedtuples. If called + with ``exact=True`` then only properties which exactly match + the prefix are returned, those a key longer than the prefix + will not be included. + + :param prefix: The prefix of the key. + """ + if isinstance(prefix, str): + prefix = prefix.encode('utf-8') + props_p = capi.lib.notmuch_message_get_properties(self._ptr(), + prefix, exact) + return PropertiesIter(self, props_p) + + +class PropertiesKeyIter(base.NotmuchIter): + + def __init__(self, parent, iter_p): + super().__init__( + parent, + iter_p, + fn_destroy=capi.lib.notmuch_message_properties_destroy, + fn_valid=capi.lib.notmuch_message_properties_valid, + fn_get=capi.lib.notmuch_message_properties_key, + fn_next=capi.lib.notmuch_message_properties_move_to_next) + + def __next__(self): + item = super().__next__() + return base.BinString.from_cffi(item) + + +class PropertiesIter(base.NotmuchIter): + + def __init__(self, parent, iter_p): + super().__init__( + parent, + iter_p, + fn_destroy=capi.lib.notmuch_message_properties_destroy, + fn_valid=capi.lib.notmuch_message_properties_valid, + fn_get=capi.lib.notmuch_message_properties_key, + fn_next=capi.lib.notmuch_message_properties_move_to_next, + ) + + def __next__(self): + if not self._fn_valid(self._iter_p): + self._destroy() + raise StopIteration + key = capi.lib.notmuch_message_properties_key(self._iter_p) + value = capi.lib.notmuch_message_properties_value(self._iter_p) + capi.lib.notmuch_message_properties_move_to_next(self._iter_p) + return PropertiesMap.Property(base.BinString.from_cffi(key), + base.BinString.from_cffi(value)) + + +class PropertiesItemsView(collections.abc.Set): + + __slots__ = ('_items',) + + def __init__(self, items): + self._items = items + + @classmethod + def _from_iterable(self, it): + return set(it) + + def __len__(self): + return len(self._items) + + def __contains__(self, item): + return item in self._items + + def __iter__(self): + yield from self._items + + +collections.abc.ItemsView.register(PropertiesItemsView) + + +class PropertiesValuesView(collections.abc.Set): + + __slots__ = ('_values',) + + def __init__(self, values): + self._values = values + + def __len__(self): + return len(self._values) + + def __contains__(self, value): + return value in self._values + + def __iter__(self): + yield from self._values + + +collections.abc.ValuesView.register(PropertiesValuesView) + + +class MessageIter(base.NotmuchIter): + + def __init__(self, parent, msgs_p, *, db): + self._db = db + super().__init__(parent, msgs_p, + fn_destroy=capi.lib.notmuch_messages_destroy, + fn_valid=capi.lib.notmuch_messages_valid, + fn_get=capi.lib.notmuch_messages_get, + fn_next=capi.lib.notmuch_messages_move_to_next) + + def __next__(self): + msg_p = super().__next__() + return Message(self, msg_p, db=self._db) diff --git a/bindings/python-cffi/notdb/_query.py b/bindings/python-cffi/notdb/_query.py new file mode 100644 index 00000000..613aaf12 --- /dev/null +++ b/bindings/python-cffi/notdb/_query.py @@ -0,0 +1,83 @@ +from notdb import _base as base +from notdb import _capi as capi +from notdb import _errors as errors +from notdb import _message as message +from notdb import _thread as thread + + +__all__ = [] + + +class Query(base.NotmuchObject): + """Private, minimal query object. + + This is not meant for users and is not a full implementation of + the query API. It is only an intermediate used internally to + match libnotmuch's memory management. + """ + _query_p = base.MemoryPointer() + + def __init__(self, db, query_p): + self._db = db + self._query_p = query_p + + @property + def alive(self): + if not self._db.alive: + return False + try: + self._query_p + except errors.ObjectDestroyedError: + return False + else: + return True + + def __del__(self): + self._destroy() + + def _destroy(self): + if self.alive: + capi.lib.notmuch_query_destroy(self._query_p) + self._query_p = None + + @property + def query(self): + """The query string as seen by libnotmuch.""" + q = capi.lib.notmuch_query_get_query_string(self._query_p) + return base.BinString.from_cffi(q) + + def messages(self): + """Return an iterator over all the messages found by the query. + + This executes the query and returns an iterator over the + :class:`Message` objects found. + """ + msgs_pp = capi.ffi.new('notmuch_messages_t**') + ret = capi.lib.notmuch_query_search_messages(self._query_p, msgs_pp) + if ret != capi.lib.NOTMUCH_STATUS_SUCCESS: + raise errors.NotmuchError(ret) + return message.MessageIter(self, msgs_pp[0], db=self._db) + + def count_messages(self): + """Return the number of messages matching this query.""" + count_p = capi.ffi.new('unsigned int *') + ret = capi.lib.notmuch_query_count_messages(self._query_p, count_p) + if ret != capi.lib.NOTMUCH_STATUS_SUCCESS: + raise errors.NotmuchError(ret) + return count_p[0] + + def threads(self): + """Return an iterator over all the threads found by the query.""" + threads_pp = capi.ffi.new('notmuch_threads_t **') + ret = capi.lib.notmuch_query_search_threads(self._query_p, threads_pp) + if ret != capi.lib.NOTMUCH_STATUS_SUCCESS: + raise errors.NotmuchError(ret) + return thread.ThreadIter(self, threads_pp[0], db=self._db) + + def count_threads(self): + """Return the number of threads matching this query.""" + count_p = capi.ffi.new('unsigned int *') + ret = capi.lib.notmuch_query_count_threads(self._query_p, count_p) + if ret != capi.lib.NOTMUCH_STATUS_SUCCESS: + raise errors.NotmuchError(ret) + return count_p[0] diff --git a/bindings/python-cffi/notdb/_tags.py b/bindings/python-cffi/notdb/_tags.py new file mode 100644 index 00000000..a25a2264 --- /dev/null +++ b/bindings/python-cffi/notdb/_tags.py @@ -0,0 +1,338 @@ +import collections.abc + +import notdb._base as base +import notdb._capi as capi +import notdb._errors as errors + + +__all__ = ['ImmutableTagSet', 'MutableTagSet', 'TagsIter'] + + +class ImmutableTagSet(base.NotmuchObject, collections.abc.Set): + """The tags associated with a message thread or whole database. + + Both a thread as well as the database expose the union of all tags + in messages associated with them. This exposes these as a + :class:`collections.abc.Set` object. + + Note that due to the underlying notmuch API the performance of the + implementation is not the same as you would expect from normal + sets. E.g. the :meth:`__contains__` and :meth:`__len__` are O(n) + rather then O(1). + + Tags are internally stored as bytestrings but normally exposed as + unicode strings using the UTF-8 encoding and the *ignore* decoder + error handler. However the :meth:`iter` method can be used to + return tags as bytestrings or using a different error handler. + + Note that when doing arithmetic operations on tags, this class + will return a plain normal set as it is no longer associated with + the message. + + :param parent: the parent object + :param ptr_name: the name of the attribute on the parent which will + return the memory pointer. This allows this object to + access the pointer via the parent's descriptor and thus + trigger :class:`MemoryPointer`'s memory safety. + :param cffi_fn: the callable CFFI wrapper to retrieve the tags + iter. This can be one of notmuch_database_get_all_tags, + notmuch_thread_get_tags or notmuch_message_get_tags. + """ + + def __init__(self, parent, ptr_name, cffi_fn): + self._parent = parent + self._ptr = lambda: getattr(parent, ptr_name) + self._cffi_fn = cffi_fn + + def __del__(self): + self._destroy() + + @property + def alive(self): + return self._parent.alive + + def _destroy(self): + pass + + @classmethod + def _from_iterable(cls, it): + return set(it) + + def __iter__(self): + """Return an iterator over the tags. + + Tags are yielded as unicode strings, decoded using the + "ignore" error handler. + + :raises NullPointerError: If the iterator can not be created. + """ + return self.iter(encoding='utf-8', errors='ignore') + + def iter(self, *, encoding=None, errors='strict'): + """Aternate iterator constructor controlling string decoding. + + Tags are stored as bytes in the notmuch database, in Python + it's easier to work with unicode strings and thus is what the + normal iterator returns. However this method allows you to + specify how you would like to get the tags, defaulting to the + bytestring representation instead of unicode strings. + + :param encoding: Which codec to use. The default *None* does not + decode at all and will return the unmodified bytes. + Otherwise this is passed on to :func:`str.decode`. + :param errors: If using a codec, this is the error handler. + See :func:`str.decode` to which this is passed on. + + :raises NullPointerError: When things do not go as planned. + """ + # self._cffi_fn should point either to + # notmuch_database_get_all_tags, notmuch_thread_get_tags or + # notmuch_message_get_tags. nothmuch.h suggests these never + # fail, let's handle NULL anyway. + tags_p = self._cffi_fn(self._ptr()) + if tags_p == capi.ffi.NULL: + raise errors.NullPointerError() + tags = TagsIter(self, tags_p, encoding=encoding, errors=errors) + return tags + + def __len__(self): + return sum(1 for t in self) + + def __contains__(self, tag): + if isinstance(tag, str): + tag = tag.encode() + for msg_tag in self.iter(): + if tag == msg_tag: + return True + else: + return False + + def __eq__(self, other): + return tuple(sorted(self.iter())) == tuple(sorted(other.iter())) + + def __hash__(self): + return hash(tuple(self.iter())) + + def __repr__(self): + return '<{name} object at 0x{addr:x} tags={{{tags}}}>'.format( + name=self.__class__.__name__, + addr=id(self), + tags=', '.join(repr(t) for t in self)) + + +class MutableTagSet(ImmutableTagSet, collections.abc.MutableSet): + """The tags associated with a message. + + This is a :class:`collections.abc.MutableSet` object which can be + used to manipulate the tags of a message. + + Note that due to the underlying notmuch API the performance of the + implementation is not the same as you would expect from normal + sets. E.g. the ``in`` operator and variants are O(n) rather then + O(1). + + Tags are bytestrings and calling ``iter()`` will return an + iterator yielding bytestrings. However the :meth:`iter` method + can be used to return tags as unicode strings, while all other + operations accept either byestrings or unicode strings. In case + unicode strings are used they will be encoded using utf-8 before + being passed to notmuch. + """ + + # Since we subclass ImmutableTagSet we inherit a __hash__. But we + # are mutable, setting it to None will make the Python machinary + # recognise us as unhashable. + __hash__ = None + + def add(self, tag): + """Add a tag to the message. + + :param tag: The tag to add. + :type tag: str or bytes. A str will be encoded using UTF-8. + + :param sync_flags: Whether to sync the maildir flags with the + new set of tags. Leaving this as *None* respects the + configuration set in the database, while *True* will always + sync and *False* will never sync. + :param sync_flags: NoneType or bool + + :raises TypeError: If the tag is not a valid type. + :raises TagTooLongError: If the added tag exceeds the maximum + lenght, see ``notmuch_cffi.NOTMUCH_TAG_MAX``. + :raises ReadOnlyDatabaseError: If the database is opened in + read-only mode. + """ + if isinstance(tag, str): + tag = tag.encode() + if not isinstance(tag, bytes): + raise TypeError('Not a valid type for a tag: {}'.format(type(tag))) + ret = capi.lib.notmuch_message_add_tag(self._ptr(), tag) + if ret != capi.lib.NOTMUCH_STATUS_SUCCESS: + raise errors.NotmuchError(ret) + + def discard(self, tag): + """Remove a tag from the message. + + :param tag: The tag to remove. + :type tag: str of bytes. A str will be encoded using UTF-8. + :param sync_flags: Whether to sync the maildir flags with the + new set of tags. Leaving this as *None* respects the + configuration set in the database, while *True* will always + sync and *False* will never sync. + :param sync_flags: NoneType or bool + + :raises TypeError: If the tag is not a valid type. + :raises TagTooLongError: If the tag exceeds the maximum + lenght, see ``notmuch_cffi.NOTMUCH_TAG_MAX``. + :raises ReadOnlyDatabaseError: If the database is opened in + read-only mode. + """ + if isinstance(tag, str): + tag = tag.encode() + if not isinstance(tag, bytes): + raise TypeError('Not a valid type for a tag: {}'.format(type(tag))) + ret = capi.lib.notmuch_message_remove_tag(self._ptr(), tag) + if ret != capi.lib.NOTMUCH_STATUS_SUCCESS: + raise errors.NotmuchError(ret) + + def clear(self): + """Remove all tags from the message. + + :raises ReadOnlyDatabaseError: If the database is opened in + read-only mode. + """ + ret = capi.lib.notmuch_message_remove_all_tags(self._ptr()) + if ret != capi.lib.NOTMUCH_STATUS_SUCCESS: + raise errors.NotmuchError(ret) + + def from_maildir_flags(self): + """Update the tags based on the state in the message's maildir flags. + + This function examines the filenames of 'message' for maildir + flags, and adds or removes tags on 'message' as follows when + these flags are present: + + Flag Action if present + ---- ----------------- + 'D' Adds the "draft" tag to the message + 'F' Adds the "flagged" tag to the message + 'P' Adds the "passed" tag to the message + 'R' Adds the "replied" tag to the message + 'S' Removes the "unread" tag from the message + + For each flag that is not present, the opposite action + (add/remove) is performed for the corresponding tags. + + Flags are identified as trailing components of the filename + after a sequence of ":2,". + + If there are multiple filenames associated with this message, + the flag is considered present if it appears in one or more + filenames. (That is, the flags from the multiple filenames are + combined with the logical OR operator.) + """ + ret = capi.lib.notmuch_message_maildir_flags_to_tags(self._ptr()) + if ret != capi.lib.NOTMUCH_STATUS_SUCCESS: + raise errors.NotmuchError(ret) + + def to_maildir_flags(self): + """Update the message's maildir flags based on the notmuch tags. + + If the message's filename is in a maildir directory, that is a + directory named ``new`` or ``cur``, and has a valid maildir + filename then the flags will be added as such: + + 'D' if the message has the "draft" tag + 'F' if the message has the "flagged" tag + 'P' if the message has the "passed" tag + 'R' if the message has the "replied" tag + 'S' if the message does not have the "unread" tag + + Any existing flags unmentioned in the list above will be + preserved in the renaming. + + Also, if this filename is in a directory named "new", rename it to + be within the neighboring directory named "cur". + + In case there are multiple files associated with the message + all filenames will get the same logic applied. + """ + ret = capi.lib.notmuch_message_tags_to_maildir_flags(self._ptr()) + if ret != capi.lib.NOTMUCH_STATUS_SUCCESS: + raise errors.NotmuchError(ret) + + +class TagsIter(base.NotmuchObject, collections.abc.Iterator): + """Iterator over tags. + + This is only an interator, not a container so calling + :meth:`__iter__` does not return a new, replenished iterator but + only itself. + + :param parent: The parent object to keep alive. + :param tags_p: The CFFI pointer to the C-level tags iterator. + :param encoding: Which codec to use. The default *None* does not + decode at all and will return the unmodified bytes. + Otherwise this is passed on to :func:`str.decode`. + :param errors: If using a codec, this is the error handler. + See :func:`str.decode` to which this is passed on. + + :raises ObjectDestoryedError: if used after destroyed. + """ + _tags_p = base.MemoryPointer() + + def __init__(self, parent, tags_p, *, encoding=None, errors='strict'): + self._parent = parent + self._tags_p = tags_p + self._encoding = encoding + self._errors = errors + + def __del__(self): + self._destroy() + + @property + def alive(self): + if not self._parent.alive: + return False + try: + self._tags_p + except errors.ObjectDestroyedError: + return False + else: + return True + + def _destroy(self): + if self.alive: + try: + capi.lib.notmuch_tags_destroy(self._tags_p) + except errors.ObjectDestroyedError: + pass + self._tags_p = None + + def __iter__(self): + """Return the iterator itself. + + Note that as this is an iterator and not a container this will + not return a new iterator. Thus any elements already consumed + will not be yielded by the :meth:`__next__` method anymore. + """ + return self + + def __next__(self): + if not capi.lib.notmuch_tags_valid(self._tags_p): + self._destroy() + raise StopIteration() + tag_p = capi.lib.notmuch_tags_get(self._tags_p) + tag = capi.ffi.string(tag_p) + if self._encoding: + tag = tag.decode(encoding=self._encoding, errors=self._errors) + capi.lib.notmuch_tags_move_to_next(self._tags_p) + return tag + + def __repr__(self): + try: + self._tags_p + except errors.ObjectDestroyedError: + return '' + else: + return '' diff --git a/bindings/python-cffi/notdb/_thread.py b/bindings/python-cffi/notdb/_thread.py new file mode 100644 index 00000000..e1ef6b07 --- /dev/null +++ b/bindings/python-cffi/notdb/_thread.py @@ -0,0 +1,190 @@ +import collections.abc +import weakref + +from notdb import _base as base +from notdb import _capi as capi +from notdb import _errors as errors +from notdb import _message as message +from notdb import _tags as tags + + +__all__ = ['Thread'] + + +class Thread(base.NotmuchObject, collections.abc.Iterable): + _thread_p = base.MemoryPointer() + + def __init__(self, parent, thread_p, *, db): + self._parent = parent + self._thread_p = thread_p + self._db = db + + @property + def alive(self): + if not self._parent.alive: + return False + try: + self._thread_p + except errors.ObjectDestroyedError: + return False + else: + return True + + def __del__(self): + self._destroy() + + def _destroy(self): + if self.alive: + capi.lib.notmuch_thread_destroy(self._thread_p) + self._thread_p = None + + @property + def threadid(self): + """The thread ID as a :class:`BinString`. + + :raises ObjectDestroyedError: if used after destoryed. + """ + ret = capi.lib.notmuch_thread_get_thread_id(self._thread_p) + return base.BinString.from_cffi(ret) + + def __len__(self): + """Return the number of messages in the thread. + + :raises ObjectDestroyedError: if used after destoryed. + """ + return capi.lib.notmuch_thread_get_total_messages(self._thread_p) + + def toplevel(self): + """Return an iterator of the toplevel messages. + + :returns: An iterator yielding :class:`Message` instances. + + :raises ObjectDestroyedError: if used after destoryed. + """ + msgs_p = capi.lib.notmuch_thread_get_toplevel_messages(self._thread_p) + return message.MessageIter(self, msgs_p, db=self._db) + + def __iter__(self): + """Return an iterator over all the messages in the thread. + + :returns: An iterator yielding :class:`Message` instances. + + :raises ObjectDestroyedError: if used after destoryed. + """ + msgs_p = capi.lib.notmuch_thread_get_messages(self._thread_p) + return message.MessageIter(self, msgs_p, db=self._db) + + @property + def matched(self): + """The number of messages in this thread which matched the query. + + Of the messages in the thread this gives the count of messages + which did directly match the search query which this thread + originates from. + + :raises ObjectDestroyedError: if used after destoryed. + """ + return capi.lib.notmuch_thread_get_matched_messages(self._thread_p) + + @property + def authors(self): + """A comma-separated string of all authors in the thread. + + Authors of messages which matched the query the thread was + retrieved from will be at the head of the string, ordered by + date of their messages. Following this will be the authors of + the other messages in the thread, also ordered by date of + their messages. Both groups of authors are separated by the + ``|`` character. + + :returns: The stringified list of authors. + :rtype: BinString + + :raises ObjectDestroyedError: if used after destoryed. + """ + ret = capi.lib.notmuch_thread_get_authors(self._thread_p) + return base.BinString.from_cffi(ret) + + @property + def subject(self): + """The subject of the thread, taken from the first message. + + The thread's subject is taken to be the subject of the first + message according to query sort order. + + :returns: The thread's subject. + :rtype: BinString + + :raises ObjectDestroyedError: if used after destoryed. + """ + ret = capi.lib.notmuch_thread_get_subject(self._thread_p) + return base.BinString.from_cffi(ret) + + @property + def first(self): + """Return the date of the oldest message in the thread. + + The time the first message was sent as an integer number of + seconds since the *epoch*, 1 Jan 1970. + + :raises ObjectDestroyedError: if used after destoryed. + """ + return capi.lib.notmuch_thread_get_oldest_date(self._thread_p) + + @property + def last(self): + """Return the date of the newest message in the thread. + + The time the last message was sent as an integer number of + seconds since the *epoch*, 1 Jan 1970. + + :raises ObjectDestroyedError: if used after destoryed. + """ + return capi.lib.notmuch_thread_get_newest_date(self._thread_p) + + @property + def tags(self): + """Return an immutable set with all tags used in this thread. + + This returns an immutable set-like object implementing the + collections.abc.Set Abstract Base Class. Due to the + underlying libnotmuch implementation some operations have + different performance characteristics then plain set objects. + Mainly any lookup operation is O(n) rather then O(1). + + Normal usage treats tags as UTF-8 encoded unicode strings so + they are exposed to Python as normal unicode string objects. + If you need to handle tags stored in libnotmuch which are not + valid unicode do check the :class:`ImmutableTagSet` docs for + how to handle this. + + :rtype: ImmutableTagSet + + :raises ObjectDestroyedError: if used after destoryed. + """ + try: + ref = self._cached_tagset + except AttributeError: + tagset = None + else: + tagset = ref() + if tagset is None: + tagset = tags.ImmutableTagSet( + self, '_thread_p', capi.lib.notmuch_thread_get_tags) + self._cached_tagset = weakref.ref(tagset) + return tagset + + +class ThreadIter(base.NotmuchIter): + + def __init__(self, parent, threads_p, *, db): + self._db = db + super().__init__(parent, threads_p, + fn_destroy=capi.lib.notmuch_threads_destroy, + fn_valid=capi.lib.notmuch_threads_valid, + fn_get=capi.lib.notmuch_threads_get, + fn_next=capi.lib.notmuch_threads_move_to_next) + + def __next__(self): + thread_p = super().__next__() + return Thread(self, thread_p, db=self._db) diff --git a/bindings/python-cffi/setup.py b/bindings/python-cffi/setup.py new file mode 100644 index 00000000..7baf63cf --- /dev/null +++ b/bindings/python-cffi/setup.py @@ -0,0 +1,22 @@ +import setuptools + + +setuptools.setup( + name='notdb', + version='0.1', + description='Pythonic bindings for the notmuch mail database using CFFI', + author='Floris Bruynooghe', + author_email='flub@devork.be', + setup_requires=['cffi>=1.0.0'], + install_requires=['cffi>=1.0.0'], + packages=setuptools.find_packages(exclude=['tests']), + cffi_modules=['notdb/_build.py:ffibuilder'], + classifiers=[ + 'Development Status :: 3 - Alpha', + 'Intended Audience :: Developers', + 'License :: OSI Approved :: GNU General Public License (GPL)', + 'Programming Language :: Python :: 3', + 'Topic :: Communications :: Email', + 'Topic :: Software Development :: Libraries', + ], +) diff --git a/bindings/python-cffi/tests/conftest.py b/bindings/python-cffi/tests/conftest.py new file mode 100644 index 00000000..1b7bbc35 --- /dev/null +++ b/bindings/python-cffi/tests/conftest.py @@ -0,0 +1,142 @@ +import email.message +import mailbox +import pathlib +import socket +import subprocess +import textwrap +import time + +import pytest + + +@pytest.fixture(scope='function') +def tmppath(tmpdir): + """The tmpdir fixture wrapped in pathlib.Path.""" + return pathlib.Path(str(tmpdir)) + + +@pytest.fixture +def notmuch(maildir): + """Return a function which runs notmuch commands on our test maildir. + + This uses the notmuch-config file created by the ``maildir`` + fixture. + """ + def run(*args): + """Run a notmuch comand. + + This function runs with a timeout error as many notmuch + commands may block if multiple processes are trying to open + the database in write-mode. It is all too easy to + accidentally do this in the unittests. + """ + cfg_fname = maildir.path / 'notmuch-config' + cmd = ['notmuch'] + list(args) + print('Invoking: {}'.format(' '.join(cmd))) + proc = subprocess.run(cmd, + timeout=5, + env={'NOTMUCH_CONFIG': str(cfg_fname)}) + proc.check_returncode() + return run + + +@pytest.fixture +def maildir(tmppath): + """A basic test interface to a valid maildir directory. + + This creates a valid maildir and provides a simple mechanism to + deliver test emails to it. It also writes a notmuch-config file + in the top of the maildir. + """ + cur = tmppath / 'cur' + cur.mkdir() + new = tmppath / 'new' + new.mkdir() + tmp = tmppath / 'tmp' + tmp.mkdir() + cfg_fname = tmppath/'notmuch-config' + with cfg_fname.open('w') as fp: + fp.write(textwrap.dedent("""\ + [database] + path={tmppath!s} + [user] + name=Some Hacker + primary_email=dst@example.com + [new] + tags=unread;inbox; + ignore= + [search] + exclude_tags=deleted;spam; + [maildir] + synchronize_flags=true + [crypto] + gpg_path=gpg + """.format(tmppath=tmppath))) + return MailDir(tmppath) + + +class MailDir: + """An interface around a correct maildir.""" + + def __init__(self, path): + self._path = pathlib.Path(path) + self.mailbox = mailbox.Maildir(str(path)) + self._idcount = 0 + + @property + def path(self): + """The pathname of the maildir.""" + return self._path + + def _next_msgid(self): + """Return a new unique message ID.""" + msgid = '{}@{}'.format(self._idcount, socket.getfqdn()) + self._idcount += 1 + return msgid + + def deliver(self, + subject='Test mail', + body='This is a test mail', + to='dst@example.com', + frm='src@example.com', + headers=None, + new=False, # Move to new dir or cur dir? + keywords=None, # List of keywords or labels + seen=False, # Seen flag (cur dir only) + replied=False, # Replied flag (cur dir only) + flagged=False): # Flagged flag (cur dir only) + """Deliver a new mail message in the mbox. + + This does only adds the message to maildir, does not insert it + into the notmuch database. + + :returns: A tuple of (msgid, pathname). + """ + msgid = self._next_msgid() + when = time.time() + msg = email.message.EmailMessage() + msg.add_header('Received', 'by MailDir; {}'.format(time.ctime(when))) + msg.add_header('Message-ID', '<{}>'.format(msgid)) + msg.add_header('Date', time.ctime(when)) + msg.add_header('From', frm) + msg.add_header('To', to) + msg.add_header('Subject', subject) + if headers: + for h, v in headers: + msg.add_header(h, v) + msg.set_content(body) + mdmsg = mailbox.MaildirMessage(msg) + if not new: + mdmsg.set_subdir('cur') + if flagged: + mdmsg.add_flag('F') + if replied: + mdmsg.add_flag('R') + if seen: + mdmsg.add_flag('S') + boxid = self.mailbox.add(mdmsg) + basename = boxid + if mdmsg.get_info(): + basename += mailbox.Maildir.colon + mdmsg.get_info() + msgpath = self.path / mdmsg.get_subdir() / basename + return (msgid, msgpath) diff --git a/bindings/python-cffi/tests/test_base.py b/bindings/python-cffi/tests/test_base.py new file mode 100644 index 00000000..b6d3d62c --- /dev/null +++ b/bindings/python-cffi/tests/test_base.py @@ -0,0 +1,116 @@ +import pytest + +from notdb import _base as base +from notdb import _errors as errors + + +class TestNotmuchObject: + + def test_no_impl_methods(self): + class Object(base.NotmuchObject): + pass + with pytest.raises(TypeError): + Object() + + def test_impl_methods(self): + + class Object(base.NotmuchObject): + + def __init__(self): + pass + + @property + def alive(self): + pass + + def _destroy(self, parent=False): + pass + + Object() + + def test_del(self): + destroyed = False + + class Object(base.NotmuchObject): + + def __init__(self): + pass + + @property + def alive(self): + pass + + def _destroy(self, parent=False): + nonlocal destroyed + destroyed = True + + o = Object() + o.__del__() + assert destroyed + + +class TestMemoryPointer: + + @pytest.fixture + def obj(self): + class Cls: + ptr = base.MemoryPointer() + return Cls() + + def test_unset(self, obj): + with pytest.raises(errors.ObjectDestroyedError): + obj.ptr + + def test_set(self, obj): + obj.ptr = 'some' + assert obj.ptr == 'some' + + def test_cleared(self, obj): + obj.ptr = 'some' + obj.ptr + obj.ptr = None + with pytest.raises(errors.ObjectDestroyedError): + obj.ptr + + def test_two_instances(self, obj): + obj2 = obj.__class__() + obj.ptr = 'foo' + obj2.ptr = 'bar' + assert obj.ptr != obj2.ptr + + +class TestBinString: + + def test_type(self): + s = base.BinString(b'foo') + assert isinstance(s, str) + + def test_init_bytes(self): + s = base.BinString(b'foo') + assert s == 'foo' + + def test_init_str(self): + s = base.BinString('foo') + assert s == 'foo' + + def test_bytes(self): + s = base.BinString(b'foo') + assert bytes(s) == b'foo' + + def test_invalid_utf8(self): + s = base.BinString(b'\x80foo') + assert s == 'foo' + assert bytes(s) == b'\x80foo' + + def test_errors(self): + s = base.BinString(b'\x80foo', errors='replace') + assert s == '�foo' + assert bytes(s) == b'\x80foo' + + def test_encoding(self): + # pound sign: '£' == '\u00a3' latin-1: b'\xa3', utf-8: b'\xc2\xa3' + with pytest.raises(UnicodeDecodeError): + base.BinString(b'\xa3', errors='strict') + s = base.BinString(b'\xa3', encoding='latin-1', errors='strict') + assert s == '£' + assert bytes(s) == b'\xa3' diff --git a/bindings/python-cffi/tests/test_database.py b/bindings/python-cffi/tests/test_database.py new file mode 100644 index 00000000..02de0f41 --- /dev/null +++ b/bindings/python-cffi/tests/test_database.py @@ -0,0 +1,326 @@ +import collections +import configparser +import os +import pathlib + +import pytest + +import notdb +import notdb._errors as errors +import notdb._database as dbmod +import notdb._message as message + + +@pytest.fixture +def db(maildir): + with dbmod.Database.create(maildir.path) as db: + yield db + + +class TestDefaultDb: + """Tests for reading the default database. + + The error cases are fairly undefined, some relevant Python error + will come out if you give it a bad filename or if the file does + not parse correctly. So we're not testing this too deeply. + """ + + def test_config_pathname_default(self, monkeypatch): + monkeypatch.delenv('NOTMUCH_CONFIG', raising=False) + user = pathlib.Path('~/.notmuch-config').expanduser() + assert dbmod._config_pathname() == user + + def test_config_pathname_env(self, monkeypatch): + monkeypatch.setenv('NOTMUCH_CONFIG', '/some/random/path') + assert dbmod._config_pathname() == pathlib.Path('/some/random/path') + + def test_default_path_nocfg(self, monkeypatch, tmppath): + monkeypatch.setenv('NOTMUCH_CONFIG', str(tmppath/'foo')) + with pytest.raises(FileNotFoundError): + dbmod.Database.default_path() + + def test_default_path_cfg_is_dir(self, monkeypatch, tmppath): + monkeypatch.setenv('NOTMUCH_CONFIG', str(tmppath)) + with pytest.raises(IsADirectoryError): + dbmod.Database.default_path() + + def test_default_path_parseerr(self, monkeypatch, tmppath): + cfg = tmppath / 'notmuch-config' + with cfg.open('w') as fp: + fp.write('invalid') + monkeypatch.setenv('NOTMUCH_CONFIG', str(cfg)) + with pytest.raises(configparser.Error): + dbmod.Database.default_path() + + def test_default_path_parse(self, monkeypatch, tmppath): + cfg = tmppath / 'notmuch-config' + with cfg.open('w') as fp: + fp.write('[database]\n') + fp.write('path={!s}'.format(tmppath)) + monkeypatch.setenv('NOTMUCH_CONFIG', str(cfg)) + assert dbmod.Database.default_path() == tmppath + + def test_default_path_param(self, monkeypatch, tmppath): + cfg_dummy = tmppath / 'dummy' + monkeypatch.setenv('NOTMUCH_CONFIG', str(cfg_dummy)) + cfg_real = tmppath / 'notmuch_config' + with cfg_real.open('w') as fp: + fp.write('[database]\n') + fp.write('path={!s}'.format(cfg_real/'mail')) + assert dbmod.Database.default_path(cfg_real) == cfg_real/'mail' + + +class TestCreate: + + def test_create(self, tmppath, db): + assert tmppath.joinpath('.notmuch/xapian/').exists() + + def test_create_already_open(self, tmppath, db): + with pytest.raises(errors.NotmuchError): + db.create(tmppath) + + def test_create_existing(self, tmppath, db): + with pytest.raises(errors.FileError): + dbmod.Database.create(path=tmppath) + + def test_close(self, db): + db.close() + + def test_del_noclose(self, db): + del db + + def test_close_del(self, db): + db.close() + del db + + def test_closed_attr(self, db): + assert not db.closed + db.close() + assert db.closed + + def test_ctx(self, db): + with db as ctx: + assert ctx is db + assert not db.closed + assert db.closed + + def test_path(self, db, tmppath): + assert db.path == tmppath + + def test_version(self, db): + assert db.version > 0 + + def test_needs_upgrade(self, db): + assert db.needs_upgrade in (True, False) + + +class TestAtomic: + + def test_exit_early(self, db): + with pytest.raises(errors.UnbalancedAtomicError): + with db.atomic() as ctx: + ctx.force_end() + + def test_exit_late(self, db): + with db.atomic() as ctx: + pass + with pytest.raises(errors.UnbalancedAtomicError): + ctx.force_end() + + +class TestRevision: + + def test_single_rev(self, db): + r = db.revision() + assert isinstance(r, dbmod.DbRevision) + assert isinstance(r.rev, int) + assert isinstance(r.uuid, bytes) + assert r is r + assert r == r + assert r <= r + assert r >= r + assert not r < r + assert not r > r + + def test_diff_db(self, tmppath): + dbpath0 = tmppath.joinpath('db0') + dbpath0.mkdir() + dbpath1 = tmppath.joinpath('db1') + dbpath1.mkdir() + db0 = dbmod.Database.create(path=dbpath0) + db1 = dbmod.Database.create(path=dbpath1) + r_db0 = db0.revision() + r_db1 = db1.revision() + assert r_db0 != r_db1 + assert r_db0.uuid != r_db1.uuid + + def test_cmp(self, db, maildir): + rev0 = db.revision() + _, pathname = maildir.deliver() + db.add(pathname, sync_flags=False) + rev1 = db.revision() + assert rev0 < rev1 + assert rev0 <= rev1 + assert not rev0 > rev1 + assert not rev0 >= rev1 + assert not rev0 == rev1 + assert rev0 != rev1 + + # XXX add tests for revisions comparisons + +class TestMessages: + + def test_add_message(self, db, maildir): + msgid, pathname = maildir.deliver() + msg, dup = db.add(pathname, sync_flags=False) + assert isinstance(msg, message.Message) + assert msg.path == pathname + assert msg.messageid == msgid + + def test_add_message_str(self, db, maildir): + msgid, pathname = maildir.deliver() + msg, dup = db.add(str(pathname), sync_flags=False) + + def test_add_message_bytes(self, db, maildir): + msgid, pathname = maildir.deliver() + msg, dup = db.add(os.fsencode(bytes(pathname)), sync_flags=False) + + def test_remove_message(self, db, maildir): + msgid, pathname = maildir.deliver() + msg, dup = db.add(pathname, sync_flags=False) + assert db.find(msgid) + dup = db.remove(pathname) + with pytest.raises(LookupError): + db.find(msgid) + + def test_remove_message_str(self, db, maildir): + msgid, pathname = maildir.deliver() + msg, dup = db.add(pathname, sync_flags=False) + assert db.find(msgid) + dup = db.remove(str(pathname)) + with pytest.raises(LookupError): + db.find(msgid) + + def test_remove_message_bytes(self, db, maildir): + msgid, pathname = maildir.deliver() + msg, dup = db.add(pathname, sync_flags=False) + assert db.find(msgid) + dup = db.remove(os.fsencode(bytes(pathname))) + with pytest.raises(LookupError): + db.find(msgid) + + def test_find_message(self, db, maildir): + msgid, pathname = maildir.deliver() + msg0, dup = db.add(pathname, sync_flags=False) + msg1 = db.find(msgid) + assert isinstance(msg1, message.Message) + assert msg1.messageid == msgid == msg0.messageid + assert msg1.path == pathname == msg0.path + + def test_find_message_notfound(self, db): + with pytest.raises(LookupError): + db.find('foo') + + def test_get_message(self, db, maildir): + msgid, pathname = maildir.deliver() + msg0, _ = db.add(pathname, sync_flags=False) + msg1 = db.get(pathname) + assert isinstance(msg1, message.Message) + assert msg1.messageid == msgid == msg0.messageid + assert msg1.path == pathname == msg0.path + + def test_get_message_str(self, db, maildir): + msgid, pathname = maildir.deliver() + db.add(pathname, sync_flags=False) + msg = db.get(str(pathname)) + assert msg.messageid == msgid + + def test_get_message_bytes(self, db, maildir): + msgid, pathname = maildir.deliver() + db.add(pathname, sync_flags=False) + msg = db.get(os.fsencode(bytes(pathname))) + assert msg.messageid == msgid + + +class TestTags: + # We just want to test this behaves like a set at a hight level. + # The set semantics are tested in detail in the test_tags module. + + def test_type(self, db): + assert isinstance(db.tags, collections.abc.Set) + + def test_none(self, db): + itags = iter(db.tags) + with pytest.raises(StopIteration): + next(itags) + assert len(db.tags) == 0 + assert not db.tags + + def test_some(self, db, maildir): + _, pathname = maildir.deliver() + msg, _ = db.add(pathname, sync_flags=False) + msg.tags.add('hello') + itags = iter(db.tags) + assert next(itags) == 'hello' + with pytest.raises(StopIteration): + next(itags) + assert 'hello' in msg.tags + + def test_cache(self, db): + assert db.tags is db.tags + + def test_iters(self, db): + i1 = iter(db.tags) + i2 = iter(db.tags) + assert i1 is not i2 + + +class TestQuery: + + @pytest.fixture + def db(self, maildir, notmuch): + """Return a read-only notdb.Database. + + The database will have 3 messages, 2 threads. + """ + msgid, _ = maildir.deliver(body='foo') + maildir.deliver(body='bar') + maildir.deliver(body='baz', + headers=[('In-Reply-To', '<{}>'.format(msgid))]) + notmuch('new') + with dbmod.Database(maildir.path, 'rw') as db: + yield db + + def test_count_messages(self, db): + assert db.count_messages('*') == 3 + + def test_messages_type(self, db): + msgs = db.messages('*') + assert isinstance(msgs, collections.abc.Iterator) + + def test_message_no_results(self, db): + msgs = db.messages('not_a_matching_query') + with pytest.raises(StopIteration): + next(msgs) + + def test_message_match(self, db): + msgs = db.messages('*') + msg = next(msgs) + assert isinstance(msg, notdb.Message) + + def test_count_threads(self, db): + assert db.count_threads('*') == 2 + + def test_threads_type(self, db): + threads = db.threads('*') + assert isinstance(threads, collections.abc.Iterator) + + def test_threads_no_match(self, db): + threads = db.threads('not_a_matching_query') + with pytest.raises(StopIteration): + next(threads) + + def test_threads_match(self, db): + threads = db.threads('*') + thread = next(threads) + assert isinstance(thread, notdb.Thread) diff --git a/bindings/python-cffi/tests/test_message.py b/bindings/python-cffi/tests/test_message.py new file mode 100644 index 00000000..56d06f34 --- /dev/null +++ b/bindings/python-cffi/tests/test_message.py @@ -0,0 +1,226 @@ +import collections.abc +import time +import pathlib + +import pytest + +import notdb + + +class TestMessage: + MaildirMsg = collections.namedtuple('MaildirMsg', ['msgid', 'path']) + + @pytest.fixture + def maildir_msg(self, maildir): + msgid, path = maildir.deliver() + return self.MaildirMsg(msgid, path) + + @pytest.fixture + def db(self, maildir): + with notdb.Database.create(maildir.path) as db: + yield db + + @pytest.fixture + def msg(self, db, maildir_msg): + msg, dup = db.add(maildir_msg.path, sync_flags=False) + yield msg + + def test_type(self, msg): + assert isinstance(msg, notdb.NotmuchObject) + assert isinstance(msg, notdb.Message) + + def test_alive(self, msg): + assert msg.alive + + def test_hash(self, msg): + assert hash(msg) + + def test_eq(self, db, msg): + copy = db.get(msg.path) + assert msg == copy + + def test_messageid_type(self, msg): + assert isinstance(msg.messageid, str) + assert isinstance(msg.messageid, notdb.BinString) + assert isinstance(bytes(msg.messageid), bytes) + + def test_messageid(self, msg, maildir_msg): + assert msg.messageid == maildir_msg.msgid + + def test_messageid_find(self, db, msg): + copy = db.find(msg.messageid) + assert msg.messageid == copy.messageid + + def test_threadid_type(self, msg): + assert isinstance(msg.threadid, str) + assert isinstance(msg.threadid, notdb.BinString) + assert isinstance(bytes(msg.threadid), bytes) + + def test_path_type(self, msg): + assert isinstance(msg.path, pathlib.Path) + + def test_path(self, msg, maildir_msg): + assert msg.path == maildir_msg.path + + def test_pathb_type(self, msg): + assert isinstance(msg.pathb, bytes) + + def test_pathb(self, msg, maildir_msg): + assert msg.path == maildir_msg.path + + def test_filenames_type(self, msg): + ifn = msg.filenames() + assert isinstance(ifn, collections.abc.Iterator) + + def test_filenames(self, msg): + ifn = msg.filenames() + fn = next(ifn) + assert fn == msg.path + assert isinstance(fn, pathlib.Path) + with pytest.raises(StopIteration): + next(ifn) + assert list(msg.filenames()) == [msg.path] + + def test_filenamesb_type(self, msg): + ifn = msg.filenamesb() + assert isinstance(ifn, collections.abc.Iterator) + + def test_filenamesb(self, msg): + ifn = msg.filenamesb() + fn = next(ifn) + assert fn == msg.pathb + assert isinstance(fn, bytes) + with pytest.raises(StopIteration): + next(ifn) + assert list(msg.filenamesb()) == [msg.pathb] + + def test_ghost_no(self, msg): + assert not msg.ghost + + def test_date(self, msg): + # XXX Someone seems to treat things as local time instead of + # UTC or the other way around. + now = int(time.time()) + assert abs(now - msg.date) < 3600*24 + + def test_header(self, msg): + assert msg.header('from') == 'src@example.com' + + def test_header_not_present(self, msg): + with pytest.raises(LookupError): + msg.header('foo') + + def test_freeze(self, msg): + with msg.frozen(): + msg.tags.add('foo') + msg.tags.add('bar') + msg.tags.discard('foo') + assert 'foo' not in msg.tags + assert 'bar' in msg.tags + + def test_freeze_err(self, msg): + msg.tags.add('foo') + try: + with msg.frozen(): + msg.tags.clear() + raise Exception('oops') + except Exception: + assert 'foo' in msg.tags + else: + pytest.fail('Context manager did not raise') + + def test_replies_type(self, msg): + assert isinstance(msg.replies(), collections.abc.Iterator) + + def test_replies(self, msg): + with pytest.raises(StopIteration): + next(msg.replies()) + + +class TestProperties: + + @pytest.fixture + def props(self, maildir): + msgid, path = maildir.deliver() + with notdb.Database.create(maildir.path) as db: + msg, dup = db.add(path, sync_flags=False) + yield msg.properties + + def test_type(self, props): + assert isinstance(props, collections.abc.MutableMapping) + + def test_add_single(self, props): + props['foo'] = 'bar' + assert props['foo'] == 'bar' + props.add('bar', 'baz') + assert props['bar'] == 'baz' + + def test_add_dup(self, props): + props.add('foo', 'bar') + props.add('foo', 'baz') + assert props['foo'] == 'bar' + assert (set(props.getall('foo', exact=True)) + == {('foo', 'bar'), ('foo', 'baz')}) + + def test_len(self, props): + props.add('foo', 'a') + props.add('foo', 'b') + props.add('bar', 'a') + assert len(props) == 3 + assert len(props.keys()) == 2 + assert len(props.values()) == 2 + assert len(props.items()) == 3 + + def test_del(self, props): + props.add('foo', 'a') + props.add('foo', 'b') + del props['foo'] + with pytest.raises(KeyError): + props['foo'] + + def test_remove(self, props): + props.add('foo', 'a') + props.add('foo', 'b') + props.remove('foo', 'a') + assert props['foo'] == 'b' + + def test_view_abcs(self, props): + assert isinstance(props.keys(), collections.abc.KeysView) + assert isinstance(props.values(), collections.abc.ValuesView) + assert isinstance(props.items(), collections.abc.ItemsView) + + def test_pop(self, props): + props.add('foo', 'a') + props.add('foo', 'b') + val = props.pop('foo') + assert val == 'a' + + def test_pop_default(self, props): + with pytest.raises(KeyError): + props.pop('foo') + assert props.pop('foo', 'default') == 'default' + + def test_popitem(self, props): + props.add('foo', 'a') + assert props.popitem() == ('foo', 'a') + with pytest.raises(KeyError): + props.popitem() + + def test_clear(self, props): + props.add('foo', 'a') + props.clear() + assert len(props) == 0 + + def test_getall(self, props): + props.add('foo', 'a') + assert set(props.getall('foo')) == {('foo', 'a')} + + def test_getall_prefix(self, props): + props.add('foo', 'a') + props.add('foobar', 'b') + assert set(props.getall('foo')) == {('foo', 'a'), ('foobar', 'b')} + + def test_getall_exact(self, props): + props.add('foo', 'a') + props.add('foobar', 'b') + assert set(props.getall('foo', exact=True)) == {('foo', 'a')} diff --git a/bindings/python-cffi/tests/test_tags.py b/bindings/python-cffi/tests/test_tags.py new file mode 100644 index 00000000..0cb42d89 --- /dev/null +++ b/bindings/python-cffi/tests/test_tags.py @@ -0,0 +1,177 @@ +"""Tests for the behaviour of immutable and mutable tagsets. + +This module tests the Pythonic behaviour of the sets. +""" + +import collections +import subprocess +import textwrap + +import pytest + +from notdb import _database as database +from notdb import _tags as tags + + +class TestImmutable: + + @pytest.fixture + def tagset(self, maildir, notmuch): + """An non-empty immutable tagset. + + This will have the default new mail tags: inbox, unread. + """ + maildir.deliver() + notmuch('new') + with database.Database(maildir.path) as db: + yield db.tags + + def test_type(self, tagset): + assert isinstance(tagset, tags.ImmutableTagSet) + assert isinstance(tagset, collections.abc.Set) + + def test_hash(self, tagset, maildir, notmuch): + h0 = hash(tagset) + notmuch('tag', '+foo', '*') + with database.Database(maildir.path) as db: + h1 = hash(db.tags) + assert h0 != h1 + + def test_eq(self, tagset): + assert tagset == tagset + + def test_neq(self, tagset, maildir, notmuch): + notmuch('tag', '+foo', '*') + with database.Database(maildir.path) as db: + assert tagset != db.tags + + def test_contains(self, tagset): + print(tuple(tagset)) + assert 'unread' in tagset + assert 'foo' not in tagset + + def test_iter(self, tagset): + expected = sorted(['unread', 'inbox']) + found = [] + for tag in tagset: + assert isinstance(tag, str) + found.append(tag) + assert expected == sorted(found) + + def test_special_iter(self, tagset): + expected = sorted([b'unread', b'inbox']) + found = [] + for tag in tagset.iter(): + assert isinstance(tag, bytes) + found.append(tag) + assert expected == sorted(found) + + def test_special_iter_codec(self, tagset): + for tag in tagset.iter(encoding='ascii', errors='surrogateescape'): + assert isinstance(tag, str) + + def test_len(self, tagset): + assert len(tagset) == 2 + + def test_and(self, tagset): + common = tagset & {'unread'} + assert isinstance(common, set) + assert isinstance(common, collections.abc.Set) + assert common == {'unread'} + + def test_or(self, tagset): + res = tagset | {'foo'} + assert isinstance(res, set) + assert isinstance(res, collections.abc.Set) + assert res == {'unread', 'inbox', 'foo'} + + def test_sub(self, tagset): + res = tagset - {'unread'} + assert isinstance(res, set) + assert isinstance(res, collections.abc.Set) + assert res == {'inbox'} + + def test_rsub(self, tagset): + res = {'foo', 'unread'} - tagset + assert isinstance(res, set) + assert isinstance(res, collections.abc.Set) + assert res == {'foo'} + + def test_xor(self, tagset): + res = tagset ^ {'unread', 'foo'} + assert isinstance(res, set) + assert isinstance(res, collections.abc.Set) + assert res == {'inbox', 'foo'} + + def test_rxor(self, tagset): + res = {'unread', 'foo'} ^ tagset + assert isinstance(res, set) + assert isinstance(res, collections.abc.Set) + assert res == {'inbox', 'foo'} + + +class TestMutableTagset: + + @pytest.fixture + def tagset(self, maildir, notmuch): + """An non-empty mutable tagset. + + This will have the default new mail tags: inbox, unread. + """ + _, pathname = maildir.deliver() + notmuch('new') + with database.Database(maildir.path, + mode=database.Mode.READ_WRITE) as db: + msg = db.get(pathname) + yield msg.tags + + def test_type(self, tagset): + assert isinstance(tagset, collections.abc.MutableSet) + assert isinstance(tagset, tags.MutableTagSet) + + def test_hash(self, tagset): + assert not isinstance(tagset, collections.abc.Hashable) + with pytest.raises(TypeError): + hash(tagset) + + def test_add(self, tagset): + assert 'foo' not in tagset + tagset.add('foo') + assert 'foo' in tagset + + def test_discard(self, tagset): + assert 'inbox' in tagset + tagset.discard('inbox') + assert 'inbox' not in tagset + + def test_discard_not_present(self, tagset): + assert 'foo' not in tagset + tagset.discard('foo') + + def test_clear(self, tagset): + assert len(tagset) > 0 + tagset.clear() + assert len(tagset) == 0 + + def test_from_maildir_flags(self, maildir, notmuch): + _, pathname = maildir.deliver(flagged=True) + notmuch('new') + with database.Database(maildir.path, + mode=database.Mode.READ_WRITE) as db: + msg = db.get(pathname) + msg.tags.discard('flagged') + msg.tags.from_maildir_flags() + assert 'flagged' in msg.tags + + def test_to_maildir_flags(self, maildir, notmuch): + _, pathname = maildir.deliver(flagged=True) + notmuch('new') + with database.Database(maildir.path, + mode=database.Mode.READ_WRITE) as db: + msg = db.get(pathname) + flags = msg.path.name.split(',')[-1] + assert 'F' in flags + msg.tags.discard('flagged') + msg.tags.to_maildir_flags() + flags = msg.path.name.split(',')[-1] + assert 'F' not in flags diff --git a/bindings/python-cffi/tests/test_thread.py b/bindings/python-cffi/tests/test_thread.py new file mode 100644 index 00000000..366bd8a5 --- /dev/null +++ b/bindings/python-cffi/tests/test_thread.py @@ -0,0 +1,102 @@ +import collections.abc +import time + +import pytest + +import notdb + + +@pytest.fixture +def thread(maildir, notmuch): + """Return a single thread with one matched message.""" + msgid, _ = maildir.deliver(body='foo') + maildir.deliver(body='bar', + headers=[('In-Reply-To', '<{}>'.format(msgid))]) + notmuch('new') + with notdb.Database(maildir.path) as db: + yield next(db.threads('foo')) + + +def test_type(thread): + assert isinstance(thread, notdb.Thread) + assert isinstance(thread, collections.abc.Iterable) + + +def test_threadid(thread): + assert isinstance(thread.threadid, notdb.BinString) + assert thread.threadid + + +def test_len(thread): + assert len(thread) == 2 + + +def test_toplevel_type(thread): + assert isinstance(thread.toplevel(), collections.abc.Iterator) + + +def test_toplevel(thread): + msgs = thread.toplevel() + assert isinstance(next(msgs), notdb.Message) + with pytest.raises(StopIteration): + next(msgs) + + +def test_toplevel_reply(thread): + msg = next(thread.toplevel()) + assert isinstance(next(msg.replies()), notdb.Message) + + +def test_iter(thread): + msgs = list(iter(thread)) + assert len(msgs) == len(thread) + for msg in msgs: + assert isinstance(msg, notdb.Message) + + +def test_matched(thread): + assert thread.matched == 1 + + +def test_authors_type(thread): + assert isinstance(thread.authors, notdb.BinString) + + +def test_authors(thread): + assert thread.authors == 'src@example.com' + + +def test_subject(thread): + assert thread.subject == 'Test mail' + + +def test_first(thread): + # XXX Someone seems to treat things as local time instead of + # UTC or the other way around. + now = int(time.time()) + assert abs(now - thread.first) < 3600*24 + + +def test_last(thread): + # XXX Someone seems to treat things as local time instead of + # UTC or the other way around. + now = int(time.time()) + assert abs(now - thread.last) < 3600*24 + + +def test_first_last(thread): + # Sadly we only have second resolution so these will always be the + # same time in our tests. + assert thread.first <= thread.last + + +def test_tags_type(thread): + assert isinstance(thread.tags, notdb.ImmutableTagSet) + + +def test_tags_cache(thread): + assert thread.tags is thread.tags + + +def test_tags(thread): + assert 'inbox' in thread.tags diff --git a/bindings/python-cffi/tox.ini b/bindings/python-cffi/tox.ini new file mode 100644 index 00000000..d6b87987 --- /dev/null +++ b/bindings/python-cffi/tox.ini @@ -0,0 +1,16 @@ +[pytest] +minversion = 3.0 +addopts = -ra --cov=notdb --cov=tests + +[tox] +envlist = py35,py36,py37,pypy35,pypy36 + +[testenv] +deps = + cffi + pytest + pytest-cov +commands = pytest --cov={envsitepackagesdir}/notdb {posargs} + +[testenv:pypy35] +basepython = pypy3.5 From e8cb7c7f60c86cb70df904c624e3f636cb6a0041 Mon Sep 17 00:00:00 2001 From: David Bremner Date: Sun, 3 Nov 2019 09:10:29 -0400 Subject: [PATCH 045/427] bindings/python-cffi: preserve environment for tests We'll need this e.g. to pass PATH to the pytest tests Based on the suggested approach in id:87d0eljggj.fsf@powell.devork.be --- bindings/python-cffi/tests/conftest.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/bindings/python-cffi/tests/conftest.py b/bindings/python-cffi/tests/conftest.py index 1b7bbc35..aa940947 100644 --- a/bindings/python-cffi/tests/conftest.py +++ b/bindings/python-cffi/tests/conftest.py @@ -5,6 +5,7 @@ import socket import subprocess import textwrap import time +import os import pytest @@ -32,10 +33,11 @@ def notmuch(maildir): """ cfg_fname = maildir.path / 'notmuch-config' cmd = ['notmuch'] + list(args) - print('Invoking: {}'.format(' '.join(cmd))) + env = os.environ.copy() + env['NOTMUCH_CONFIG'] = str(cfg_fname) proc = subprocess.run(cmd, timeout=5, - env={'NOTMUCH_CONFIG': str(cfg_fname)}) + env=env) proc.check_returncode() return run From 3aaa8cfe941bd59730ae04df46da6aa3181f85e5 Mon Sep 17 00:00:00 2001 From: David Bremner Date: Sun, 3 Nov 2019 10:54:10 -0400 Subject: [PATCH 046/427] configure: check for python cffi and pytest modules This is needed to build the new python bindings, and run their tests. --- configure | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/configure b/configure index 3c148e12..b467643c 100755 --- a/configure +++ b/configure @@ -671,6 +671,15 @@ if [ $have_python -eq 0 ]; then errors=$((errors + 1)) fi +printf "Checking for python3 cffi... " +if "$python" -c 'import sys,cffi; assert sys.version_info >= (3,0)' >/dev/null 2>&1; then + printf "Yes.\n" + have_python3_cffi=1 +else + printf "No.\n" + have_python3_cffi=0 +fi + printf "Checking for valgrind development files... " if pkg-config --exists valgrind; then printf "Yes.\n" @@ -1223,6 +1232,12 @@ HAVE_GETLINE = ${have_getline} # building/testing ruby bindings. HAVE_RUBY_DEV = ${have_ruby_dev} +# Is the python cffi package available? +HAVE_PYTHON3_CFFI = ${have_python3_cffi} + +# Is the python pytest package available? +HAVE_PYTHON3_PYTEST = ${have_python3_pytest} + # Whether the strcasestr function is available (if not, then notmuch will # build its own version) HAVE_STRCASESTR = ${have_strcasestr} @@ -1376,6 +1391,12 @@ NOTMUCH_RUBY=${RUBY} # building/testing ruby bindings. NOTMUCH_HAVE_RUBY_DEV=${have_ruby_dev} +# Is the python cffi package available? +NOTMUCH_HAVE_PYTHON3_CFFI=${have_python3_cffi} + +# Is the python pytest package available? +NOTMUCH_HAVE_PYTHON3_PYTEST=${have_python3_pytest} + # Platform we are run on PLATFORM=${platform} EOF From 46e96156218e456df3fdd239e8c055220fba667a Mon Sep 17 00:00:00 2001 From: David Bremner Date: Sat, 19 Oct 2019 22:52:56 -0300 Subject: [PATCH 047/427] build: optionally build python-cffi bindings Put the build product (and tests) in a well known location so that we can find them e.g. from the tests. --- Makefile.local | 2 +- bindings/Makefile.local | 9 +++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/Makefile.local b/Makefile.local index 3c6dacbc..7c12612d 100644 --- a/Makefile.local +++ b/Makefile.local @@ -1,7 +1,7 @@ # -*- makefile -*- .PHONY: all -all: notmuch notmuch-shared build-man build-info ruby-bindings +all: notmuch notmuch-shared build-man build-info ruby-bindings python-cffi-bindings ifeq ($(MAKECMDGOALS),) ifeq ($(shell cat .first-build-message 2>/dev/null),) @NOTMUCH_FIRST_BUILD=1 $(MAKE) --no-print-directory all diff --git a/bindings/Makefile.local b/bindings/Makefile.local index 18f95835..19ddd6ea 100644 --- a/bindings/Makefile.local +++ b/bindings/Makefile.local @@ -13,6 +13,13 @@ ifeq ($(HAVE_RUBY_DEV),1) $(MAKE) -C $(dir)/ruby endif +python-cffi-bindings: lib/$(LINKER_NAME) +ifeq ($(HAVE_PYTHON3_CFFI),1) + cd $(dir)/python-cffi && \ + ${PYTHON} setup.py build --build-lib build/stage && \ + mkdir -p build/stage/tests && cp tests/*.py build/stage/tests +endif + CLEAN += $(patsubst %,$(dir)/ruby/%, \ .RUBYARCHDIR.time \ Makefile database.o directory.o filenames.o\ @@ -20,3 +27,5 @@ CLEAN += $(patsubst %,$(dir)/ruby/%, \ status.o tags.o thread.o threads.o) CLEAN += bindings/ruby/.vendorarchdir.time + +CLEAN += bindings/python-cffi/build From 85adc756c97646ae63e68fc2f80afd6b34b86fa9 Mon Sep 17 00:00:00 2001 From: David Bremner Date: Sat, 19 Oct 2019 23:10:24 -0300 Subject: [PATCH 048/427] tests: run python-cffi tests The entire python-cffi test suite is considered as a single test at the level of the notmuch test suite. This might or might not be ideal, but it gets them run. --- configure | 39 ++++++++++++++++++++++++++++++++------- test/T391-python-cffi.sh | 14 ++++++++++++++ 2 files changed, 46 insertions(+), 7 deletions(-) create mode 100755 test/T391-python-cffi.sh diff --git a/configure b/configure index b467643c..79ef34b3 100755 --- a/configure +++ b/configure @@ -671,13 +671,38 @@ if [ $have_python -eq 0 ]; then errors=$((errors + 1)) fi -printf "Checking for python3 cffi... " -if "$python" -c 'import sys,cffi; assert sys.version_info >= (3,0)' >/dev/null 2>&1; then - printf "Yes.\n" - have_python3_cffi=1 -else - printf "No.\n" - have_python3_cffi=0 +have_python3=0 +if [ $have_python -eq 1 ]; then + printf "Checking for python3..." + if "$python" -c 'import sys; assert sys.version_info >= (3,0)' > /dev/null 2>&1; then + printf "Yes.\n" + have_python3=1 + else + printf "No.\n" + fi +fi + +have_python3_cffi=0 +have_python3_pytest=0 +if [ $have_python3 -eq 1 ]; then + printf "Checking for python3 cffi... " + if "$python" -c 'import cffi' >/dev/null 2>&1; then + printf "Yes.\n" + have_python3_cffi=1 + else + printf "No.\n" + fi + + printf "Checking for python3 pytest (>= 3.0)... " + conf=$(mktemp) + printf "[pytest]\nminversion=3.0\n" > $conf + if pytest-3 -c $conf --version >/dev/null 2>&1; then + printf "Yes.\n" + have_python3_pytest=1 + else + printf "No.\n" + fi + rm -f $conf fi printf "Checking for valgrind development files... " diff --git a/test/T391-python-cffi.sh b/test/T391-python-cffi.sh new file mode 100755 index 00000000..e4f80dc6 --- /dev/null +++ b/test/T391-python-cffi.sh @@ -0,0 +1,14 @@ +#!/usr/bin/env bash +test_description="python bindings (pytest)" +. $(dirname "$0")/test-lib.sh || exit 1 + +if [ $NOTMUCH_HAVE_PYTHON3_CFFI -eq 0 -o $NOTMUCH_HAVE_PYTHON3_PYTEST -eq 0 ]; then + test_done +fi + + +test_begin_subtest "python cffi tests" +pytest_dir=$NOTMUCH_SRCDIR/bindings/python-cffi/build/stage +printf "[pytest]\nminversion = 3.0\naddopts = -ra\n" > $pytest_dir/pytest.ini +test_expect_success "(cd $pytest_dir && ${NOTMUCH_PYTHON} -m pytest --log-file=$TMP_DIRECTORY/test.output)" +test_done From af8c069194df3b47c30d0f82e2c2acca873e54e3 Mon Sep 17 00:00:00 2001 From: David Bremner Date: Sun, 3 Nov 2019 14:19:26 -0400 Subject: [PATCH 049/427] travis: add python3-{cffi,pytest,setuptools}, libpython3-all-dev These are needed for building and testing the new python bindings. --- .travis.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.travis.yml b/.travis.yml index f9516bde..6c03f0fb 100644 --- a/.travis.yml +++ b/.travis.yml @@ -13,6 +13,10 @@ addons: - libgmime-3.0-dev - libtalloc-dev - python3-sphinx + - python3-cffi + - python3-pytest + - python3-setuptools + - libpython3-all-dev - gpgsm script: From a48ccb5ef802d8732faf8235898ba86094861148 Mon Sep 17 00:00:00 2001 From: David Bremner Date: Sun, 3 Nov 2019 22:09:45 -0400 Subject: [PATCH 050/427] switch travis to bionic This should solve the problem with pytest versions. Drop the notmuch PPA, as (hopefully) we don't need those packages in bionic --- .travis.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 6c03f0fb..9dc03619 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,12 +1,11 @@ language: c -dist: xenial +dist: bionic addons: apt: sources: - sourceline: 'ppa:xapian-backports/ppa' - - sourceline: 'ppa:notmuch/notmuch' packages: - dtach - libxapian-dev From fbb8e1f4301f0993cf0b477b8e0e40d2273e3523 Mon Sep 17 00:00:00 2001 From: David Bremner Date: Mon, 4 Nov 2019 06:39:50 -0400 Subject: [PATCH 051/427] debian: add python3 dependencies for the new python bindings These should generally match those in .travis.yml --- debian/control | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/debian/control b/debian/control index b1829582..85fb562d 100644 --- a/debian/control +++ b/debian/control @@ -17,7 +17,12 @@ Build-Depends: python3-all (>= 3.1.2-7~), dh-python, dh-elpa (>= 1.3), + python3-cffi, + python3-pytest, + python3-pytest-cov, + python3-setuptools, python3-sphinx, + libpython3-all-dev, ruby, ruby-dev (>>1:1.9.3~), emacs-nox | emacs-gtk | emacs-lucid | emacs25-nox | emacs25 (>=25~) | emacs25-lucid (>=25~) | From a950aa28449feef76246ad2b64224fd72e2e574c Mon Sep 17 00:00:00 2001 From: Floris Bruynooghe Date: Sun, 17 Nov 2019 17:41:34 +0100 Subject: [PATCH 052/427] Show which notmuch command and version is being used This add the notmuch version and absolute path of the binary used in the pytest header. This is nice when running the tests interactively as you get confirmation you're testing the version you thought you were testing. --- bindings/python-cffi/tests/conftest.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/bindings/python-cffi/tests/conftest.py b/bindings/python-cffi/tests/conftest.py index aa940947..674c7218 100644 --- a/bindings/python-cffi/tests/conftest.py +++ b/bindings/python-cffi/tests/conftest.py @@ -10,6 +10,13 @@ import os import pytest +def pytest_report_header(): + vers = subprocess.run(['notmuch', '--version'], stdout=subprocess.PIPE) + which = subprocess.run(['which', 'notmuch'], stdout=subprocess.PIPE) + return ['{} ({})'.format(vers.stdout.decode(errors='replace').strip(), + which.stdout.decode(errors='replace').strip())] + + @pytest.fixture(scope='function') def tmppath(tmpdir): """The tmpdir fixture wrapped in pathlib.Path.""" From e2df30f7a98f91543d0b3561dbb366eb4b3d812c Mon Sep 17 00:00:00 2001 From: Floris Bruynooghe Date: Sun, 17 Nov 2019 17:41:35 +0100 Subject: [PATCH 053/427] Rename package to notmuch2 This is based on a previous discussion on the list where this was more or less seen as the least-bad option. --- .../{notdb => notmuch2}/__init__.py | 14 +++++++------- .../python-cffi/{notdb => notmuch2}/_base.py | 4 ++-- .../python-cffi/{notdb => notmuch2}/_build.py | 4 ++-- .../{notdb => notmuch2}/_database.py | 12 ++++++------ .../python-cffi/{notdb => notmuch2}/_errors.py | 2 +- .../{notdb => notmuch2}/_message.py | 8 ++++---- .../python-cffi/{notdb => notmuch2}/_query.py | 10 +++++----- .../python-cffi/{notdb => notmuch2}/_tags.py | 6 +++--- .../python-cffi/{notdb => notmuch2}/_thread.py | 10 +++++----- bindings/python-cffi/setup.py | 4 ++-- bindings/python-cffi/tests/test_base.py | 4 ++-- bindings/python-cffi/tests/test_database.py | 14 +++++++------- bindings/python-cffi/tests/test_message.py | 14 +++++++------- bindings/python-cffi/tests/test_tags.py | 4 ++-- bindings/python-cffi/tests/test_thread.py | 18 +++++++++--------- bindings/python-cffi/tox.ini | 4 ++-- 16 files changed, 66 insertions(+), 66 deletions(-) rename bindings/python-cffi/{notdb => notmuch2}/__init__.py (90%) rename bindings/python-cffi/{notdb => notmuch2}/_base.py (99%) rename bindings/python-cffi/{notdb => notmuch2}/_build.py (99%) rename bindings/python-cffi/{notdb => notmuch2}/_database.py (99%) rename bindings/python-cffi/{notdb => notmuch2}/_errors.py (99%) rename bindings/python-cffi/{notdb => notmuch2}/_message.py (99%) rename bindings/python-cffi/{notdb => notmuch2}/_query.py (93%) rename bindings/python-cffi/{notdb => notmuch2}/_tags.py (99%) rename bindings/python-cffi/{notdb => notmuch2}/_thread.py (97%) diff --git a/bindings/python-cffi/notdb/__init__.py b/bindings/python-cffi/notmuch2/__init__.py similarity index 90% rename from bindings/python-cffi/notdb/__init__.py rename to bindings/python-cffi/notmuch2/__init__.py index 67051df5..4d76ec15 100644 --- a/bindings/python-cffi/notdb/__init__.py +++ b/bindings/python-cffi/notmuch2/__init__.py @@ -42,13 +42,13 @@ usually more and essentially O(n) rather than O(1) as you might usually expect from Python containers. """ -from notdb import _capi -from notdb._base import * -from notdb._database import * -from notdb._errors import * -from notdb._message import * -from notdb._tags import * -from notdb._thread import * +from notmuch2 import _capi +from notmuch2._base import * +from notmuch2._database import * +from notmuch2._errors import * +from notmuch2._message import * +from notmuch2._tags import * +from notmuch2._thread import * NOTMUCH_TAG_MAX = _capi.lib.NOTMUCH_TAG_MAX diff --git a/bindings/python-cffi/notdb/_base.py b/bindings/python-cffi/notmuch2/_base.py similarity index 99% rename from bindings/python-cffi/notdb/_base.py rename to bindings/python-cffi/notmuch2/_base.py index acb64413..31258149 100644 --- a/bindings/python-cffi/notdb/_base.py +++ b/bindings/python-cffi/notmuch2/_base.py @@ -1,8 +1,8 @@ import abc import collections.abc -from notdb import _capi as capi -from notdb import _errors as errors +from notmuch2 import _capi as capi +from notmuch2 import _errors as errors __all__ = ['NotmuchObject', 'BinString'] diff --git a/bindings/python-cffi/notdb/_build.py b/bindings/python-cffi/notmuch2/_build.py similarity index 99% rename from bindings/python-cffi/notdb/_build.py rename to bindings/python-cffi/notmuch2/_build.py index 6be7e5b1..3ba3e558 100644 --- a/bindings/python-cffi/notdb/_build.py +++ b/bindings/python-cffi/notmuch2/_build.py @@ -3,14 +3,14 @@ import cffi ffibuilder = cffi.FFI() ffibuilder.set_source( - 'notdb._capi', + 'notmuch2._capi', r""" #include #include #include #if LIBNOTMUCH_MAJOR_VERSION < 5 - #error libnotmuch version not supported by notdb + #error libnotmuch version not supported by notmuch2 python bindings #endif """, include_dirs=['../../lib'], diff --git a/bindings/python-cffi/notdb/_database.py b/bindings/python-cffi/notmuch2/_database.py similarity index 99% rename from bindings/python-cffi/notdb/_database.py rename to bindings/python-cffi/notmuch2/_database.py index d414082a..a15c4d03 100644 --- a/bindings/python-cffi/notdb/_database.py +++ b/bindings/python-cffi/notmuch2/_database.py @@ -6,12 +6,12 @@ import os import pathlib import weakref -import notdb._base as base -import notdb._capi as capi -import notdb._errors as errors -import notdb._message as message -import notdb._query as querymod -import notdb._tags as tags +import notmuch2._base as base +import notmuch2._capi as capi +import notmuch2._errors as errors +import notmuch2._message as message +import notmuch2._query as querymod +import notmuch2._tags as tags __all__ = ['Database', 'AtomicContext', 'DbRevision'] diff --git a/bindings/python-cffi/notdb/_errors.py b/bindings/python-cffi/notmuch2/_errors.py similarity index 99% rename from bindings/python-cffi/notdb/_errors.py rename to bindings/python-cffi/notmuch2/_errors.py index 924e722f..1c88763b 100644 --- a/bindings/python-cffi/notdb/_errors.py +++ b/bindings/python-cffi/notmuch2/_errors.py @@ -1,4 +1,4 @@ -from notdb import _capi as capi +from notmuch2 import _capi as capi class NotmuchError(Exception): diff --git a/bindings/python-cffi/notdb/_message.py b/bindings/python-cffi/notmuch2/_message.py similarity index 99% rename from bindings/python-cffi/notdb/_message.py rename to bindings/python-cffi/notmuch2/_message.py index 9b2b037f..bb561426 100644 --- a/bindings/python-cffi/notdb/_message.py +++ b/bindings/python-cffi/notmuch2/_message.py @@ -4,10 +4,10 @@ import os import pathlib import weakref -import notdb._base as base -import notdb._capi as capi -import notdb._errors as errors -import notdb._tags as tags +import notmuch2._base as base +import notmuch2._capi as capi +import notmuch2._errors as errors +import notmuch2._tags as tags __all__ = ['Message'] diff --git a/bindings/python-cffi/notdb/_query.py b/bindings/python-cffi/notmuch2/_query.py similarity index 93% rename from bindings/python-cffi/notdb/_query.py rename to bindings/python-cffi/notmuch2/_query.py index 613aaf12..1db6ec96 100644 --- a/bindings/python-cffi/notdb/_query.py +++ b/bindings/python-cffi/notmuch2/_query.py @@ -1,8 +1,8 @@ -from notdb import _base as base -from notdb import _capi as capi -from notdb import _errors as errors -from notdb import _message as message -from notdb import _thread as thread +from notmuch2 import _base as base +from notmuch2 import _capi as capi +from notmuch2 import _errors as errors +from notmuch2 import _message as message +from notmuch2 import _thread as thread __all__ = [] diff --git a/bindings/python-cffi/notdb/_tags.py b/bindings/python-cffi/notmuch2/_tags.py similarity index 99% rename from bindings/python-cffi/notdb/_tags.py rename to bindings/python-cffi/notmuch2/_tags.py index a25a2264..fe422a79 100644 --- a/bindings/python-cffi/notdb/_tags.py +++ b/bindings/python-cffi/notmuch2/_tags.py @@ -1,8 +1,8 @@ import collections.abc -import notdb._base as base -import notdb._capi as capi -import notdb._errors as errors +import notmuch2._base as base +import notmuch2._capi as capi +import notmuch2._errors as errors __all__ = ['ImmutableTagSet', 'MutableTagSet', 'TagsIter'] diff --git a/bindings/python-cffi/notdb/_thread.py b/bindings/python-cffi/notmuch2/_thread.py similarity index 97% rename from bindings/python-cffi/notdb/_thread.py rename to bindings/python-cffi/notmuch2/_thread.py index e1ef6b07..a754749f 100644 --- a/bindings/python-cffi/notdb/_thread.py +++ b/bindings/python-cffi/notmuch2/_thread.py @@ -1,11 +1,11 @@ import collections.abc import weakref -from notdb import _base as base -from notdb import _capi as capi -from notdb import _errors as errors -from notdb import _message as message -from notdb import _tags as tags +from notmuch2 import _base as base +from notmuch2 import _capi as capi +from notmuch2 import _errors as errors +from notmuch2 import _message as message +from notmuch2 import _tags as tags __all__ = ['Thread'] diff --git a/bindings/python-cffi/setup.py b/bindings/python-cffi/setup.py index 7baf63cf..37918e3d 100644 --- a/bindings/python-cffi/setup.py +++ b/bindings/python-cffi/setup.py @@ -2,7 +2,7 @@ import setuptools setuptools.setup( - name='notdb', + name='notmuch2', version='0.1', description='Pythonic bindings for the notmuch mail database using CFFI', author='Floris Bruynooghe', @@ -10,7 +10,7 @@ setuptools.setup( setup_requires=['cffi>=1.0.0'], install_requires=['cffi>=1.0.0'], packages=setuptools.find_packages(exclude=['tests']), - cffi_modules=['notdb/_build.py:ffibuilder'], + cffi_modules=['notmuch2/_build.py:ffibuilder'], classifiers=[ 'Development Status :: 3 - Alpha', 'Intended Audience :: Developers', diff --git a/bindings/python-cffi/tests/test_base.py b/bindings/python-cffi/tests/test_base.py index b6d3d62c..d3280a67 100644 --- a/bindings/python-cffi/tests/test_base.py +++ b/bindings/python-cffi/tests/test_base.py @@ -1,7 +1,7 @@ import pytest -from notdb import _base as base -from notdb import _errors as errors +from notmuch2 import _base as base +from notmuch2 import _errors as errors class TestNotmuchObject: diff --git a/bindings/python-cffi/tests/test_database.py b/bindings/python-cffi/tests/test_database.py index 02de0f41..e3a8344d 100644 --- a/bindings/python-cffi/tests/test_database.py +++ b/bindings/python-cffi/tests/test_database.py @@ -5,10 +5,10 @@ import pathlib import pytest -import notdb -import notdb._errors as errors -import notdb._database as dbmod -import notdb._message as message +import notmuch2 +import notmuch2._errors as errors +import notmuch2._database as dbmod +import notmuch2._message as message @pytest.fixture @@ -279,7 +279,7 @@ class TestQuery: @pytest.fixture def db(self, maildir, notmuch): - """Return a read-only notdb.Database. + """Return a read-only notmuch2.Database. The database will have 3 messages, 2 threads. """ @@ -306,7 +306,7 @@ class TestQuery: def test_message_match(self, db): msgs = db.messages('*') msg = next(msgs) - assert isinstance(msg, notdb.Message) + assert isinstance(msg, notmuch2.Message) def test_count_threads(self, db): assert db.count_threads('*') == 2 @@ -323,4 +323,4 @@ class TestQuery: def test_threads_match(self, db): threads = db.threads('*') thread = next(threads) - assert isinstance(thread, notdb.Thread) + assert isinstance(thread, notmuch2.Thread) diff --git a/bindings/python-cffi/tests/test_message.py b/bindings/python-cffi/tests/test_message.py index 56d06f34..532bf921 100644 --- a/bindings/python-cffi/tests/test_message.py +++ b/bindings/python-cffi/tests/test_message.py @@ -4,7 +4,7 @@ import pathlib import pytest -import notdb +import notmuch2 class TestMessage: @@ -17,7 +17,7 @@ class TestMessage: @pytest.fixture def db(self, maildir): - with notdb.Database.create(maildir.path) as db: + with notmuch2.Database.create(maildir.path) as db: yield db @pytest.fixture @@ -26,8 +26,8 @@ class TestMessage: yield msg def test_type(self, msg): - assert isinstance(msg, notdb.NotmuchObject) - assert isinstance(msg, notdb.Message) + assert isinstance(msg, notmuch2.NotmuchObject) + assert isinstance(msg, notmuch2.Message) def test_alive(self, msg): assert msg.alive @@ -41,7 +41,7 @@ class TestMessage: def test_messageid_type(self, msg): assert isinstance(msg.messageid, str) - assert isinstance(msg.messageid, notdb.BinString) + assert isinstance(msg.messageid, notmuch2.BinString) assert isinstance(bytes(msg.messageid), bytes) def test_messageid(self, msg, maildir_msg): @@ -53,7 +53,7 @@ class TestMessage: def test_threadid_type(self, msg): assert isinstance(msg.threadid, str) - assert isinstance(msg.threadid, notdb.BinString) + assert isinstance(msg.threadid, notmuch2.BinString) assert isinstance(bytes(msg.threadid), bytes) def test_path_type(self, msg): @@ -142,7 +142,7 @@ class TestProperties: @pytest.fixture def props(self, maildir): msgid, path = maildir.deliver() - with notdb.Database.create(maildir.path) as db: + with notmuch2.Database.create(maildir.path) as db: msg, dup = db.add(path, sync_flags=False) yield msg.properties diff --git a/bindings/python-cffi/tests/test_tags.py b/bindings/python-cffi/tests/test_tags.py index 0cb42d89..f12fa1e6 100644 --- a/bindings/python-cffi/tests/test_tags.py +++ b/bindings/python-cffi/tests/test_tags.py @@ -9,8 +9,8 @@ import textwrap import pytest -from notdb import _database as database -from notdb import _tags as tags +from notmuch2 import _database as database +from notmuch2 import _tags as tags class TestImmutable: diff --git a/bindings/python-cffi/tests/test_thread.py b/bindings/python-cffi/tests/test_thread.py index 366bd8a5..1f44b35d 100644 --- a/bindings/python-cffi/tests/test_thread.py +++ b/bindings/python-cffi/tests/test_thread.py @@ -3,7 +3,7 @@ import time import pytest -import notdb +import notmuch2 @pytest.fixture @@ -13,17 +13,17 @@ def thread(maildir, notmuch): maildir.deliver(body='bar', headers=[('In-Reply-To', '<{}>'.format(msgid))]) notmuch('new') - with notdb.Database(maildir.path) as db: + with notmuch2.Database(maildir.path) as db: yield next(db.threads('foo')) def test_type(thread): - assert isinstance(thread, notdb.Thread) + assert isinstance(thread, notmuch2.Thread) assert isinstance(thread, collections.abc.Iterable) def test_threadid(thread): - assert isinstance(thread.threadid, notdb.BinString) + assert isinstance(thread.threadid, notmuch2.BinString) assert thread.threadid @@ -37,21 +37,21 @@ def test_toplevel_type(thread): def test_toplevel(thread): msgs = thread.toplevel() - assert isinstance(next(msgs), notdb.Message) + assert isinstance(next(msgs), notmuch2.Message) with pytest.raises(StopIteration): next(msgs) def test_toplevel_reply(thread): msg = next(thread.toplevel()) - assert isinstance(next(msg.replies()), notdb.Message) + assert isinstance(next(msg.replies()), notmuch2.Message) def test_iter(thread): msgs = list(iter(thread)) assert len(msgs) == len(thread) for msg in msgs: - assert isinstance(msg, notdb.Message) + assert isinstance(msg, notmuch2.Message) def test_matched(thread): @@ -59,7 +59,7 @@ def test_matched(thread): def test_authors_type(thread): - assert isinstance(thread.authors, notdb.BinString) + assert isinstance(thread.authors, notmuch2.BinString) def test_authors(thread): @@ -91,7 +91,7 @@ def test_first_last(thread): def test_tags_type(thread): - assert isinstance(thread.tags, notdb.ImmutableTagSet) + assert isinstance(thread.tags, notmuch2.ImmutableTagSet) def test_tags_cache(thread): diff --git a/bindings/python-cffi/tox.ini b/bindings/python-cffi/tox.ini index d6b87987..34148a11 100644 --- a/bindings/python-cffi/tox.ini +++ b/bindings/python-cffi/tox.ini @@ -1,6 +1,6 @@ [pytest] minversion = 3.0 -addopts = -ra --cov=notdb --cov=tests +addopts = -ra --cov=notmuch2 --cov=tests [tox] envlist = py35,py36,py37,pypy35,pypy36 @@ -10,7 +10,7 @@ deps = cffi pytest pytest-cov -commands = pytest --cov={envsitepackagesdir}/notdb {posargs} +commands = pytest --cov={envsitepackagesdir}/notmuch2 {posargs} [testenv:pypy35] basepython = pypy3.5 From 1e072204cd1963fbf684cdc9c92092efb2d44924 Mon Sep 17 00:00:00 2001 From: Floris Bruynooghe Date: Sun, 17 Nov 2019 20:24:46 +0100 Subject: [PATCH 054/427] Move from _add_message to _index_file API This moves away from the deprecated notmuch_database_add_message API and instead uses the notmuch_database_index_file API. This means instroducing a class to manage the index options and bumping the library version requirement to 5.1. --- bindings/python-cffi/notmuch2/_build.py | 26 ++++++- bindings/python-cffi/notmuch2/_database.py | 88 +++++++++++++++++++++- 2 files changed, 107 insertions(+), 7 deletions(-) diff --git a/bindings/python-cffi/notmuch2/_build.py b/bindings/python-cffi/notmuch2/_build.py index 3ba3e558..5e1fcac1 100644 --- a/bindings/python-cffi/notmuch2/_build.py +++ b/bindings/python-cffi/notmuch2/_build.py @@ -12,6 +12,9 @@ ffibuilder.set_source( #if LIBNOTMUCH_MAJOR_VERSION < 5 #error libnotmuch version not supported by notmuch2 python bindings #endif + #if LIBNOTMUCH_MINOR_VERSION < 1 + #ERROR libnotmuch version < 5.1 not supported + #endif """, include_dirs=['../../lib'], library_dirs=['../../lib'], @@ -68,6 +71,12 @@ ffibuilder.cdef( NOTMUCH_EXCLUDE_FALSE, NOTMUCH_EXCLUDE_ALL } notmuch_exclude_t; + typedef enum { + NOTMUCH_DECRYPT_FALSE, + NOTMUCH_DECRYPT_TRUE, + NOTMUCH_DECRYPT_AUTO, + NOTMUCH_DECRYPT_NOSTASH, + } notmuch_decryption_policy_t; // These are fully opaque types for us, we only ever use pointers. typedef struct _notmuch_database notmuch_database_t; @@ -81,6 +90,7 @@ ffibuilder.cdef( typedef struct _notmuch_directory notmuch_directory_t; typedef struct _notmuch_filenames notmuch_filenames_t; typedef struct _notmuch_config_list notmuch_config_list_t; + typedef struct _notmuch_indexopts notmuch_indexopts_t; const char * notmuch_status_to_string (notmuch_status_t status); @@ -118,9 +128,10 @@ ffibuilder.cdef( notmuch_database_get_revision (notmuch_database_t *notmuch, const char **uuid); notmuch_status_t - notmuch_database_add_message (notmuch_database_t *database, - const char *filename, - notmuch_message_t **message); + notmuch_database_index_file (notmuch_database_t *database, + const char *filename, + notmuch_indexopts_t *indexopts, + notmuch_message_t **message); notmuch_status_t notmuch_database_remove_message (notmuch_database_t *database, const char *filename); @@ -294,6 +305,15 @@ ffibuilder.cdef( notmuch_filenames_move_to_next (notmuch_filenames_t *filenames); void notmuch_filenames_destroy (notmuch_filenames_t *filenames); + notmuch_indexopts_t * + notmuch_database_get_default_indexopts (notmuch_database_t *db); + notmuch_status_t + notmuch_indexopts_set_decrypt_policy (notmuch_indexopts_t *indexopts, + notmuch_decryption_policy_t decrypt_policy); + notmuch_decryption_policy_t + notmuch_indexopts_get_decrypt_policy (const notmuch_indexopts_t *indexopts); + void + notmuch_indexopts_destroy (notmuch_indexopts_t *options); """ ) diff --git a/bindings/python-cffi/notmuch2/_database.py b/bindings/python-cffi/notmuch2/_database.py index a15c4d03..a1c624a7 100644 --- a/bindings/python-cffi/notmuch2/_database.py +++ b/bindings/python-cffi/notmuch2/_database.py @@ -45,6 +45,13 @@ class QueryExclude(enum.Enum): ALL = capi.lib.NOTMUCH_EXCLUDE_ALL +class DecryptionPolicy(enum.Enum): + FALSE = capi.lib.NOTMUCH_DECRYPT_FALSE + TRUE = capi.lib.NOTMUCH_DECRYPT_TRUE + AUTO = capi.lib.NOTMUCH_DECRYPT_AUTO + NOSTASH = capi.lib.NOTMUCH_DECRYPT_NOSTASH + + class Database(base.NotmuchObject): """Toplevel access to notmuch. @@ -332,7 +339,17 @@ class Database(base.NotmuchObject): def get_directory(self, path): raise NotImplementedError - def add(self, filename, *, sync_flags=False): + def default_indexopts(self): + """Returns default index options for the database. + + :raises ObjectDestoryedError: if used after destroyed. + + :returns: :class:`IndexOptions`. + """ + opts = capi.lib.notmuch_database_get_default_indexopts(self._db_p) + return IndexOptions(self, opts) + + def add(self, filename, *, sync_flags=False, indexopts=None): """Add a message to the database. Add a new message to the notmuch database. The message is @@ -346,6 +363,11 @@ class Database(base.NotmuchObject): :param sync_flags: Whether to sync the known maildir flags to notmuch tags. See :meth:`Message.flags_to_tags` for details. + :type sync_flags: bool + :param indexopts: The indexing options, see + :meth:`default_indexopts`. Leave as `None` to use the + default options configured in the database. + :type indexopts: :class:`IndexOptions` or `None` :returns: A tuple where the first item is the newly inserted messages as a :class:`Message` instance, and the second @@ -370,9 +392,9 @@ class Database(base.NotmuchObject): if not hasattr(os, 'PathLike') and isinstance(filename, pathlib.Path): filename = bytes(filename) msg_pp = capi.ffi.new('notmuch_message_t **') - ret = capi.lib.notmuch_database_add_message(self._db_p, - os.fsencode(filename), - msg_pp) + opts_p = indexopts._opts_p if indexopts else capi.ffi.NULL + ret = capi.lib.notmuch_database_index_file( + self._db_p, os.fsencode(filename), opts_p, msg_pp) ok = [capi.lib.NOTMUCH_STATUS_SUCCESS, capi.lib.NOTMUCH_STATUS_DUPLICATE_MESSAGE_ID] if ret not in ok: @@ -703,3 +725,61 @@ class DbRevision: def __repr__(self): return 'DbRevision(rev={self.rev}, uuid={self.uuid})'.format(self=self) + + +class IndexOptions(base.NotmuchObject): + """Indexing options. + + This represents the indexing options which can be used to index a + message. See :meth:`Database.default_indexopts` to create an + instance of this. It can be used e.g. when indexing a new message + using :meth:`Database.add`. + """ + _opts_p = base.MemoryPointer() + + def __init__(self, parent, opts_p): + self._parent = parent + self._opts_p = opts_p + + @property + def alive(self): + if not self._parent.alive: + return False + try: + self._opts_p + except errors.ObjectDestroyedError: + return False + else: + return True + + def _destroy(self): + if self.alive: + capi.lib.notmuch_indexopts_destroy(self._opts_p) + self._opts_p = None + + @property + def decrypt_policy(self): + """The decryption policy. + + This is an enum from the :class:`DecryptionPolicy`. See the + `index.decrypt` section in :man:`notmuch-config` for details + on the options. **Do not set this to + :attr:`DecryptionPolicy.TRUE`** without considering the + security of your index. + + You can change this policy by assigning a new + :class:`DecryptionPolicy` to this property. + + :raises ObjectDestoryedError: if used after destroyed. + + :returns: A :class:`DecryptionPolicy` enum instance. + """ + raw = capi.lib.notmuch_indexopts_get_decrypt_policy(self._opts_p) + return DecryptionPolicy(raw) + + @decrypt_policy.setter + def decrypt_policy(self, val): + ret = capi.lib.notmuch_indexopts_set_decrypt_policy( + self._opts_p, val.value) + if ret != capi.lib.NOTMUCH_STATUS_SUCCESS: + raise errors.NotmuchError(ret, msg) From 3185830e3afaa238d0c79220fcefea8800b8cf85 Mon Sep 17 00:00:00 2001 From: David Bremner Date: Mon, 25 Nov 2019 20:47:24 -0400 Subject: [PATCH 055/427] python-cffi: use shutil.which I was supposed to amend the original patch that added this function, but somehow I botched that. The original version runs, so make an extra commit for the tidying. --- bindings/python-cffi/tests/conftest.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/bindings/python-cffi/tests/conftest.py b/bindings/python-cffi/tests/conftest.py index 674c7218..e322cc64 100644 --- a/bindings/python-cffi/tests/conftest.py +++ b/bindings/python-cffi/tests/conftest.py @@ -1,6 +1,7 @@ import email.message import mailbox import pathlib +import shutil import socket import subprocess import textwrap @@ -11,10 +12,9 @@ import pytest def pytest_report_header(): + which = shutil.which('notmuch') vers = subprocess.run(['notmuch', '--version'], stdout=subprocess.PIPE) - which = subprocess.run(['which', 'notmuch'], stdout=subprocess.PIPE) - return ['{} ({})'.format(vers.stdout.decode(errors='replace').strip(), - which.stdout.decode(errors='replace').strip())] + return ['{} ({})'.format(vers.stdout.decode(errors='replace').strip(),which)] @pytest.fixture(scope='function') From 2044c7aad9cbe2e87c773169a622e549b588d208 Mon Sep 17 00:00:00 2001 From: Oliver Kiddle Date: Thu, 14 Nov 2019 23:10:50 +0100 Subject: [PATCH 056/427] configure: Install zsh completions where zsh will find them. Zsh searches in the $fpath array for completion functions. By default this includes $(prefix)/share/zsh/site-functions but not the existing value. The prefix for zsh and notmuch isn't guaranteed to be the same but it normally will be making this a better default for zsh_completion_dir. --- configure | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/configure b/configure index 79ef34b3..fad2655c 100755 --- a/configure +++ b/configure @@ -168,7 +168,7 @@ Fine tuning of some installation directories is available: --emacslispdir=DIR Emacs code [PREFIX/share/emacs/site-lisp] --emacsetcdir=DIR Emacs miscellaneous files [PREFIX/share/emacs/site-lisp] --bashcompletiondir=DIR Bash completions files [PREFIX/share/bash-completion/completions] - --zshcompletiondir=DIR Zsh completions files [PREFIX/share/zsh/functions/Completion/Unix] + --zshcompletiondir=DIR Zsh completions files [PREFIX/share/zsh/site-functions] Some features can be disabled (--with-feature=no is equivalent to --without-feature) : @@ -1240,7 +1240,7 @@ desktop_dir = \$(prefix)/share/applications bash_completion_dir = ${BASHCOMPLETIONDIR:=\$(prefix)/share/bash-completion/completions} # The directory to which zsh completions files should be installed -zsh_completion_dir = ${ZSHCOMLETIONDIR:=\$(prefix)/share/zsh/functions/Completion/Unix} +zsh_completion_dir = ${ZSHCOMLETIONDIR:=\$(prefix)/share/zsh/site-functions} # Whether the canonicalize_file_name function is available (if not, then notmuch will # build its own version) From 575f207b3d936ff7010ac60ab4172dd4a9ca2f41 Mon Sep 17 00:00:00 2001 From: Daniel Kahn Gillmor Date: Sun, 10 Nov 2019 12:37:43 -0500 Subject: [PATCH 057/427] Standards-Version: bump to 4.4.1 (no changes needed) Signed-off-by: Daniel Kahn Gillmor --- debian/control | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/debian/control b/debian/control index 85fb562d..4901dada 100644 --- a/debian/control +++ b/debian/control @@ -33,7 +33,7 @@ Build-Depends: gnupg , bash-completion (>=1.9.0~), texinfo -Standards-Version: 4.3.0 +Standards-Version: 4.4.1 Homepage: https://notmuchmail.org/ Vcs-Git: https://git.notmuchmail.org/git/notmuch -b release Vcs-Browser: https://git.notmuchmail.org/git/notmuch From 7ffff7927c579a8592c33a3e496e23a86e68b1f5 Mon Sep 17 00:00:00 2001 From: Daniel Kahn Gillmor Date: Sun, 10 Nov 2019 12:37:44 -0500 Subject: [PATCH 058/427] Rules-Requires-Root: no (we do nothing as root during package build) Signed-off-by: Daniel Kahn Gillmor --- debian/control | 1 + 1 file changed, 1 insertion(+) diff --git a/debian/control b/debian/control index 4901dada..9e5533d1 100644 --- a/debian/control +++ b/debian/control @@ -37,6 +37,7 @@ Standards-Version: 4.4.1 Homepage: https://notmuchmail.org/ Vcs-Git: https://git.notmuchmail.org/git/notmuch -b release Vcs-Browser: https://git.notmuchmail.org/git/notmuch +Rules-Requires-Root: no Package: notmuch Architecture: any From 20bd613261e3ae24bf5c222d9ca19d40e8940a89 Mon Sep 17 00:00:00 2001 From: Daniel Kahn Gillmor Date: Sun, 10 Nov 2019 12:37:47 -0500 Subject: [PATCH 059/427] debian/copyright: use secure git URL Signed-off-by: Daniel Kahn Gillmor --- debian/copyright | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/debian/copyright b/debian/copyright index 0931d9b9..a88ce1dc 100644 --- a/debian/copyright +++ b/debian/copyright @@ -1,6 +1,6 @@ Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ Upstream-Name: notmuch -Source: git://notmuchmail.org/git/notmuch +Source: https://git.notmuchmail.org/git/notmuch Upstream-Contact: Notmuch Mailing List Files: * From fe66c3048a09b73b2b984e2078b5627d938593dd Mon Sep 17 00:00:00 2001 From: Daniel Kahn Gillmor Date: Sun, 10 Nov 2019 12:37:48 -0500 Subject: [PATCH 060/427] Add debian/upstream/metadata (for DEP-12) Signed-off-by: Daniel Kahn Gillmor --- debian/upstream/metadata | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 debian/upstream/metadata diff --git a/debian/upstream/metadata b/debian/upstream/metadata new file mode 100644 index 00000000..8f266aa8 --- /dev/null +++ b/debian/upstream/metadata @@ -0,0 +1,6 @@ +Bug-Database: https://nmbug.notmuchmail.org/status/ +Bug-Submit: mailto:notmuch@notmuchmail.org +FAQ: https://notmuchmail.org/faq/ +Repository: https://git.notmuchmail.org/git/notmuch +Repository-Browse: https://git.notmuchmail.org/git/notmuch +Screenshots: https://notmuchmail.org/screenshots/ From e712b91f46c076b5661835033d1d3db4293f8eb2 Mon Sep 17 00:00:00 2001 From: Daniel Kahn Gillmor Date: Wed, 4 Dec 2019 02:07:49 -0500 Subject: [PATCH 061/427] wrap-and-sort -ast Signed-off-by: Daniel Kahn Gillmor --- debian/control | 111 ++++++++++++++++++++++++------------ debian/notmuch-mutt.install | 2 +- debian/notmuch-vim.dirs | 4 +- debian/notmuch-vim.install | 4 +- debian/notmuch.install | 2 +- debian/notmuch.manpages | 28 ++++----- 6 files changed, 93 insertions(+), 58 deletions(-) diff --git a/debian/control b/debian/control index 9e5533d1..1c9427b2 100644 --- a/debian/control +++ b/debian/control @@ -4,35 +4,37 @@ Priority: optional Maintainer: Carl Worth Uploaders: Jameson Graef Rollins , - David Bremner -Build-Conflicts: ruby1.8, gdb-minimal, gdb [ia64 mips mips64el] + David Bremner , +Build-Conflicts: + gdb [ia64 mips mips64el], + gdb-minimal, + ruby1.8, Build-Depends: - dpkg-dev (>= 1.17.14), + bash-completion (>=1.9.0~), debhelper (>= 11~), - pkg-config, - libxapian-dev, - libgmime-3.0-dev (>= 3.0.3~), - libtalloc-dev, - libz-dev, - python3-all (>= 3.1.2-7~), - dh-python, dh-elpa (>= 1.3), + dh-python, + dpkg-dev (>= 1.17.14), + dtach (>= 0.8) , + emacs-nox | emacs-gtk | emacs-lucid | emacs25-nox | emacs25 (>=25~) | emacs25-lucid (>=25~) | emacs24-nox | emacs24 (>=24~) | emacs24-lucid (>=24~), + gdb [!ia64 !mips !mips64el !kfreebsd-any !alpha] , + gnupg , + gpgsm , + libgmime-3.0-dev (>= 3.0.3~), + libpython3-all-dev, + libtalloc-dev, + libxapian-dev, + libz-dev, + pkg-config, + python3-all (>= 3.1.2-7~), python3-cffi, python3-pytest, python3-pytest-cov, python3-setuptools, python3-sphinx, - libpython3-all-dev, - ruby, ruby-dev (>>1:1.9.3~), - emacs-nox | emacs-gtk | emacs-lucid | - emacs25-nox | emacs25 (>=25~) | emacs25-lucid (>=25~) | - emacs24-nox | emacs24 (>=24~) | emacs24-lucid (>=24~), - gdb [!ia64 !mips !mips64el !kfreebsd-any !alpha] , - dtach (>= 0.8) , - gpgsm , - gnupg , - bash-completion (>=1.9.0~), - texinfo + ruby, + ruby-dev (>>1:1.9.3~), + texinfo, Standards-Version: 4.4.1 Homepage: https://notmuchmail.org/ Vcs-Git: https://git.notmuchmail.org/git/notmuch -b release @@ -41,8 +43,14 @@ Rules-Requires-Root: no Package: notmuch Architecture: any -Depends: libnotmuch5 (= ${binary:Version}), ${shlibs:Depends}, ${misc:Depends} -Recommends: elpa-notmuch | notmuch-vim | notmuch-mutt | alot, gnupg-agent, gpgsm +Depends: + libnotmuch5 (= ${binary:Version}), + ${misc:Depends}, + ${shlibs:Depends}, +Recommends: + elpa-notmuch | notmuch-vim | notmuch-mutt | alot, + gnupg-agent, + gpgsm, Description: thread-based email index, search and tagging Notmuch is a system for indexing, searching, reading, and tagging large collections of email messages in maildir or mh format. It uses @@ -54,8 +62,11 @@ Description: thread-based email index, search and tagging Package: libnotmuch5 Section: libs Architecture: any -Depends: ${shlibs:Depends}, ${misc:Depends} -Pre-Depends: ${misc:Pre-Depends} +Depends: + ${misc:Depends}, + ${shlibs:Depends}, +Pre-Depends: + ${misc:Pre-Depends}, Description: thread-based email index, search and tagging (runtime) Notmuch is a system for indexing, searching, reading, and tagging large collections of email messages in maildir or mh format. It uses @@ -68,7 +79,9 @@ Description: thread-based email index, search and tagging (runtime) Package: libnotmuch-dev Section: libdevel Architecture: any -Depends: ${misc:Depends}, libnotmuch5 (= ${binary:Version}) +Depends: + libnotmuch5 (= ${binary:Version}), + ${misc:Depends}, Description: thread-based email index, search and tagging (development) Notmuch is a system for indexing, searching, reading, and tagging large collections of email messages in maildir or mh format. It uses @@ -81,7 +94,10 @@ Description: thread-based email index, search and tagging (development) Package: python3-notmuch Architecture: all Section: python -Depends: ${misc:Depends}, ${python3:Depends}, libnotmuch5 (>= ${source:Version}) +Depends: + libnotmuch5 (>= ${source:Version}), + ${misc:Depends}, + ${python3:Depends}, Description: Python 3 interface to the notmuch mail search and index library Notmuch is a system for indexing, searching, reading, and tagging large collections of email messages in maildir or mh format. It uses @@ -94,7 +110,9 @@ Description: Python 3 interface to the notmuch mail search and index library Package: ruby-notmuch Architecture: any Section: ruby -Depends: ${shlibs:Depends}, ${misc:Depends} +Depends: + ${misc:Depends}, + ${shlibs:Depends}, Description: Ruby interface to the notmuch mail search and index library Notmuch is a system for indexing, searching, reading, and tagging large collections of email messages in maildir or mh format. It uses @@ -107,13 +125,17 @@ Description: Ruby interface to the notmuch mail search and index library Package: notmuch-emacs Section: oldlibs Architecture: all -Depends: elpa-notmuch, ${misc:Depends} +Depends: + elpa-notmuch, + ${misc:Depends}, Description: thread-based email index, search and tagging (transitional package) This dummy package help ease transition to the new package elpa-notmuch Package: elpa-notmuch Architecture: all -Depends: ${misc:Depends}, ${elpa:Depends} +Depends: + ${elpa:Depends}, + ${misc:Depends}, Description: thread-based email index, search and tagging (emacs interface) Notmuch is a system for indexing, searching, reading, and tagging large collections of email messages in maildir or mh format. It uses @@ -125,10 +147,18 @@ Description: thread-based email index, search and tagging (emacs interface) Package: notmuch-vim Architecture: all -Breaks: notmuch (<<0.6~254~) -Replaces: notmuch (<<0.6~254~) -Depends: ${misc:Depends}, notmuch, vim-addon-manager, vim-ruby, ruby-notmuch -Recommends: ruby-mail +Breaks: + notmuch (<<0.6~254~), +Replaces: + notmuch (<<0.6~254~), +Depends: + notmuch, + ruby-notmuch, + vim-addon-manager, + vim-ruby, + ${misc:Depends}, +Recommends: + ruby-mail, Description: thread-based email index, search and tagging (vim interface) Notmuch is a system for indexing, searching, reading, and tagging large collections of email messages in maildir or mh format. It uses @@ -141,13 +171,18 @@ Description: thread-based email index, search and tagging (vim interface) Package: notmuch-mutt Architecture: all Depends: + libmail-box-perl, + libmailtools-perl, + libstring-shellquote-perl, + libterm-readline-gnu-perl, notmuch (>= 0.4), - libmail-box-perl, libmailtools-perl, - libstring-shellquote-perl, libterm-readline-gnu-perl, ${misc:Depends}, ${perl:Depends}, -Recommends: mutt -Enhances: notmuch, mutt +Recommends: + mutt, +Enhances: + mutt, + notmuch, Description: thread-based email index, search and tagging (Mutt interface) notmuch-mutt provides integration among the Mutt mail user agent and the Notmuch mail indexer. diff --git a/debian/notmuch-mutt.install b/debian/notmuch-mutt.install index 9b468bdb..8314f883 100644 --- a/debian/notmuch-mutt.install +++ b/debian/notmuch-mutt.install @@ -1,2 +1,2 @@ -usr/bin/notmuch-mutt etc/Muttrc.d/notmuch-mutt.rc +usr/bin/notmuch-mutt diff --git a/debian/notmuch-vim.dirs b/debian/notmuch-vim.dirs index c6373e42..2b531314 100644 --- a/debian/notmuch-vim.dirs +++ b/debian/notmuch-vim.dirs @@ -1,4 +1,4 @@ -usr/share/vim/registry -usr/share/vim/addons/plugin usr/share/vim/addons/doc +usr/share/vim/addons/plugin usr/share/vim/addons/syntax +usr/share/vim/registry diff --git a/debian/notmuch-vim.install b/debian/notmuch-vim.install index a1af708d..cf898738 100644 --- a/debian/notmuch-vim.install +++ b/debian/notmuch-vim.install @@ -1,4 +1,4 @@ -vim/notmuch.vim usr/share/vim/addons/plugin vim/notmuch.txt usr/share/vim/addons/doc -vim/syntax/notmuch-*.vim usr/share/vim/addons/syntax +vim/notmuch.vim usr/share/vim/addons/plugin vim/notmuch.yaml usr/share/vim/registry +vim/syntax/notmuch-*.vim usr/share/vim/addons/syntax diff --git a/debian/notmuch.install b/debian/notmuch.install index 0cce21bd..b4847fe5 100644 --- a/debian/notmuch.install +++ b/debian/notmuch.install @@ -1,5 +1,5 @@ +emacs/notmuch-emacs-mua.desktop usr/share/applications usr/bin/notmuch usr/bin/notmuch-emacs-mua usr/share/bash-completion usr/share/zsh/vendor-completions -emacs/notmuch-emacs-mua.desktop usr/share/applications diff --git a/debian/notmuch.manpages b/debian/notmuch.manpages index f9fcb54a..da91fc00 100644 --- a/debian/notmuch.manpages +++ b/debian/notmuch.manpages @@ -1,18 +1,18 @@ -usr/share/man/man5/notmuch-hooks.5.gz -usr/share/man/man1/notmuch-dump.1.gz -usr/share/man/man1/notmuch-count.1.gz -usr/share/man/man1/notmuch-compact.1.gz -usr/share/man/man1/notmuch-emacs-mua.1.gz -usr/share/man/man1/notmuch-new.1.gz -usr/share/man/man1/notmuch.1.gz -usr/share/man/man1/notmuch-reindex.1.gz usr/share/man/man1/notmuch-address.1.gz -usr/share/man/man1/notmuch-tag.1.gz -usr/share/man/man1/notmuch-reply.1.gz -usr/share/man/man1/notmuch-search.1.gz -usr/share/man/man1/notmuch-restore.1.gz -usr/share/man/man1/notmuch-insert.1.gz -usr/share/man/man1/notmuch-show.1.gz +usr/share/man/man1/notmuch-compact.1.gz usr/share/man/man1/notmuch-config.1.gz +usr/share/man/man1/notmuch-count.1.gz +usr/share/man/man1/notmuch-dump.1.gz +usr/share/man/man1/notmuch-emacs-mua.1.gz +usr/share/man/man1/notmuch-insert.1.gz +usr/share/man/man1/notmuch-new.1.gz +usr/share/man/man1/notmuch-reindex.1.gz +usr/share/man/man1/notmuch-reply.1.gz +usr/share/man/man1/notmuch-restore.1.gz +usr/share/man/man1/notmuch-search.1.gz +usr/share/man/man1/notmuch-show.1.gz +usr/share/man/man1/notmuch-tag.1.gz +usr/share/man/man1/notmuch.1.gz +usr/share/man/man5/notmuch-hooks.5.gz usr/share/man/man7/notmuch-properties.7.gz usr/share/man/man7/notmuch-search-terms.7.gz From eb1f799892a483f4e933867b2b9bafabd405045e Mon Sep 17 00:00:00 2001 From: Tomi Ollila Date: Sun, 17 Nov 2019 23:24:41 +0200 Subject: [PATCH 062/427] configure: fix reference to possibly undefined $PKG_CONFIG_PATH In case zlib not found by pkg-config(1) the pkg-config information is resolved by attempting to print ZLIB_VERSION from from zlib installation if it exists anyway. If above done successfully compat/zlib.pc is written for forthcoming pkg-config execution. Since `set -u` is in effect (since 124a67e96, 2016-05-06), expanding unset $PKG_CONFIG_PATH (would have) failed whenever tried. Now it is changed to set as "$PKG_CONFIG_PATH:compat" if PKG_CONFIG_PATH is set and is non-empty string, plain "compat" otherwise. --- configure | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure b/configure index fad2655c..c16d18dc 100755 --- a/configure +++ b/configure @@ -604,7 +604,7 @@ fi if ! pkg-config --exists zlib; then ${CC} -o compat/gen_zlib_pc "$srcdir"/compat/gen_zlib_pc.c >/dev/null 2>&1 && compat/gen_zlib_pc > compat/zlib.pc && - PKG_CONFIG_PATH="$PKG_CONFIG_PATH":compat && + PKG_CONFIG_PATH=${PKG_CONFIG_PATH:+$PKG_CONFIG_PATH:}compat && export PKG_CONFIG_PATH rm -f compat/gen_zlib_pc fi From a1d6e406f6f352c3d68a717b3f30a4f4011a945b Mon Sep 17 00:00:00 2001 From: David Edmondson Date: Wed, 11 Sep 2019 21:32:57 -0400 Subject: [PATCH 063/427] emacs: Asynchronous retrieval of GPG keys Rather than blocking emacs while gpg does its' thing, by default run key retrieval asynchronously, possibly updating the display of the message on successful completion. --- emacs/notmuch-crypto.el | 85 +++++++++++++++++++++++++++++++++++------ 1 file changed, 74 insertions(+), 11 deletions(-) diff --git a/emacs/notmuch-crypto.el b/emacs/notmuch-crypto.el index 4216f583..0ab85f4b 100644 --- a/emacs/notmuch-crypto.el +++ b/emacs/notmuch-crypto.el @@ -43,6 +43,11 @@ mode." :package-version '(notmuch . "0.25") :group 'notmuch-crypto) +(defcustom notmuch-crypto-get-keys-asynchronously t + "Retrieve gpg keys asynchronously." + :type 'boolean + :group 'notmuch-crypto) + (defface notmuch-crypto-part-header '((((class color) (background dark)) @@ -114,7 +119,7 @@ mode." (let ((keyid (concat "0x" (plist-get sigstatus :keyid)))) (setq label (concat "Unknown key ID " keyid " or unsupported algorithm")) (setq button-action 'notmuch-crypto-sigstatus-error-callback) - (setq help-msg (concat "Click to retrieve key ID " keyid " from keyserver and redisplay.")))) + (setq help-msg (concat "Click to retrieve key ID " keyid " from keyserver.")))) ((string= status "bad") (let ((keyid (concat "0x" (plist-get sigstatus :keyid)))) (setq label (concat "Bad signature (claimed key ID " keyid ")")) @@ -148,19 +153,77 @@ mode." (call-process epg-gpg-program nil t t "--batch" "--no-tty" "--list-keys" fingerprint)) (recenter -1)))) +(defun notmuch-crypto--async-key-sentinel (process event) + "When the user asks for a GPG key to be retrieved +asynchronously, handle completion of that task. + +If the retrieval is successful, the thread where the retrieval +was initiated is still displayed and the cursor has not moved, +redisplay the thread." + (let ((status (process-status process)) + (exit-status (process-exit-status process)) + (keyid (process-get process :gpg-key-id))) + (when (memq status '(exit signal)) + (message "Getting the GPG key %s asynchronously...%s." + keyid + (if (= exit-status 0) + "completed" + "failed")) + ;; If the original buffer is still alive and point didn't move + ;; (i.e. the user didn't move on or away), refresh the buffer to + ;; show the updated signature status. + (let ((show-buffer (process-get process :notmuch-show-buffer)) + (show-point (process-get process :notmuch-show-point))) + (when (and (bufferp show-buffer) + (buffer-live-p show-buffer) + (= show-point + (with-current-buffer show-buffer + (point)))) + (with-current-buffer show-buffer + (notmuch-show-refresh-view))))))) + +(defun notmuch-crypto--set-button-label (button label) + "Set the text displayed in BUTTON to LABEL." + (save-excursion + (let ((inhibit-read-only t)) + ;; This knows rather too much about how we typically format + ;; buttons. + (goto-char (button-start button)) + (forward-char 2) + (delete-region (point) (- (button-end button) 2)) + (insert label)))) + (defun notmuch-crypto-sigstatus-error-callback (button) (let* ((sigstatus (button-get button :notmuch-sigstatus)) (keyid (concat "0x" (plist-get sigstatus :keyid))) - (buffer (get-buffer-create "*notmuch-crypto-gpg-out*")) - (window (display-buffer buffer t nil))) - (with-selected-window window - (with-current-buffer buffer - (goto-char (point-max)) - (call-process epg-gpg-program nil t t "--batch" "--no-tty" "--recv-keys" keyid) - (insert "\n") - (call-process epg-gpg-program nil t t "--batch" "--no-tty" "--list-keys" keyid)) - (recenter -1)) - (notmuch-show-refresh-view))) + (buffer (get-buffer-create "*notmuch-crypto-gpg-out*"))) + (if notmuch-crypto-get-keys-asynchronously + (progn + (notmuch-crypto--set-button-label + button (format "Retrieving key %s asynchronously..." keyid)) + (let ((p (make-process :name "notmuch GPG key retrieval" + :buffer buffer + :command (list epg-gpg-program "--recv-keys" keyid) + :connection-type 'pipe + :sentinel #'notmuch-crypto--async-key-sentinel + ;; Create the process stopped so that + ;; we have time to store the key id, + ;; etc. on it. + :stop t))) + (process-put p :gpg-key-id keyid) + (process-put p :notmuch-show-buffer (current-buffer)) + (process-put p :notmuch-show-point (point)) + (message "Getting the GPG key %s asynchronously..." keyid) + (continue-process p))) + (let ((window (display-buffer buffer t nil))) + (with-selected-window window + (with-current-buffer buffer + (goto-char (point-max)) + (call-process epg-gpg-program nil t t "--recv-keys" keyid) + (insert "\n") + (call-process epg-gpg-program nil t t "--list-keys" keyid)) + (recenter -1)) + (notmuch-show-refresh-view))))) (defun notmuch-crypto-insert-encstatus-button (encstatus) (let* ((status (plist-get encstatus :status)) From 50f0cbcc4d78f93776009fd223e6d6b6736ecff9 Mon Sep 17 00:00:00 2001 From: David Edmondson Date: Wed, 11 Sep 2019 21:32:58 -0400 Subject: [PATCH 064/427] emacs: Minor refactoring of crypto code --- emacs/notmuch-crypto.el | 91 +++++++++++++++++++++-------------------- 1 file changed, 47 insertions(+), 44 deletions(-) diff --git a/emacs/notmuch-crypto.el b/emacs/notmuch-crypto.el index 0ab85f4b..457c821c 100644 --- a/emacs/notmuch-crypto.el +++ b/emacs/notmuch-crypto.el @@ -96,34 +96,37 @@ mode." :supertype 'notmuch-button-type) (defun notmuch-crypto-insert-sigstatus-button (sigstatus from) + "Insert a button describing the signature status SIGSTATUS sent +by user FROM." (let* ((status (plist-get sigstatus :status)) - (help-msg nil) (show-button t) - (label nil) (face 'notmuch-crypto-signature-unknown) - (button-action (lambda (button) (message (button-get button 'help-echo))))) + (button-action (lambda (button) (message (button-get button 'help-echo)))) + (keyid (concat "0x" (plist-get sigstatus :keyid))) + label help-msg) (cond ((string= status "good") - (let ((fingerprint (concat "0x" (plist-get sigstatus :fingerprint)))) - ;; if userid present, userid has full or greater validity - (if (plist-member sigstatus :userid) - (let ((userid (plist-get sigstatus :userid))) - (setq label (concat "Good signature by: " userid)) - (setq face 'notmuch-crypto-signature-good)) - (progn - (setq label (concat "Good signature by key: " fingerprint)) - (setq face 'notmuch-crypto-signature-good-key))) - (setq button-action 'notmuch-crypto-sigstatus-good-callback) - (setq help-msg (concat "Click to list key ID 0x" fingerprint ".")))) + (let ((fingerprint (concat "0x" (plist-get sigstatus :fingerprint))) + (userid (plist-get sigstatus :userid))) + ;; If userid is present it has full or greater validity. + (if userid + (setq label (concat "Good signature by: " userid) + face 'notmuch-crypto-signature-good) + (setq label (concat "Good signature by key: " fingerprint) + face 'notmuch-crypto-signature-good-key)) + (setq button-action 'notmuch-crypto-sigstatus-good-callback + help-msg (concat "Click to list key ID 0x" fingerprint ".")))) + ((string= status "error") - (let ((keyid (concat "0x" (plist-get sigstatus :keyid)))) - (setq label (concat "Unknown key ID " keyid " or unsupported algorithm")) - (setq button-action 'notmuch-crypto-sigstatus-error-callback) - (setq help-msg (concat "Click to retrieve key ID " keyid " from keyserver.")))) + (setq label (concat "Unknown key ID " keyid " or unsupported algorithm") + button-action 'notmuch-crypto-sigstatus-error-callback + help-msg (concat "Click to retrieve key ID " keyid + " from keyserver."))) + ((string= status "bad") - (let ((keyid (concat "0x" (plist-get sigstatus :keyid)))) - (setq label (concat "Bad signature (claimed key ID " keyid ")")) - (setq face 'notmuch-crypto-signature-bad))) + (setq label (concat "Bad signature (claimed key ID " keyid ")") + face 'notmuch-crypto-signature-bad)) + (status (setq label (concat "Unknown signature status: " status))) (t @@ -140,19 +143,19 @@ mode." :notmuch-from from) (insert "\n")))) -(declare-function notmuch-show-refresh-view "notmuch-show" (&optional reset-state)) - (defun notmuch-crypto-sigstatus-good-callback (button) (let* ((sigstatus (button-get button :notmuch-sigstatus)) (fingerprint (concat "0x" (plist-get sigstatus :fingerprint))) (buffer (get-buffer-create "*notmuch-crypto-gpg-out*")) - (window (display-buffer buffer t nil))) + (window (display-buffer buffer))) (with-selected-window window (with-current-buffer buffer (goto-char (point-max)) (call-process epg-gpg-program nil t t "--batch" "--no-tty" "--list-keys" fingerprint)) (recenter -1)))) +(declare-function notmuch-show-refresh-view "notmuch-show" (&optional reset-state)) + (defun notmuch-crypto--async-key-sentinel (process event) "When the user asks for a GPG key to be retrieved asynchronously, handle completion of that task. @@ -194,6 +197,8 @@ redisplay the thread." (insert label)))) (defun notmuch-crypto-sigstatus-error-callback (button) + "When signature validation has failed, try to retrieve the +corresponding key when the status button is pressed." (let* ((sigstatus (button-get button :notmuch-sigstatus)) (keyid (concat "0x" (plist-get sigstatus :keyid))) (buffer (get-buffer-create "*notmuch-crypto-gpg-out*"))) @@ -215,7 +220,7 @@ redisplay the thread." (process-put p :notmuch-show-point (point)) (message "Getting the GPG key %s asynchronously..." keyid) (continue-process p))) - (let ((window (display-buffer buffer t nil))) + (let ((window (display-buffer buffer))) (with-selected-window window (with-current-buffer buffer (goto-char (point-max)) @@ -226,25 +231,23 @@ redisplay the thread." (notmuch-show-refresh-view))))) (defun notmuch-crypto-insert-encstatus-button (encstatus) - (let* ((status (plist-get encstatus :status)) - (help-msg nil) - (label "Decryption not attempted") - (face 'notmuch-crypto-decryption)) - (cond - ((string= status "good") - (setq label "Decryption successful")) - ((string= status "bad") - (setq label "Decryption error")) - (t - (setq label (concat "Unknown encryption status" - (if status (concat ": " status)))))) - (insert-button - (concat "[ " label " ]") - :type 'notmuch-crypto-status-button-type - 'help-echo help-msg - 'face face - 'mouse-face face) - (insert "\n"))) + "Insert a button describing the encryption status ENCSTATUS." + (insert-button + (concat "[ " + (let ((status (plist-get encstatus :status))) + (cond + ((string= status "good") + "Decryption successful") + ((string= status "bad") + "Decryption error") + (t + (concat "Unknown encryption status" + (if status (concat ": " status)))))) + " ]") + :type 'notmuch-crypto-status-button-type + 'face 'notmuch-crypto-decryption + 'mouse-face 'notmuch-crypto-decryption) + (insert "\n")) ;; From d137affaceb78d7a54bdd62995c1ffa1fba4308b Mon Sep 17 00:00:00 2001 From: David Edmondson Date: Wed, 11 Sep 2019 21:32:59 -0400 Subject: [PATCH 065/427] emacs: Add notmuch-crypto-gpg-program and use it Allow the user to specify the gpg program to use when retrieving keys, etc., defaulting to the value of `epg-gpg-program'. --- emacs/notmuch-crypto.el | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/emacs/notmuch-crypto.el b/emacs/notmuch-crypto.el index 457c821c..9b615a0f 100644 --- a/emacs/notmuch-crypto.el +++ b/emacs/notmuch-crypto.el @@ -48,6 +48,11 @@ mode." :type 'boolean :group 'notmuch-crypto) +(defcustom notmuch-crypto-gpg-program epg-gpg-program + "The gpg executable." + :type 'string + :group 'notmuch-crypto) + (defface notmuch-crypto-part-header '((((class color) (background dark)) @@ -151,7 +156,7 @@ by user FROM." (with-selected-window window (with-current-buffer buffer (goto-char (point-max)) - (call-process epg-gpg-program nil t t "--batch" "--no-tty" "--list-keys" fingerprint)) + (call-process notmuch-crypto-gpg-program nil t t "--batch" "--no-tty" "--list-keys" fingerprint)) (recenter -1)))) (declare-function notmuch-show-refresh-view "notmuch-show" (&optional reset-state)) @@ -208,7 +213,7 @@ corresponding key when the status button is pressed." button (format "Retrieving key %s asynchronously..." keyid)) (let ((p (make-process :name "notmuch GPG key retrieval" :buffer buffer - :command (list epg-gpg-program "--recv-keys" keyid) + :command (list notmuch-crypto-gpg-program "--recv-keys" keyid) :connection-type 'pipe :sentinel #'notmuch-crypto--async-key-sentinel ;; Create the process stopped so that @@ -220,13 +225,14 @@ corresponding key when the status button is pressed." (process-put p :notmuch-show-point (point)) (message "Getting the GPG key %s asynchronously..." keyid) (continue-process p))) + (let ((window (display-buffer buffer))) (with-selected-window window (with-current-buffer buffer (goto-char (point-max)) - (call-process epg-gpg-program nil t t "--recv-keys" keyid) + (call-process notmuch-crypto-gpg-program nil t t "--recv-keys" keyid) (insert "\n") - (call-process epg-gpg-program nil t t "--list-keys" keyid)) + (call-process notmuch-crypto-gpg-program nil t t "--list-keys" keyid)) (recenter -1)) (notmuch-show-refresh-view))))) From a7884929d51790ed2782b04ba5b5e466d7148a63 Mon Sep 17 00:00:00 2001 From: David Edmondson Date: Wed, 11 Sep 2019 21:33:00 -0400 Subject: [PATCH 066/427] emacs: Improve the reporting of key activity Improve the information provided about key retrieval and key validity. --- emacs/notmuch-crypto.el | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/emacs/notmuch-crypto.el b/emacs/notmuch-crypto.el index 9b615a0f..f4585d5e 100644 --- a/emacs/notmuch-crypto.el +++ b/emacs/notmuch-crypto.el @@ -149,13 +149,16 @@ by user FROM." (insert "\n")))) (defun notmuch-crypto-sigstatus-good-callback (button) - (let* ((sigstatus (button-get button :notmuch-sigstatus)) + (let* ((id (notmuch-show-get-message-id)) + (sigstatus (button-get button :notmuch-sigstatus)) (fingerprint (concat "0x" (plist-get sigstatus :fingerprint))) (buffer (get-buffer-create "*notmuch-crypto-gpg-out*")) (window (display-buffer buffer))) (with-selected-window window (with-current-buffer buffer (goto-char (point-max)) + (insert (format "-- Key %s in message %s:\n" + fingerprint id)) (call-process notmuch-crypto-gpg-program nil t t "--batch" "--no-tty" "--list-keys" fingerprint)) (recenter -1)))) @@ -211,10 +214,14 @@ corresponding key when the status button is pressed." (progn (notmuch-crypto--set-button-label button (format "Retrieving key %s asynchronously..." keyid)) + (with-current-buffer buffer + (goto-char (point-max)) + (insert (format "--- Retrieving key %s:\n" keyid))) (let ((p (make-process :name "notmuch GPG key retrieval" - :buffer buffer - :command (list notmuch-crypto-gpg-program "--recv-keys" keyid) :connection-type 'pipe + :buffer buffer + :stderr buffer + :command (list notmuch-crypto-gpg-program "--recv-keys" keyid) :sentinel #'notmuch-crypto--async-key-sentinel ;; Create the process stopped so that ;; we have time to store the key id, @@ -230,6 +237,7 @@ corresponding key when the status button is pressed." (with-selected-window window (with-current-buffer buffer (goto-char (point-max)) + (insert (format "--- Retrieving key %s:\n" keyid)) (call-process notmuch-crypto-gpg-program nil t t "--recv-keys" keyid) (insert "\n") (call-process notmuch-crypto-gpg-program nil t t "--list-keys" keyid)) From 6cd47227de09556dbc611d842c9b4b0f077598fc Mon Sep 17 00:00:00 2001 From: David Bremner Date: Sun, 17 Nov 2019 21:58:12 -0400 Subject: [PATCH 067/427] test: add a known broken test for S/MIME decryption This should serve to clarify this feature is not implimented in notmuch yet. --- test/T355-smime.sh | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/test/T355-smime.sh b/test/T355-smime.sh index 336da917..11a4d6cd 100755 --- a/test/T355-smime.sh +++ b/test/T355-smime.sh @@ -94,4 +94,13 @@ Verification successful EOF test_expect_equal_file EXPECTED OUTPUT +test_begin_subtest "Decryption (notmuch CLI)" +test_subtest_known_broken +notmuch show --decrypt=true subject:"test encrypted message 001" |\ + grep "^This is a" > OUTPUT +cat < EXPECTED +This is a test encrypted message. +EOF +test_expect_equal_file EXPECTED OUTPUT + test_done From 1d9ec88d878d9817f18c98e71fa8b987d74f0508 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=96rjan=20Ekeberg?= Date: Fri, 13 Dec 2019 00:35:35 +0100 Subject: [PATCH 068/427] emacs: limit search for attachment to stop at first mime-part This commit changes the behaviour of notmuch-mua-attachment-check so that it stops searching for notmuch-mua-attachment-regexp when a new mime-part is reached. This avoids false warnings when matching words occur inside forwarded messages. --- emacs/notmuch-mua.el | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/emacs/notmuch-mua.el b/emacs/notmuch-mua.el index 7fdd76bc..76572b87 100644 --- a/emacs/notmuch-mua.el +++ b/emacs/notmuch-mua.el @@ -137,17 +137,20 @@ Typically this is added to `notmuch-mua-send-hook'." ;; When the message mentions attachment... (save-excursion (message-goto-body) - (loop while (re-search-forward notmuch-mua-attachment-regexp (point-max) t) - ;; For every instance of the "attachment" string - ;; found, examine the text properties. If the text - ;; has either a `face' or `syntax-table' property - ;; then it is quoted text and should *not* cause the - ;; user to be asked about a missing attachment. - if (let ((props (text-properties-at (match-beginning 0)))) - (not (or (memq 'syntax-table props) - (memq 'face props)))) - return t - finally return nil)) + ;; Limit search from reaching other possible parts of the message + (let ((search-limit (search-forward "\n<#" nil t))) + (message-goto-body) + (loop while (re-search-forward notmuch-mua-attachment-regexp search-limit t) + ;; For every instance of the "attachment" string + ;; found, examine the text properties. If the text + ;; has either a `face' or `syntax-table' property + ;; then it is quoted text and should *not* cause the + ;; user to be asked about a missing attachment. + if (let ((props (text-properties-at (match-beginning 0)))) + (not (or (memq 'syntax-table props) + (memq 'face props)))) + return t + finally return nil))) ;; ...but doesn't have a part with a filename... (save-excursion (message-goto-body) From 757ed001aad27d3c5ee8828174778b71013e4afd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=96rjan=20Ekeberg?= Date: Fri, 13 Dec 2019 00:35:36 +0100 Subject: [PATCH 069/427] test: extend test of attachment warnings Check that attachment warnings are not raised when the word "attach" only occurs in a forwarded message. --- test/emacs-attachment-warnings.el | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/test/emacs-attachment-warnings.el b/test/emacs-attachment-warnings.el index 200ca7ba..a3067b14 100644 --- a/test/emacs-attachment-warnings.el +++ b/test/emacs-attachment-warnings.el @@ -36,6 +36,12 @@ Return `t' if the message would be sent, otherwise `nil'" ;; fontification properties. For fontification to happen we need to ;; allow some time for redisplay. (sit-for 0.01))) + (t . (lambda () + ;; "attach" is only mentioned in a forwarded message. + (insert "Hello\n") + (insert "<#mml type=message/rfc822 disposition=inline>\n") + (insert "X-Has-Attach:\n") + (insert "<#/mml>\n"))) ;; These should not be okay: (nil . (lambda () (insert "Here is an attachment:\n"))) @@ -49,6 +55,12 @@ Return `t' if the message would be sent, otherwise `nil'" ;; looking at fontification properties. For fontification ;; to happen we need to allow some time for redisplay. (sit-for 0.01))) + (nil . (lambda () + ;; "attachment" is mentioned before a forwarded message. + (insert "I also attach something.\n") + (insert "<#mml type=message/rfc822 disposition=inline>\n") + (insert "X-Has-Attach:\n") + (insert "<#/mml>\n"))) )) (defun notmuch-test-attachment-warning-1 () From 7ebb2f5509c175c0efc6682fc7abf89a68f61f7d Mon Sep 17 00:00:00 2001 From: Daniel Kahn Gillmor Date: Wed, 4 Dec 2019 03:47:38 -0500 Subject: [PATCH 070/427] debian: return an error if debian snapshot build fails Signed-off-by: Daniel Kahn Gillmor --- Makefile.local | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/Makefile.local b/Makefile.local index 7c12612d..586cdf75 100644 --- a/Makefile.local +++ b/Makefile.local @@ -97,14 +97,16 @@ pre-release: .PHONY: debian-snapshot debian-snapshot: make VERSION=$(VERSION) clean - TMPFILE=$$(mktemp /tmp/notmuch.XXXXXX); \ - cp debian/changelog $${TMPFILE}; \ - EDITOR=/bin/true dch -b -v $(VERSION)+1 \ - -D UNRELEASED 'test build, not for upload'; \ - echo '3.0 (native)' > debian/source/format; \ - debuild -us -uc; \ - mv -f $${TMPFILE} debian/changelog; \ - echo '3.0 (quilt)' > debian/source/format + RETVAL=0 && \ + TMPFILE=$$(mktemp /tmp/notmuch.XXXXXX) && \ + cp debian/changelog $${TMPFILE} && \ + (EDITOR=/bin/true dch -b -v $(VERSION)+1 \ + -D UNRELEASED 'test build, not for upload' && \ + echo '3.0 (native)' > debian/source/format && \ + debuild -us -uc); RETVAL=$$? \ + mv -f $${TMPFILE} debian/changelog; \ + echo '3.0 (quilt)' > debian/source/format; \ + exit $$RETVAL .PHONY: release-message release-message: From 158dc389e9f2b6894c3591941540c8c2e6121528 Mon Sep 17 00:00:00 2001 From: Daniel Kahn Gillmor Date: Wed, 4 Dec 2019 03:47:39 -0500 Subject: [PATCH 071/427] debian: ship notmuch-emacs-mua.desktop from "make install" copy This helps dh_missing know what's going on. Signed-off-by: Daniel Kahn Gillmor --- debian/notmuch.install | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/debian/notmuch.install b/debian/notmuch.install index b4847fe5..60f09712 100644 --- a/debian/notmuch.install +++ b/debian/notmuch.install @@ -1,5 +1,5 @@ -emacs/notmuch-emacs-mua.desktop usr/share/applications usr/bin/notmuch usr/bin/notmuch-emacs-mua +usr/share/applications/notmuch-emacs-mua.desktop usr/share/bash-completion usr/share/zsh/vendor-completions From dc0650f5f58211d1935dd7ff84f942640d35da21 Mon Sep 17 00:00:00 2001 From: Daniel Kahn Gillmor Date: Wed, 4 Dec 2019 03:47:40 -0500 Subject: [PATCH 072/427] debian: install notmuch(3) manpage in libnotmuch-dev dh_missing noticed that we are building this manpage but not shipping it in debian. Signed-off-by: Daniel Kahn Gillmor --- debian/libnotmuch-dev.manpages | 1 + 1 file changed, 1 insertion(+) create mode 100644 debian/libnotmuch-dev.manpages diff --git a/debian/libnotmuch-dev.manpages b/debian/libnotmuch-dev.manpages new file mode 100644 index 00000000..9c4bd5d3 --- /dev/null +++ b/debian/libnotmuch-dev.manpages @@ -0,0 +1 @@ +usr/share/man/man3/notmuch.3.gz From dbb8e420c10e038e033adc2e53bbe16bd0988197 Mon Sep 17 00:00:00 2001 From: Daniel Kahn Gillmor Date: Wed, 4 Dec 2019 03:47:41 -0500 Subject: [PATCH 073/427] debian: ship notmuch-setup(1) as a copy of notmuch(1) This was being shipped by "make install", but we weren't shipping it in the debian package. Thanks to dh_missing for noticing! Signed-off-by: Daniel Kahn Gillmor --- debian/notmuch.manpages | 1 + 1 file changed, 1 insertion(+) diff --git a/debian/notmuch.manpages b/debian/notmuch.manpages index da91fc00..3ac2ff5b 100644 --- a/debian/notmuch.manpages +++ b/debian/notmuch.manpages @@ -10,6 +10,7 @@ usr/share/man/man1/notmuch-reindex.1.gz usr/share/man/man1/notmuch-reply.1.gz usr/share/man/man1/notmuch-restore.1.gz usr/share/man/man1/notmuch-search.1.gz +usr/share/man/man1/notmuch-setup.1.gz usr/share/man/man1/notmuch-show.1.gz usr/share/man/man1/notmuch-tag.1.gz usr/share/man/man1/notmuch.1.gz From cc62d33c07c1f14fbfc0df7464238c35dde8d929 Mon Sep 17 00:00:00 2001 From: Daniel Kahn Gillmor Date: Wed, 4 Dec 2019 03:47:42 -0500 Subject: [PATCH 074/427] debian: Remove python2 detritus Since we removed python-notmuch, we do not need to retain this file any longer. Signed-off-by: Daniel Kahn Gillmor --- debian/python-notmuch.install | 1 - 1 file changed, 1 deletion(-) delete mode 100644 debian/python-notmuch.install diff --git a/debian/python-notmuch.install b/debian/python-notmuch.install deleted file mode 100644 index b2cc1360..00000000 --- a/debian/python-notmuch.install +++ /dev/null @@ -1 +0,0 @@ -usr/lib/python2* From bfa99713830970a0613250ded9a0830c4f8db13f Mon Sep 17 00:00:00 2001 From: Daniel Kahn Gillmor Date: Mon, 9 Dec 2019 13:49:07 -0500 Subject: [PATCH 075/427] debian: record upstream files which should not be installed by the package Signed-off-by: Daniel Kahn Gillmor --- debian/not-installed | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 debian/not-installed diff --git a/debian/not-installed b/debian/not-installed new file mode 100644 index 00000000..fd929459 --- /dev/null +++ b/debian/not-installed @@ -0,0 +1,3 @@ +usr/share/applications/mimeinfo.cache +usr/share/info/dir +usr/share/emacs/site-lisp/*.elc From a24006d62ffe9aa6a485f0bf3215e6f3dd3ad2aa Mon Sep 17 00:00:00 2001 From: Daniel Kahn Gillmor Date: Mon, 9 Dec 2019 13:49:08 -0500 Subject: [PATCH 076/427] debian: ship info files in the standard location Signed-off-by: Daniel Kahn Gillmor --- debian/elpa-notmuch.elpa | 1 - debian/elpa-notmuch.info | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) create mode 100644 debian/elpa-notmuch.info diff --git a/debian/elpa-notmuch.elpa b/debian/elpa-notmuch.elpa index 19e3ba51..a924468a 100644 --- a/debian/elpa-notmuch.elpa +++ b/debian/elpa-notmuch.elpa @@ -1,3 +1,2 @@ emacs/*.el emacs/notmuch-logo.png -debian/tmp/usr/share/info/* diff --git a/debian/elpa-notmuch.info b/debian/elpa-notmuch.info new file mode 100644 index 00000000..0ac0fbf6 --- /dev/null +++ b/debian/elpa-notmuch.info @@ -0,0 +1 @@ +usr/share/info/*.info From a2ecab476290c941b4a88c9383d6852ee6dbeb47 Mon Sep 17 00:00:00 2001 From: Daniel Kahn Gillmor Date: Mon, 9 Dec 2019 13:49:09 -0500 Subject: [PATCH 077/427] debian elpa-notmuch: ship elisp and .png from "make install" Rather than ship from the source directories, ship these files from where they're installed by "make install". This doesn't resolve all the dh_missing warnings yet (due to #946142), but it leaves the last bit of that problem in the hands of the dh-elpa package, and it's not due to notmuch's packaging any longer. Note that notmuch-pkg.el is only relevant for elpa, so it was not installed by "make install", and has to be extracted deliberately from the source tree. Note also that we now don't ship make-deps.el or rstdoc.el, as these are build tools and don't belong in the deployed package. Signed-off-by: Daniel Kahn Gillmor --- debian/elpa-notmuch.elpa | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/debian/elpa-notmuch.elpa b/debian/elpa-notmuch.elpa index a924468a..4712b73f 100644 --- a/debian/elpa-notmuch.elpa +++ b/debian/elpa-notmuch.elpa @@ -1,2 +1,3 @@ -emacs/*.el -emacs/notmuch-logo.png +debian/tmp/usr/share/emacs/site-lisp/*.el +debian/tmp/usr/share/emacs/site-lisp/notmuch-logo.png +emacs/notmuch-pkg.el From b5db6aa1277e9e5645976a2dfdec2baaea385877 Mon Sep 17 00:00:00 2001 From: Daniel Kahn Gillmor Date: Mon, 9 Dec 2019 13:49:10 -0500 Subject: [PATCH 078/427] debian: move packaging to dh 12 --- debian/compat | 1 - debian/control | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) delete mode 100644 debian/compat diff --git a/debian/compat b/debian/compat deleted file mode 100644 index b4de3947..00000000 --- a/debian/compat +++ /dev/null @@ -1 +0,0 @@ -11 diff --git a/debian/control b/debian/control index 1c9427b2..fb2b31c1 100644 --- a/debian/control +++ b/debian/control @@ -11,7 +11,7 @@ Build-Conflicts: ruby1.8, Build-Depends: bash-completion (>=1.9.0~), - debhelper (>= 11~), + debhelper-compat (= 12), dh-elpa (>= 1.3), dh-python, dpkg-dev (>= 1.17.14), From 34c5233894f5143baaf5d4fc94df68fc186409d6 Mon Sep 17 00:00:00 2001 From: Daniel Kahn Gillmor Date: Mon, 23 Dec 2019 16:02:16 -0500 Subject: [PATCH 079/427] python/notmuch2: fix typo for ObjectDestroyedError There is no functional change here, just a fix to a typo in the docstrings. Signed-off-by: Daniel Kahn Gillmor --- bindings/python-cffi/notmuch2/__init__.py | 2 +- bindings/python-cffi/notmuch2/_database.py | 4 ++-- bindings/python-cffi/notmuch2/_tags.py | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/bindings/python-cffi/notmuch2/__init__.py b/bindings/python-cffi/notmuch2/__init__.py index 4d76ec15..613317e0 100644 --- a/bindings/python-cffi/notmuch2/__init__.py +++ b/bindings/python-cffi/notmuch2/__init__.py @@ -13,7 +13,7 @@ Errors All errors occuring due to errors from the underlying notmuch database are subclasses of the :exc:`NotmuchError`. Due to memory management it is possible to try and use an object after it has been freed. In -this case a :exc:`ObjectDestoryedError` will be raised. +this case a :exc:`ObjectDestroyedError` will be raised. Memory Management ================= diff --git a/bindings/python-cffi/notmuch2/_database.py b/bindings/python-cffi/notmuch2/_database.py index a1c624a7..7ef4fe17 100644 --- a/bindings/python-cffi/notmuch2/_database.py +++ b/bindings/python-cffi/notmuch2/_database.py @@ -342,7 +342,7 @@ class Database(base.NotmuchObject): def default_indexopts(self): """Returns default index options for the database. - :raises ObjectDestoryedError: if used after destroyed. + :raises ObjectDestroyedError: if used after destroyed. :returns: :class:`IndexOptions`. """ @@ -770,7 +770,7 @@ class IndexOptions(base.NotmuchObject): You can change this policy by assigning a new :class:`DecryptionPolicy` to this property. - :raises ObjectDestoryedError: if used after destroyed. + :raises ObjectDestroyedError: if used after destroyed. :returns: A :class:`DecryptionPolicy` enum instance. """ diff --git a/bindings/python-cffi/notmuch2/_tags.py b/bindings/python-cffi/notmuch2/_tags.py index fe422a79..212852a8 100644 --- a/bindings/python-cffi/notmuch2/_tags.py +++ b/bindings/python-cffi/notmuch2/_tags.py @@ -277,7 +277,7 @@ class TagsIter(base.NotmuchObject, collections.abc.Iterator): :param errors: If using a codec, this is the error handler. See :func:`str.decode` to which this is passed on. - :raises ObjectDestoryedError: if used after destroyed. + :raises ObjectDestroyedError: if used after destroyed. """ _tags_p = base.MemoryPointer() From 93cc4b99dff2dea1f4af3b39f1864eb976c37648 Mon Sep 17 00:00:00 2001 From: Daniel Kahn Gillmor Date: Mon, 23 Dec 2019 16:06:48 -0500 Subject: [PATCH 080/427] python/notmuch2: fix typo for "destroyed" Another fix to the docstrings, this time for the English part of the docstrings, not the Python class name. No functional changes here. Signed-off-by: Daniel Kahn Gillmor --- bindings/python-cffi/notmuch2/_database.py | 28 +++++++++++----------- bindings/python-cffi/notmuch2/_errors.py | 2 +- bindings/python-cffi/notmuch2/_message.py | 24 +++++++++---------- bindings/python-cffi/notmuch2/_thread.py | 20 ++++++++-------- 4 files changed, 37 insertions(+), 37 deletions(-) diff --git a/bindings/python-cffi/notmuch2/_database.py b/bindings/python-cffi/notmuch2/_database.py index 7ef4fe17..95f59ca0 100644 --- a/bindings/python-cffi/notmuch2/_database.py +++ b/bindings/python-cffi/notmuch2/_database.py @@ -262,7 +262,7 @@ class Database(base.NotmuchObject): This is returned as a :class:`pathlib.Path` instance. - :raises ObjectDestroyedError: if used after destoryed. + :raises ObjectDestroyedError: if used after destroyed. """ try: return self._cache_path @@ -277,7 +277,7 @@ class Database(base.NotmuchObject): This is a positive integer. - :raises ObjectDestroyedError: if used after destoryed. + :raises ObjectDestroyedError: if used after destroyed. """ try: return self._cache_version @@ -296,7 +296,7 @@ class Database(base.NotmuchObject): A read-only database will never be upgradable. - :raises ObjectDestroyedError: if used after destoryed. + :raises ObjectDestroyedError: if used after destroyed. """ ret = capi.lib.notmuch_database_needs_upgrade(self._db_p) return bool(ret) @@ -320,7 +320,7 @@ class Database(base.NotmuchObject): not imply durability, it only ensures the changes are performed atomically. - :raises ObjectDestroyedError: if used after destoryed. + :raises ObjectDestroyedError: if used after destroyed. """ ctx = AtomicContext(self, '_db_p') return ctx @@ -330,7 +330,7 @@ class Database(base.NotmuchObject): Returned as a ``(revision, uuid)`` namedtuple. - :raises ObjectDestroyedError: if used after destoryed. + :raises ObjectDestroyedError: if used after destroyed. """ raw_uuid = capi.ffi.new('char**') rev = capi.lib.notmuch_database_get_revision(self._db_p, raw_uuid) @@ -387,7 +387,7 @@ class Database(base.NotmuchObject): READ_ONLY mode. :raises UpgradeRequiredError: The database must be upgraded first. - :raises ObjectDestroyedError: if used after destoryed. + :raises ObjectDestroyedError: if used after destroyed. """ if not hasattr(os, 'PathLike') and isinstance(filename, pathlib.Path): filename = bytes(filename) @@ -426,7 +426,7 @@ class Database(base.NotmuchObject): READ_ONLY mode. :raises UpgradeRequiredError: The database must be upgraded first. - :raises ObjectDestroyedError: if used after destoryed. + :raises ObjectDestroyedError: if used after destroyed. """ if not hasattr(os, 'PathLike') and isinstance(filename, pathlib.Path): filename = bytes(filename) @@ -458,7 +458,7 @@ class Database(base.NotmuchObject): :raises OutOfMemoryError: When there is no memory to allocate the message instance. :raises XapianError: A Xapian exception ocurred. - :raises ObjectDestroyedError: if used after destoryed. + :raises ObjectDestroyedError: if used after destroyed. """ msg_pp = capi.ffi.new('notmuch_message_t **') ret = capi.lib.notmuch_database_find_message(self._db_p, @@ -489,7 +489,7 @@ class Database(base.NotmuchObject): :raises OutOfMemoryError: When there is no memory to allocate the message instance. :raises XapianError: A Xapian exception ocurred. - :raises ObjectDestroyedError: if used after destoryed. + :raises ObjectDestroyedError: if used after destroyed. """ if not hasattr(os, 'PathLike') and isinstance(filename, pathlib.Path): filename = bytes(filename) @@ -522,7 +522,7 @@ class Database(base.NotmuchObject): :rtype: ImmutableTagSet - :raises ObjectDestroyedError: if used after destoryed. + :raises ObjectDestroyedError: if used after destroyed. """ try: ref = self._cached_tagset @@ -570,7 +570,7 @@ class Database(base.NotmuchObject): :raises OutOfMemoryError: if no memory is available to allocate the query. - :raises ObjectDestroyedError: if used after destoryed. + :raises ObjectDestroyedError: if used after destroyed. """ query = self._create_query(query, omit_excluded=omit_excluded, @@ -587,7 +587,7 @@ class Database(base.NotmuchObject): :returns: An iterator over the messages found. :rtype: MessageIter - :raises ObjectDestroyedError: if used after destoryed. + :raises ObjectDestroyedError: if used after destroyed. """ query = self._create_query(query, omit_excluded=omit_excluded, @@ -635,7 +635,7 @@ class AtomicContext: section is not active. When it is raised at exit time the atomic section is still active and you may need to try using :meth:`force_end`. - :raises ObjectDestroyedError: if used after destoryed. + :raises ObjectDestroyedError: if used after destroyed. """ def __init__(self, db, ptr_name): @@ -675,7 +675,7 @@ class AtomicContext: not ended. :raises UnbalancedAtomicError: If the database was currently not in an atomic section. - :raises ObjectDestroyedError: if used after destoryed. + :raises ObjectDestroyedError: if used after destroyed. """ ret = capi.lib.notmuch_database_end_atomic(self._ptr()) if ret != capi.lib.NOTMUCH_STATUS_SUCCESS: diff --git a/bindings/python-cffi/notmuch2/_errors.py b/bindings/python-cffi/notmuch2/_errors.py index 1c88763b..13369445 100644 --- a/bindings/python-cffi/notmuch2/_errors.py +++ b/bindings/python-cffi/notmuch2/_errors.py @@ -97,7 +97,7 @@ class IllegalArgumentError(NotmuchError): pass class ObjectDestroyedError(NotmuchError): - """The object has already been destoryed and it's memory freed. + """The object has already been destroyed and it's memory freed. This occurs when :meth:`destroy` has been called on the object but you still happen to have access to the object. This should not diff --git a/bindings/python-cffi/notmuch2/_message.py b/bindings/python-cffi/notmuch2/_message.py index bb561426..c5fdbf6d 100644 --- a/bindings/python-cffi/notmuch2/_message.py +++ b/bindings/python-cffi/notmuch2/_message.py @@ -98,7 +98,7 @@ class Message(base.NotmuchObject): bytes() on it will return the original bytes used to create it. - :raises ObjectDestroyedError: if used after destoryed. + :raises ObjectDestroyedError: if used after destroyed. """ ret = capi.lib.notmuch_message_get_message_id(self._msg_p) return base.BinString(capi.ffi.string(ret)) @@ -116,7 +116,7 @@ class Message(base.NotmuchObject): bytes() on it will return the original bytes used to create it. - :raises ObjectDestroyedError: if used after destoryed. + :raises ObjectDestroyedError: if used after destroyed. """ ret = capi.lib.notmuch_message_get_thread_id(self._msg_p) return base.BinString(capi.ffi.string(ret)) @@ -128,7 +128,7 @@ class Message(base.NotmuchObject): If multiple files in the database contain the same message ID this will be just one of the files, chosen at random. - :raises ObjectDestroyedError: if used after destoryed. + :raises ObjectDestroyedError: if used after destroyed. """ ret = capi.lib.notmuch_message_get_filename(self._msg_p) return pathlib.Path(os.fsdecode(capi.ffi.string(ret))) @@ -140,7 +140,7 @@ class Message(base.NotmuchObject): See :attr:`path` for details, this is the same but does return the path as a bytes object which is faster but less convenient. - :raises ObjectDestroyedError: if used after destoryed. + :raises ObjectDestroyedError: if used after destroyed. """ ret = capi.lib.notmuch_message_get_filename(self._msg_p) return capi.ffi.string(ret) @@ -155,7 +155,7 @@ class Message(base.NotmuchObject): :returns: Iterator yielding :class:`pathlib.Path` instances. :rtype: iter - :raises ObjectDestroyedError: if used after destoryed. + :raises ObjectDestroyedError: if used after destroyed. """ fnames_p = capi.lib.notmuch_message_get_filenames(self._msg_p) return PathIter(self, fnames_p) @@ -169,7 +169,7 @@ class Message(base.NotmuchObject): :returns: Iterator yielding :class:`bytes` instances. :rtype: iter - :raises ObjectDestroyedError: if used after destoryed. + :raises ObjectDestroyedError: if used after destroyed. """ fnames_p = capi.lib.notmuch_message_get_filenames(self._msg_p) return FilenamesIter(self, fnames_p) @@ -184,7 +184,7 @@ class Message(base.NotmuchObject): :attr:`messageid` and :attr:`threadid` attributes are valid for it. - :raises ObjectDestroyedError: if used after destoryed. + :raises ObjectDestroyedError: if used after destroyed. """ ret = capi.lib.notmuch_message_get_flag( self._msg_p, capi.lib.NOTMUCH_MESSAGE_FLAG_GHOST) @@ -201,7 +201,7 @@ class Message(base.NotmuchObject): these messages to be flagged, which results in this property being set to *True*. - :raises ObjectDestroyedError: if used after destoryed. + :raises ObjectDestroyedError: if used after destroyed. """ ret = capi.lib.notmuch_message_get_flag( self._msg_p, capi.lib.NOTMUCH_MESSAGE_FLAG_EXCLUDED) @@ -216,7 +216,7 @@ class Message(base.NotmuchObject): message's header, you can get the original header value with :meth:`header`. - :raises ObjectDestroyedError: if used after destoryed. + :raises ObjectDestroyedError: if used after destroyed. """ return capi.lib.notmuch_message_get_date(self._msg_p) @@ -240,7 +240,7 @@ class Message(base.NotmuchObject): :raises LookupError: if the header is not present. :raises NullPointerError: For unexpected notmuch errors. - :raises ObjectDestroyedError: if used after destoryed. + :raises ObjectDestroyedError: if used after destroyed. """ # The returned is supposedly guaranteed to be UTF-8. Header # names must be ASCII as per RFC x822. @@ -263,7 +263,7 @@ class Message(base.NotmuchObject): :raises ReadOnlyDatabaseError: When manipulating tags on a database opened in read-only mode. - :raises ObjectDestroyedError: if used after destoryed. + :raises ObjectDestroyedError: if used after destroyed. """ try: ref = self._cached_tagset @@ -297,7 +297,7 @@ class Message(base.NotmuchObject): :raises UnbalancedFreezeThawError: if you somehow managed to call __exit__ of this context manager more than once. Why did you do that? - :raises ObjectDestroyedError: if used after destoryed. + :raises ObjectDestroyedError: if used after destroyed. """ ret = capi.lib.notmuch_message_freeze(self._msg_p) if ret != capi.lib.NOTMUCH_STATUS_SUCCESS: diff --git a/bindings/python-cffi/notmuch2/_thread.py b/bindings/python-cffi/notmuch2/_thread.py index a754749f..bb76f2dc 100644 --- a/bindings/python-cffi/notmuch2/_thread.py +++ b/bindings/python-cffi/notmuch2/_thread.py @@ -42,7 +42,7 @@ class Thread(base.NotmuchObject, collections.abc.Iterable): def threadid(self): """The thread ID as a :class:`BinString`. - :raises ObjectDestroyedError: if used after destoryed. + :raises ObjectDestroyedError: if used after destroyed. """ ret = capi.lib.notmuch_thread_get_thread_id(self._thread_p) return base.BinString.from_cffi(ret) @@ -50,7 +50,7 @@ class Thread(base.NotmuchObject, collections.abc.Iterable): def __len__(self): """Return the number of messages in the thread. - :raises ObjectDestroyedError: if used after destoryed. + :raises ObjectDestroyedError: if used after destroyed. """ return capi.lib.notmuch_thread_get_total_messages(self._thread_p) @@ -59,7 +59,7 @@ class Thread(base.NotmuchObject, collections.abc.Iterable): :returns: An iterator yielding :class:`Message` instances. - :raises ObjectDestroyedError: if used after destoryed. + :raises ObjectDestroyedError: if used after destroyed. """ msgs_p = capi.lib.notmuch_thread_get_toplevel_messages(self._thread_p) return message.MessageIter(self, msgs_p, db=self._db) @@ -69,7 +69,7 @@ class Thread(base.NotmuchObject, collections.abc.Iterable): :returns: An iterator yielding :class:`Message` instances. - :raises ObjectDestroyedError: if used after destoryed. + :raises ObjectDestroyedError: if used after destroyed. """ msgs_p = capi.lib.notmuch_thread_get_messages(self._thread_p) return message.MessageIter(self, msgs_p, db=self._db) @@ -82,7 +82,7 @@ class Thread(base.NotmuchObject, collections.abc.Iterable): which did directly match the search query which this thread originates from. - :raises ObjectDestroyedError: if used after destoryed. + :raises ObjectDestroyedError: if used after destroyed. """ return capi.lib.notmuch_thread_get_matched_messages(self._thread_p) @@ -100,7 +100,7 @@ class Thread(base.NotmuchObject, collections.abc.Iterable): :returns: The stringified list of authors. :rtype: BinString - :raises ObjectDestroyedError: if used after destoryed. + :raises ObjectDestroyedError: if used after destroyed. """ ret = capi.lib.notmuch_thread_get_authors(self._thread_p) return base.BinString.from_cffi(ret) @@ -115,7 +115,7 @@ class Thread(base.NotmuchObject, collections.abc.Iterable): :returns: The thread's subject. :rtype: BinString - :raises ObjectDestroyedError: if used after destoryed. + :raises ObjectDestroyedError: if used after destroyed. """ ret = capi.lib.notmuch_thread_get_subject(self._thread_p) return base.BinString.from_cffi(ret) @@ -127,7 +127,7 @@ class Thread(base.NotmuchObject, collections.abc.Iterable): The time the first message was sent as an integer number of seconds since the *epoch*, 1 Jan 1970. - :raises ObjectDestroyedError: if used after destoryed. + :raises ObjectDestroyedError: if used after destroyed. """ return capi.lib.notmuch_thread_get_oldest_date(self._thread_p) @@ -138,7 +138,7 @@ class Thread(base.NotmuchObject, collections.abc.Iterable): The time the last message was sent as an integer number of seconds since the *epoch*, 1 Jan 1970. - :raises ObjectDestroyedError: if used after destoryed. + :raises ObjectDestroyedError: if used after destroyed. """ return capi.lib.notmuch_thread_get_newest_date(self._thread_p) @@ -160,7 +160,7 @@ class Thread(base.NotmuchObject, collections.abc.Iterable): :rtype: ImmutableTagSet - :raises ObjectDestroyedError: if used after destoryed. + :raises ObjectDestroyedError: if used after destroyed. """ try: ref = self._cached_tagset From cedc6db19d2bb37ce0c8f74be25cdf045b35caca Mon Sep 17 00:00:00 2001 From: Daniel Kahn Gillmor Date: Mon, 23 Dec 2019 16:35:40 -0500 Subject: [PATCH 081/427] debian: Override lintian suggestion to move elpa-notmuch to Section: lisp Signed-off-by: Daniel Kahn Gillmor --- debian/elpa-notmuch.lintian-overrides | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 debian/elpa-notmuch.lintian-overrides diff --git a/debian/elpa-notmuch.lintian-overrides b/debian/elpa-notmuch.lintian-overrides new file mode 100644 index 00000000..aa275eda --- /dev/null +++ b/debian/elpa-notmuch.lintian-overrides @@ -0,0 +1,4 @@ +# elpa-notmuch is an elisp plugin for dealing with e-mail. We can +# already tell from the package name that it is an elisp package, so +# it belongs in Section: mail, and lintian is being too strict here. +elpa-notmuch: wrong-section-according-to-package-name elpa-notmuch => lisp From 5aa60342c86b163848bca6882f34821d4871b849 Mon Sep 17 00:00:00 2001 From: Daniel Kahn Gillmor Date: Tue, 24 Dec 2019 17:05:44 -0500 Subject: [PATCH 082/427] legacy-display: accept text/plain legacy display parts https://www.ietf.org/id/draft-autocrypt-lamps-protected-headers-02.html Makes it clear that the "Legacy Display" part of an encrypted message with protected headers can (and indeed, should) be of content-type text/plain, though some clients still generate the Legacy Display part as content-type text/rfc822-headers. Notmuch should recognize the part whichever of the two content-types it uses. See also discussion in https://github.com/autocrypt/protected-headers/issues/23 for why the community of implementers is moving in the direction of text/plain. Signed-off-by: Daniel Kahn Gillmor --- util/repair.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/util/repair.c b/util/repair.c index 9fba97b7..f5cbb14b 100644 --- a/util/repair.c +++ b/util/repair.c @@ -49,8 +49,14 @@ _notmuch_crypto_payload_has_legacy_display (GMimeObject *payload) if (g_mime_multipart_get_count (mpayload) != 2) return false; first = g_mime_multipart_get_part (mpayload, 0); - if (! g_mime_content_type_is_type (g_mime_object_get_content_type (first), - "text", "rfc822-headers")) + /* Early implementations that generated "Legacy Display" parts used + Content-Type: text/rfc822-headers, but text/plain is more widely + rendered, so it is now the standard choice. We accept either as a + Legacy Display part. */ + if (! (g_mime_content_type_is_type (g_mime_object_get_content_type (first), + "text", "plain") || + g_mime_content_type_is_type (g_mime_object_get_content_type (first), + "text", "rfc822-headers"))) return false; protected_header_parameter = g_mime_object_get_content_type_parameter (first, "protected-headers"); if ((! protected_header_parameter) || strcmp (protected_header_parameter, "v1")) From fd9a951249693b8aa218dd888d39d4a54d63745c Mon Sep 17 00:00:00 2001 From: Daniel Kahn Gillmor Date: Mon, 23 Dec 2019 12:39:27 -0500 Subject: [PATCH 083/427] legacy-display: drop tests that try to match headers in a Legacy Display part These tests were an attempt to establish that the content of the "Legacy Display" part is the same as the actual protected headers of the message. But this is more conservative than we need to be. https://www.ietf.org/id/draft-autocrypt-lamps-protected-headers-02.html section 5.3 makes clear that the Legacy Display part is purely decorative, and section 5.2.1 clarifies that the detection can be done purely by MIME structure and Content-Type alone. Furthermore, now that we're accepting text/plain Legacy Display parts, it's not clear the lines in the Legacy Display part should be interpreted as needing an exact string match (e.g. "real" headers are likely to be RFC 2047 encoded, but the text/plain Legacy Display part probably should not be). The concerns that motivated this test in the past were twofold: that we might accidentally hide some information from the reader of the message that they should have available to them, or that we could introduce a covert channel that would be invisible to other clients. I no longer think these are significant concerns: a) There will be no accidental misidentification of a Legacy Display part. The identification of the Legacy Display part is unambiguous due to MIME structure and Content-Type. MIME structure MUST be the first child part of a two-part multipart/mixed Cryptographic Payload. And the protected-headers=v1 content-type parameter must be present on both the cryptographic payload and the legacy display part, so no one would accidentally generate this structure and have it be accidentally matched. b) As for creating a covert channel, many such channels already exist. For example, non-standard e-mail headers, custom MIME types, unusual MIME structures, etc, all make it possible to ship some content in a message that will be visible in some MUAs but not in others. This doesn't make the situation demonstrably worse. Signed-off-by: Daniel Kahn Gillmor --- util/repair.c | 60 ++------------------------------------------------- 1 file changed, 2 insertions(+), 58 deletions(-) diff --git a/util/repair.c b/util/repair.c index f5cbb14b..5a64e001 100644 --- a/util/repair.c +++ b/util/repair.c @@ -27,13 +27,7 @@ _notmuch_crypto_payload_has_legacy_display (GMimeObject *payload) { GMimeMultipart *mpayload; const char *protected_header_parameter; - GMimeTextPart *legacy_display; - char *legacy_display_header_text = NULL; - GMimeStream *stream = NULL; - GMimeParser *parser = NULL; - GMimeObject *legacy_header_object = NULL, *first; - GMimeHeaderList *legacy_display_headers = NULL, *protected_headers = NULL; - bool ret = false; + GMimeObject *first; if (! g_mime_content_type_is_type (g_mime_object_get_content_type (payload), "multipart", "mixed")) @@ -64,57 +58,7 @@ _notmuch_crypto_payload_has_legacy_display (GMimeObject *payload) if (! GMIME_IS_TEXT_PART (first)) return false; - /* ensure that the headers in the first part all match the values - * found in the payload's own protected headers! if they don't, - * we should not treat this as a valid "legacy-display" part. - * - * Crafting a GMimeHeaderList object from the content of the - * text/rfc822-headers part is pretty clumsy; we should probably - * push something into GMime that makes this a one-shot - * operation. */ - if ((protected_headers = g_mime_object_get_header_list (payload), protected_headers) && - (legacy_display = GMIME_TEXT_PART (first), legacy_display) && - (legacy_display_header_text = g_mime_text_part_get_text (legacy_display), legacy_display_header_text) && - (stream = g_mime_stream_mem_new_with_buffer (legacy_display_header_text, strlen (legacy_display_header_text)), stream) && - (g_mime_stream_write (stream, "\r\n\r\n", 4) == 4) && - (g_mime_stream_seek (stream, 0, GMIME_STREAM_SEEK_SET) == 0) && - (parser = g_mime_parser_new_with_stream (stream), parser) && - (legacy_header_object = g_mime_parser_construct_part (parser, NULL), legacy_header_object) && - (legacy_display_headers = g_mime_object_get_header_list (legacy_header_object), legacy_display_headers)) { - /* walk through legacy_display_headers, comparing them against - * their values in the protected_headers: */ - ret = true; - for (int i = 0; i < g_mime_header_list_get_count (legacy_display_headers); i++) { - GMimeHeader *dh = g_mime_header_list_get_header_at (legacy_display_headers, i); - if (dh == NULL) { - ret = false; - goto DONE; - } - GMimeHeader *ph = g_mime_header_list_get_header (protected_headers, g_mime_header_get_name (dh)); - if (ph == NULL) { - ret = false; - goto DONE; - } - const char *dhv = g_mime_header_get_value (dh); - const char *phv = g_mime_header_get_value (ph); - if (dhv == NULL || phv == NULL || strcmp (dhv, phv)) { - ret = false; - goto DONE; - } - } - } - - DONE: - if (legacy_display_header_text) - g_free (legacy_display_header_text); - if (stream) - g_object_unref (stream); - if (parser) - g_object_unref (parser); - if (legacy_header_object) - g_object_unref (legacy_header_object); - - return ret; + return true; } GMimeObject * From 92a7f26face78334893b11fbdd6f79139b92bf5b Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Fri, 3 Jan 2020 09:04:00 -0800 Subject: [PATCH 084/427] emacs: don't start processes stopped It causes this function to fail with: let: Wrong type argument: null, t Support for this was removed from Emacs in April 2019 (5c5e309527e6b582e2c04b83e7af45f3144863ac) because it never worked correctly (apparently). This also shouldn't be necessary as sentinels will not be called unless emacs is idle or waiting for input. Therefore, the `process-put' calls immediately following the `make-process' call should always complete before the sentinel is first called. --- emacs/notmuch-crypto.el | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/emacs/notmuch-crypto.el b/emacs/notmuch-crypto.el index f4585d5e..4035ee37 100644 --- a/emacs/notmuch-crypto.el +++ b/emacs/notmuch-crypto.el @@ -222,16 +222,11 @@ corresponding key when the status button is pressed." :buffer buffer :stderr buffer :command (list notmuch-crypto-gpg-program "--recv-keys" keyid) - :sentinel #'notmuch-crypto--async-key-sentinel - ;; Create the process stopped so that - ;; we have time to store the key id, - ;; etc. on it. - :stop t))) + :sentinel #'notmuch-crypto--async-key-sentinel))) (process-put p :gpg-key-id keyid) (process-put p :notmuch-show-buffer (current-buffer)) (process-put p :notmuch-show-point (point)) - (message "Getting the GPG key %s asynchronously..." keyid) - (continue-process p))) + (message "Getting the GPG key %s asynchronously..." keyid))) (let ((window (display-buffer buffer))) (with-selected-window window From 0a0413f5142b578ee6c5ba45e1dfcdc522f57e42 Mon Sep 17 00:00:00 2001 From: Daniel Kahn Gillmor Date: Mon, 23 Dec 2019 15:14:38 -0500 Subject: [PATCH 085/427] debian: add Build-Depends-Package for libnotmuch5.symbols See lintian informational tag symbols-file-missing-build-depends-package-field for hints about this minor metadata update. Signed-off-by: Daniel Kahn Gillmor --- debian/libnotmuch5.symbols | 1 + 1 file changed, 1 insertion(+) diff --git a/debian/libnotmuch5.symbols b/debian/libnotmuch5.symbols index 308567b8..2ae73dad 100644 --- a/debian/libnotmuch5.symbols +++ b/debian/libnotmuch5.symbols @@ -1,4 +1,5 @@ libnotmuch.so.5 libnotmuch5 #MINVER# +* Build-Depends-Package: libnotmuch-dev notmuch_built_with@Base 0.23~rc0 notmuch_config_list_destroy@Base 0.23~rc0 notmuch_config_list_key@Base 0.23~rc0 From aba7fb375bf4edad5da40134b4e7a390a881792b Mon Sep 17 00:00:00 2001 From: Daniel Kahn Gillmor Date: Fri, 10 Jan 2020 13:58:07 -0500 Subject: [PATCH 086/427] doc: clean up manpage description of "notmuch-config list" output The escaping in the description of the output of "notmuch-config list" appears to have been inherited from some previous attempts at documentation. It leaked out in the actual generated manpage documentation, where it looks like this: list Every configuration item is printed to stdout, each on a separate line of the form: *section*.\ *item*\ =\ *value* This simplification cleans up the overescaping. Signed-off-by: Daniel Kahn Gillmor --- doc/man1/notmuch-config.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/man1/notmuch-config.rst b/doc/man1/notmuch-config.rst index 28487079..7347ad31 100644 --- a/doc/man1/notmuch-config.rst +++ b/doc/man1/notmuch-config.rst @@ -38,7 +38,7 @@ programmatically as described in the SYNOPSIS above. Every configuration item is printed to stdout, each on a separate line of the form:: - *section*.\ *item*\ =\ *value* + section.item=value No additional whitespace surrounds the dot or equals sign characters. In a multiple-value item (a list), the values are From e091427d98b0c49e96fb312ad1af6862776b896a Mon Sep 17 00:00:00 2001 From: Peter Wang Date: Sat, 8 Feb 2020 12:49:21 +1100 Subject: [PATCH 087/427] test: add known broken test with timestamp beyond 2038 --- test/T160-json.sh | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/test/T160-json.sh b/test/T160-json.sh index 004adb4e..ec1b5adb 100755 --- a/test/T160-json.sh +++ b/test/T160-json.sh @@ -64,6 +64,21 @@ test_expect_equal_json "$output" "[{\"thread\": \"XXX\", \"tags\": [\"inbox\", \"unread\"]}]" +test_begin_subtest "Search message: json, 64-bit timestamp" +test_subtest_known_broken +add_message "[subject]=\"json-search-64bit-timestamp-subject\"" "[date]=\"Tue, 01 Jan 2999 12:00:00 -0000\"" "[body]=\"json-search-64bit-timestamp-message\"" +output=$(notmuch search --format=json "json-search-64bit-timestamp-message" | notmuch_search_sanitize) +test_expect_equal_json "$output" "[{\"thread\": \"XXX\", + \"timestamp\": 32472187200, + \"date_relative\": \"the future\", + \"matched\": 1, + \"total\": 1, + \"authors\": \"Notmuch Test Suite\", + \"subject\": \"json-search-64bit-timestamp-subject\", + \"query\": [\"id:$gen_msg_id\", null], + \"tags\": [\"inbox\", + \"unread\"]}]" + test_begin_subtest "Format version: too low" test_expect_code 20 "notmuch search --format-version=0 \\*" From c17fca40e2bc5514863d98807aaed318f144fd1a Mon Sep 17 00:00:00 2001 From: Peter Wang Date: Sat, 8 Feb 2020 12:49:22 +1100 Subject: [PATCH 088/427] sprinter: change integer method to use int64_t In particular, timestamps beyond 2038 could overflow the sprinter interface on systems where time_t is 64-bit but 'int' is a signed 32-bit integer type. --- sprinter-json.c | 5 +++-- sprinter-sexp.c | 5 +++-- sprinter-text.c | 5 +++-- sprinter.h | 2 +- test/T160-json.sh | 1 - 5 files changed, 10 insertions(+), 8 deletions(-) diff --git a/sprinter-json.c b/sprinter-json.c index c6ec8577..273bdeca 100644 --- a/sprinter-json.c +++ b/sprinter-json.c @@ -1,3 +1,4 @@ +#include #include #include #include @@ -124,11 +125,11 @@ json_string (struct sprinter *sp, const char *val) } static void -json_integer (struct sprinter *sp, int val) +json_integer (struct sprinter *sp, int64_t val) { struct sprinter_json *spj = json_begin_value (sp); - fprintf (spj->stream, "%d", val); + fprintf (spj->stream, "%"PRId64, val); } static void diff --git a/sprinter-sexp.c b/sprinter-sexp.c index 6891ea42..35c007d5 100644 --- a/sprinter-sexp.c +++ b/sprinter-sexp.c @@ -18,6 +18,7 @@ * Author: Peter Feigl */ +#include #include #include #include @@ -161,11 +162,11 @@ sexp_keyword (struct sprinter *sp, const char *val) } static void -sexp_integer (struct sprinter *sp, int val) +sexp_integer (struct sprinter *sp, int64_t val) { struct sprinter_sexp *sps = sexp_begin_value (sp); - fprintf (sps->stream, "%d", val); + fprintf (sps->stream, "%"PRId64, val); } static void diff --git a/sprinter-text.c b/sprinter-text.c index 648b54b1..7b68f98c 100644 --- a/sprinter-text.c +++ b/sprinter-text.c @@ -1,3 +1,4 @@ +#include #include #include #include @@ -44,11 +45,11 @@ text_string (struct sprinter *sp, const char *val) } static void -text_integer (struct sprinter *sp, int val) +text_integer (struct sprinter *sp, int64_t val) { struct sprinter_text *sptxt = (struct sprinter_text *) sp; - fprintf (sptxt->stream, "%d", val); + fprintf (sptxt->stream, "%"PRId64, val); } static void diff --git a/sprinter.h b/sprinter.h index 182b1a8b..528d8a2d 100644 --- a/sprinter.h +++ b/sprinter.h @@ -33,7 +33,7 @@ typedef struct sprinter { */ void (*string)(struct sprinter *, const char *); void (*string_len)(struct sprinter *, const char *, size_t); - void (*integer)(struct sprinter *, int); + void (*integer)(struct sprinter *, int64_t); void (*boolean)(struct sprinter *, bool); void (*null)(struct sprinter *); diff --git a/test/T160-json.sh b/test/T160-json.sh index ec1b5adb..d975efa7 100755 --- a/test/T160-json.sh +++ b/test/T160-json.sh @@ -65,7 +65,6 @@ test_expect_equal_json "$output" "[{\"thread\": \"XXX\", \"unread\"]}]" test_begin_subtest "Search message: json, 64-bit timestamp" -test_subtest_known_broken add_message "[subject]=\"json-search-64bit-timestamp-subject\"" "[date]=\"Tue, 01 Jan 2999 12:00:00 -0000\"" "[body]=\"json-search-64bit-timestamp-message\"" output=$(notmuch search --format=json "json-search-64bit-timestamp-message" | notmuch_search_sanitize) test_expect_equal_json "$output" "[{\"thread\": \"XXX\", From 018ad3703ba46ea603854f51f8ff5ae86183cca0 Mon Sep 17 00:00:00 2001 From: Daniel Kahn Gillmor Date: Tue, 18 Feb 2020 17:42:57 -0500 Subject: [PATCH 089/427] Drop deprecated/unused crypto.gpg_path crypto.gpg_path was only used when we built against gmime versions before 3.0. Since we now depend on gmime 3.0.3 or later, it is meaningless. The removal of the field from the _notmuch_config struct would be an ABI change if that struct were externally exposed, but it is not, so it's safe to unilaterally remove it. Signed-off-by: Daniel Kahn Gillmor --- bindings/python-cffi/tests/conftest.py | 2 -- doc/man1/notmuch-config.rst | 8 -------- notmuch-config.c | 1 - 3 files changed, 11 deletions(-) diff --git a/bindings/python-cffi/tests/conftest.py b/bindings/python-cffi/tests/conftest.py index e322cc64..de7db8e7 100644 --- a/bindings/python-cffi/tests/conftest.py +++ b/bindings/python-cffi/tests/conftest.py @@ -78,8 +78,6 @@ def maildir(tmppath): exclude_tags=deleted;spam; [maildir] synchronize_flags=true - [crypto] - gpg_path=gpg """.format(tmppath=tmppath))) return MailDir(tmppath) diff --git a/doc/man1/notmuch-config.rst b/doc/man1/notmuch-config.rst index 7347ad31..323a8b65 100644 --- a/doc/man1/notmuch-config.rst +++ b/doc/man1/notmuch-config.rst @@ -134,14 +134,6 @@ The available configuration items are described below. Default: ``true``. -**crypto.gpg_path** - Name (or full path) of gpg binary to use in verification and - decryption of PGP/MIME messages. NOTE: This configuration item is - deprecated, and will be ignored if notmuch is built against GMime - 3.0 or later. - - Default: ``gpg``. - **index.decrypt** **[STORED IN DATABASE]** Policy for decrypting encrypted messages during indexing. Must be one of: ``false``, ``auto``, ``nostash``, or ``true``. diff --git a/notmuch-config.c b/notmuch-config.c index 1b079e85..19c2ddb3 100644 --- a/notmuch-config.c +++ b/notmuch-config.c @@ -119,7 +119,6 @@ struct _notmuch_config { bool is_new; char *database_path; - char *crypto_gpg_path; char *user_name; char *user_primary_email; const char **user_other_email; From 13f2027101a5f9e1ace7a83d7877b2bf4ad4ca75 Mon Sep 17 00:00:00 2001 From: Daniel Kahn Gillmor Date: Wed, 18 Mar 2020 04:02:50 -0400 Subject: [PATCH 090/427] configure: Check GMime version properly Signed-off-by: Daniel Kahn Gillmor --- configure | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure b/configure index c16d18dc..70031d14 100755 --- a/configure +++ b/configure @@ -513,7 +513,7 @@ fi GMIME_MINVER=3.0.3 printf "Checking for GMime development files... " -if pkg-config --exists "gmime-3.0 > $GMIME_MINVER"; then +if pkg-config --exists "gmime-3.0 >= $GMIME_MINVER"; then printf "Yes.\n" have_gmime=1 gmime_cflags=$(pkg-config --cflags gmime-3.0) From 1c390652455356c9acfea0eb982c688e2f09e47a Mon Sep 17 00:00:00 2001 From: Daniel Kahn Gillmor Date: Wed, 18 Mar 2020 03:48:40 -0400 Subject: [PATCH 091/427] tests/smime: fix typo in README Signed-off-by: Daniel Kahn Gillmor --- test/smime/README | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/smime/README b/test/smime/README index 92803c77..46211922 100644 --- a/test/smime/README +++ b/test/smime/README @@ -2,6 +2,6 @@ test.crt: self signed certificated % gpgsm --gen-key # needs gpgsm 2.1 key+cert.pem: cert + unencryped private - % gpsm --import test.crt + % gpgsm --import test.crt % gpgsm --export-private-key-p12 -out foo.p12 (no passphrase) % openssl pkcs12 -in ns.p12 -clcerts -nodes > key+cert.pem From b27b23a7f9bc33a842e0c17369bf3dc23e48ccd6 Mon Sep 17 00:00:00 2001 From: Daniel Kahn Gillmor Date: Wed, 18 Mar 2020 03:47:48 -0400 Subject: [PATCH 092/427] mime-node: Pass the correct flags to g_mime_multipart_signed_verify GMIME_ENCRYPT_NONE and GMIME_VERIFY_NONE have the same value, but they are different enumerated types. So in C, this is a cosmetic change, but it is technically correct if we only had stricter typing. Signed-off-by: Daniel Kahn Gillmor --- mime-node.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mime-node.c b/mime-node.c index d4996a33..e531078c 100644 --- a/mime-node.c +++ b/mime-node.c @@ -201,7 +201,7 @@ node_verify (mime_node_t *node, GMimeObject *part) node->verify_attempted = true; node->sig_list = g_mime_multipart_signed_verify ( - GMIME_MULTIPART_SIGNED (part), GMIME_ENCRYPT_NONE, &err); + GMIME_MULTIPART_SIGNED (part), GMIME_VERIFY_NONE, &err); if (node->sig_list) set_signature_list_destructor (node); From f2a85904f16267930271f5b6601099497f93b0f9 Mon Sep 17 00:00:00 2001 From: Daniel Kahn Gillmor Date: Tue, 17 Mar 2020 22:57:42 -0400 Subject: [PATCH 093/427] Correct doxygen framing for libnotmuch.h MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Apparently doxygen needs its comments formatted in a specific way to notice that the group is closed. Without this fix, with doxygen 1.8.16-2 we see: ``` doxygen ./doc/doxygen.cfg …/notmuch/lib/notmuch.h:2322: warning: end of file while inside a group ``` Signed-off-by: Daniel Kahn Gillmor --- lib/notmuch.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/notmuch.h b/lib/notmuch.h index 34666b88..ceb5a018 100644 --- a/lib/notmuch.h +++ b/lib/notmuch.h @@ -2312,7 +2312,7 @@ notmuch_indexopts_destroy (notmuch_indexopts_t *options); */ notmuch_bool_t notmuch_built_with (const char *name); -/* @} */ +/**@}*/ #pragma GCC visibility pop From f140bbcb40ac2510189329c11ca8ff20650c9eab Mon Sep 17 00:00:00 2001 From: Mark Walters Date: Thu, 27 Feb 2020 17:16:47 +0000 Subject: [PATCH 094/427] notmuch-show.c: add an option for messages to be returned unthreaded This adds a --unthreaded option to notmuch show to tell it to return the matching messages in an unthreaded order (so just by date). To make it easier for users, in particular for notmuch-tree.el, we output each message with the same "nesting" as if it were an entire thread in its own right. amended by db: s/status= /status = / --- notmuch-show.c | 61 +++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 55 insertions(+), 6 deletions(-) diff --git a/notmuch-show.c b/notmuch-show.c index 21792a57..ab1cd144 100644 --- a/notmuch-show.c +++ b/notmuch-show.c @@ -1066,11 +1066,11 @@ do_show_single (void *ctx, /* Formatted output of threads */ static int -do_show (void *ctx, - notmuch_query_t *query, - const notmuch_show_format_t *format, - sprinter_t *sp, - notmuch_show_params_t *params) +do_show_threaded (void *ctx, + notmuch_query_t *query, + const notmuch_show_format_t *format, + sprinter_t *sp, + notmuch_show_params_t *params) { notmuch_threads_t *threads; notmuch_thread_t *thread; @@ -1107,6 +1107,50 @@ do_show (void *ctx, return res != NOTMUCH_STATUS_SUCCESS; } +static int +do_show_unthreaded (void *ctx, + notmuch_query_t *query, + const notmuch_show_format_t *format, + sprinter_t *sp, + notmuch_show_params_t *params) +{ + notmuch_messages_t *messages; + notmuch_message_t *message; + notmuch_status_t status, res = NOTMUCH_STATUS_SUCCESS; + notmuch_bool_t excluded; + + status = notmuch_query_search_messages (query, &messages); + if (print_status_query ("notmuch show", query, status)) + return 1; + + sp->begin_list (sp); + + for (; + notmuch_messages_valid (messages); + notmuch_messages_move_to_next (messages)) { + sp->begin_list (sp); + sp->begin_list (sp); + + message = notmuch_messages_get (messages); + + notmuch_message_set_flag (message, NOTMUCH_MESSAGE_FLAG_MATCH, TRUE); + excluded = notmuch_message_get_flag (message, NOTMUCH_MESSAGE_FLAG_EXCLUDED); + + if (!excluded || !params->omit_excluded) { + status = show_message (ctx, format, sp, message, 0, params); + if (status && !res) + res = status; + } else { + sp->null (sp); + } + notmuch_message_destroy (message); + sp->end (sp); + sp->end (sp); + } + sp->end (sp); + return res; +} + enum { NOTMUCH_FORMAT_NOT_SPECIFIED, NOTMUCH_FORMAT_JSON, @@ -1168,6 +1212,7 @@ notmuch_show_command (notmuch_config_t *config, int argc, char *argv[]) bool exclude = true; bool entire_thread_set = false; bool single_message; + bool unthreaded = FALSE; notmuch_opt_desc_t options[] = { { .opt_keyword = &format, .name = "format", .keywords = @@ -1181,6 +1226,7 @@ notmuch_show_command (notmuch_config_t *config, int argc, char *argv[]) { .opt_bool = &exclude, .name = "exclude" }, { .opt_bool = ¶ms.entire_thread, .name = "entire-thread", .present = &entire_thread_set }, + { .opt_bool = &unthreaded, .name = "unthreaded" }, { .opt_int = ¶ms.part, .name = "part" }, { .opt_keyword = (int *) (¶ms.crypto.decrypt), .name = "decrypt", .keyword_no_arg_value = "true", .keywords = @@ -1317,7 +1363,10 @@ notmuch_show_command (notmuch_config_t *config, int argc, char *argv[]) params.omit_excluded = false; } - ret = do_show (config, query, formatter, sprinter, ¶ms); + if (unthreaded) + ret = do_show_unthreaded (config, query, formatter, sprinter, ¶ms); + else + ret = do_show_threaded (config, query, formatter, sprinter, ¶ms); } DONE: From 63f4ba30571ef5cc633c418f8ab7151308e55f0e Mon Sep 17 00:00:00 2001 From: Mark Walters Date: Thu, 27 Feb 2020 17:16:48 +0000 Subject: [PATCH 095/427] Introduce unthreaded mode This commit introduces a new 'unthreaded' search mode where each matching message is shown on a separate line. It shares almost all of its code with tree view. Subsequent commits will allow it to diverge slightly in appearance. --- emacs/notmuch-hello.el | 2 +- emacs/notmuch-lib.el | 1 + emacs/notmuch-show.el | 2 +- emacs/notmuch-tree.el | 28 +++++++++++++++++++++------- 4 files changed, 24 insertions(+), 9 deletions(-) diff --git a/emacs/notmuch-hello.el b/emacs/notmuch-hello.el index aff8beb5..858446df 100644 --- a/emacs/notmuch-hello.el +++ b/emacs/notmuch-hello.el @@ -31,7 +31,7 @@ (declare-function notmuch-search "notmuch" (&optional query oldest-first target-thread target-line continuation)) (declare-function notmuch-poll "notmuch" ()) (declare-function notmuch-tree "notmuch-tree" - (&optional query query-context target buffer-name open-target)) + (&optional query query-context target buffer-name open-target unthreaded)) (defun notmuch-saved-search-get (saved-search field) "Get FIELD from SAVED-SEARCH. diff --git a/emacs/notmuch-lib.el b/emacs/notmuch-lib.el index 8acad267..73b165e4 100644 --- a/emacs/notmuch-lib.el +++ b/emacs/notmuch-lib.el @@ -154,6 +154,7 @@ For example, if you wanted to remove an \"inbox\" tag and add an (define-key map "q" 'notmuch-bury-or-kill-this-buffer) (define-key map "s" 'notmuch-search) (define-key map "z" 'notmuch-tree) + (define-key map "u" 'notmuch-unthreaded) (define-key map "m" 'notmuch-mua-new-mail) (define-key map "g" 'notmuch-refresh-this-buffer) (define-key map "=" 'notmuch-refresh-this-buffer) diff --git a/emacs/notmuch-show.el b/emacs/notmuch-show.el index ef2bf1e0..d4a1389b 100644 --- a/emacs/notmuch-show.el +++ b/emacs/notmuch-show.el @@ -48,7 +48,7 @@ (declare-function notmuch-count-attachments "notmuch" (mm-handle)) (declare-function notmuch-save-attachments "notmuch" (mm-handle &optional queryp)) (declare-function notmuch-tree "notmuch-tree" - (&optional query query-context target buffer-name open-target)) + (&optional query query-context target buffer-name open-target unthreaded)) (declare-function notmuch-tree-get-message-properties "notmuch-tree" nil) (declare-function notmuch-read-query "notmuch" (prompt)) (declare-function notmuch-draft-resume "notmuch-draft" (id)) diff --git a/emacs/notmuch-tree.el b/emacs/notmuch-tree.el index c00315e8..80b830dd 100644 --- a/emacs/notmuch-tree.el +++ b/emacs/notmuch-tree.el @@ -42,6 +42,11 @@ ;; the following variable is defined in notmuch.el (defvar notmuch-search-query-string) +;; this variable distinguishes the unthreaded display from the normal tree display +(defvar notmuch-tree-unthreaded nil + "A buffer local copy of argument unthreaded to the function notmuch-tree") +(make-variable-buffer-local 'notmuch-tree-unthreaded) + (defgroup notmuch-tree nil "Showing message and thread structure." :group 'notmuch) @@ -890,7 +895,7 @@ Complete list of currently available key bindings: (notmuch-sexp-parse-partial-list 'notmuch-tree-insert-forest-thread results-buf))))) -(defun notmuch-tree-worker (basic-query &optional query-context target open-target) +(defun notmuch-tree-worker (basic-query &optional query-context target open-target unthreaded) "Insert the tree view of the search in the current buffer. This is is a helper function for notmuch-tree. The arguments are @@ -898,6 +903,7 @@ the same as for the function notmuch-tree." (interactive) (notmuch-tree-mode) (add-hook 'post-command-hook #'notmuch-tree-command-hook t t) + (setq notmuch-tree-unthreaded unthreaded) (setq notmuch-tree-basic-query basic-query) (setq notmuch-tree-query-context (if (or (string= query-context "") (string= query-context "*")) @@ -915,7 +921,7 @@ the same as for the function notmuch-tree." (let* ((search-args (concat basic-query (if query-context (concat " and (" query-context ")")) )) - (message-arg "--entire-thread")) + (message-arg (if unthreaded "--unthreaded" "--entire-thread"))) (if (equal (car (process-lines notmuch-command "count" search-args)) "0") (setq search-args basic-query)) (notmuch-tag-clear-cache) @@ -940,7 +946,7 @@ the same as for the function notmuch-tree." ")") notmuch-tree-basic-query)) -(defun notmuch-tree (&optional query query-context target buffer-name open-target) +(defun notmuch-tree (&optional query query-context target buffer-name open-target unthreaded) "Display threads matching QUERY in Tree View. The arguments are: @@ -953,23 +959,31 @@ The arguments are: current if it appears in the tree view results. BUFFER-NAME: the name of the buffer to display the tree view. If it is nil \"*notmuch-tree\" followed by QUERY is used. - OPEN-TARGET: If TRUE open the target message in the message pane." + OPEN-TARGET: If TRUE open the target message in the message pane. + UNTHREADED: If TRUE only show matching messages in an unthreaded view." (interactive) (if (null query) - (setq query (notmuch-read-query "Notmuch tree view search: "))) + (setq query (notmuch-read-query (concat "Notmuch " + (if unthreaded "unthreaded " "tree ") + "view search: ")))) (let ((buffer (get-buffer-create (generate-new-buffer-name (or buffer-name - (concat "*notmuch-tree-" query "*"))))) + (concat "*notmuch-" + (if unthreaded "unthreaded-" "tree-") + query "*"))))) (inhibit-read-only t)) (switch-to-buffer buffer)) ;; Don't track undo information for this buffer (set 'buffer-undo-list t) - (notmuch-tree-worker query query-context target open-target) + (notmuch-tree-worker query query-context target open-target unthreaded) (setq truncate-lines t)) +(defun notmuch-unthreaded (&optional query query-context target buffer-name open-target) + (interactive) + (notmuch-tree query query-context target buffer-name open-target t)) ;; From a82fb6e67042d402166104e803376f6f68613ba9 Mon Sep 17 00:00:00 2001 From: Mark Walters Date: Thu, 27 Feb 2020 17:16:49 +0000 Subject: [PATCH 096/427] Unthreaded mode: allow different result format It is likely that the user will want a different line format for unthreaded mode from tree mode; in particular the thread structure graphics are unnecessary in unthreaded mode. Add a new customisable variable and set it to something sensible. --- emacs/notmuch-tree.el | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/emacs/notmuch-tree.el b/emacs/notmuch-tree.el index 80b830dd..760eaaec 100644 --- a/emacs/notmuch-tree.el +++ b/emacs/notmuch-tree.el @@ -76,6 +76,31 @@ Note the author string should not contain :type '(alist :key-type (string) :value-type (string)) :group 'notmuch-tree) +(defcustom notmuch-unthreaded-result-format + `(("date" . "%12s ") + ("authors" . "%-20s") + ((("subject" . "%s")) ." %-54s ") + ("tags" . "(%s)")) + "Result formatting for unthreaded Tree view. Supported fields are: date, + authors, subject, tree, tags. Tree means the thread tree + box graphics. The field may also be a list in which case + the formatting rules are applied recursively and then the + output of all the fields in the list is inserted + according to format-string. + +Note the author string should not contain + whitespace (put it in the neighbouring fields instead). + For example: + (setq notmuch-tree-result-format \(\(\"authors\" . \"%-40s\"\) + \(\"subject\" . \"%s\"\)\)\)" + :type '(alist :key-type (string) :value-type (string)) + :group 'notmuch-tree) + +(defun notmuch-tree-result-format () + (if notmuch-tree-unthreaded + notmuch-unthreaded-result-format + notmuch-tree-result-format)) + ;; Faces for messages that match the query. (defface notmuch-tree-match-face '((t :inherit default)) @@ -759,7 +784,7 @@ unchanged ADDRESS if parsing fails." ;; We need to save the previous subject as it will get overwritten ;; by the insert-field calls. (let ((previous-subject notmuch-tree-previous-subject)) - (insert (notmuch-tree-format-field-list notmuch-tree-result-format msg)) + (insert (notmuch-tree-format-field-list (notmuch-tree-result-format) msg)) (notmuch-tree-set-message-properties msg) (notmuch-tree-set-prop :previous-subject previous-subject) (insert "\n"))) From c36e91d9d4774666064a840b19ccf7880f658ad1 Mon Sep 17 00:00:00 2001 From: Mark Walters Date: Thu, 27 Feb 2020 17:16:50 +0000 Subject: [PATCH 097/427] Unthreaded mode: allow user to choose different `show out' than tree Tree mode allows the user to choose whether to use the split screen displaying just the current message or a full screen displaying the entire thread. As unthreaded mode is quite different in use the user may want a different customisation for this mode. --- emacs/notmuch-tree.el | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/emacs/notmuch-tree.el b/emacs/notmuch-tree.el index 760eaaec..895c05f4 100644 --- a/emacs/notmuch-tree.el +++ b/emacs/notmuch-tree.el @@ -56,6 +56,16 @@ :type 'boolean :group 'notmuch-tree) +(defcustom notmuch-unthreaded-show-out t + "View selected messages in new window rather than split-pane." + :type 'boolean + :group 'notmuch-tree) + +(defun notmuch-tree-show-out () + (if notmuch-tree-unthreaded + notmuch-unthreaded-show-out + notmuch-tree-show-out)) + (defcustom notmuch-tree-result-format `(("date" . "%12s ") ("authors" . "%-20s") @@ -531,8 +541,8 @@ NOT change the database." Shows in split pane or whole window according to value of `notmuch-tree-show-out'. A prefix argument reverses the choice." (interactive "P") - (if (or (and notmuch-tree-show-out (not arg)) - (and (not notmuch-tree-show-out) arg)) + (if (or (and (notmuch-tree-show-out) (not arg)) + (and (not (notmuch-tree-show-out)) arg)) (notmuch-tree-show-message-out) (notmuch-tree-show-message-in))) From c578c32e3be76fe19c3e7355a88fcb799b68b17e Mon Sep 17 00:00:00 2001 From: Mark Walters Date: Thu, 27 Feb 2020 17:16:51 +0000 Subject: [PATCH 098/427] Add a U binding to switch to unthreaded from other views We have shortcuts S and Z to let the user switch to Search view and Tree view with the current search. Add U to let the user switch to unthreaded view from the current search, and ensure that S and Z switch from unthreaded to search and tree veiew respectively. --- emacs/notmuch-show.el | 10 ++++++++++ emacs/notmuch-tree.el | 23 +++++++++++++++++++++-- emacs/notmuch.el | 6 ++++++ 3 files changed, 37 insertions(+), 2 deletions(-) diff --git a/emacs/notmuch-show.el b/emacs/notmuch-show.el index d4a1389b..214e279f 100644 --- a/emacs/notmuch-show.el +++ b/emacs/notmuch-show.el @@ -50,6 +50,8 @@ (declare-function notmuch-tree "notmuch-tree" (&optional query query-context target buffer-name open-target unthreaded)) (declare-function notmuch-tree-get-message-properties "notmuch-tree" nil) +(declare-function notmuch-unthreaded + (&optional query query-context target buffer-name open-target)) (declare-function notmuch-read-query "notmuch" (prompt)) (declare-function notmuch-draft-resume "notmuch-draft" (id)) @@ -1471,6 +1473,7 @@ reset based on the original query." (let ((map (make-sparse-keymap))) (set-keymap-parent map notmuch-common-keymap) (define-key map "Z" 'notmuch-tree-from-show-current-query) + (define-key map "U" 'notmuch-unthreaded-from-show-current-query) (define-key map (kbd "") 'widget-backward) (define-key map (kbd "M-TAB") 'notmuch-show-previous-button) (define-key map (kbd "") 'notmuch-show-previous-button) @@ -1559,6 +1562,13 @@ All currently available key bindings: notmuch-show-query-context (notmuch-show-get-message-id))) +(defun notmuch-unthreaded-from-show-current-query () + "Call notmuch unthreaded with the current query" + (interactive) + (notmuch-unthreaded notmuch-show-thread-id + notmuch-show-query-context + (notmuch-show-get-message-id))) + (defun notmuch-show-move-to-message-top () (goto-char (notmuch-show-message-top))) diff --git a/emacs/notmuch-tree.el b/emacs/notmuch-tree.el index 895c05f4..9a83292c 100644 --- a/emacs/notmuch-tree.el +++ b/emacs/notmuch-tree.el @@ -294,6 +294,8 @@ FUNC." (define-key map [remap notmuch-jump-search] (notmuch-tree-close-message-pane-and #'notmuch-jump-search)) (define-key map "S" 'notmuch-search-from-tree-current-query) + (define-key map "U" 'notmuch-unthreaded-from-tree-current-query) + (define-key map "Z" 'notmuch-tree-from-unthreaded-current-query) ;; these use notmuch-show functions directly (define-key map "|" 'notmuch-show-pipe-message) @@ -474,6 +476,18 @@ NOT change the database." (notmuch-tree-close-message-window) (notmuch-tree query))) +(defun notmuch-unthreaded-from-tree-current-query () + "Switch from tree view to unthreaded view" + (interactive) + (unless notmuch-tree-unthreaded + (notmuch-tree-refresh-view 'unthreaded))) + +(defun notmuch-tree-from-unthreaded-current-query () + "Switch from unthreaded view to tree view" + (interactive) + (when notmuch-tree-unthreaded + (notmuch-tree-refresh-view 'tree))) + (defun notmuch-search-from-tree-current-query () "Call notmuch search with the current query" (interactive) @@ -635,19 +649,24 @@ message will be \"unarchived\", i.e. the tag changes in (when (window-live-p notmuch-tree-message-window) (notmuch-tree-show-message-in))) -(defun notmuch-tree-refresh-view () +(defun notmuch-tree-refresh-view (&optional view) "Refresh view." (interactive) (when (get-buffer-process (current-buffer)) (error "notmuch tree process already running for current buffer")) (let ((inhibit-read-only t) (basic-query notmuch-tree-basic-query) + (unthreaded (cond ((eq view 'unthreaded) t) + ((eq view 'tree) nil) + (t notmuch-tree-unthreaded))) (query-context notmuch-tree-query-context) (target (notmuch-tree-get-message-id))) (erase-buffer) (notmuch-tree-worker basic-query query-context - target))) + target + nil + unthreaded))) (defun notmuch-tree-thread-top () (when (notmuch-tree-get-message-properties) diff --git a/emacs/notmuch.el b/emacs/notmuch.el index 0d68d123..f4789b4f 100644 --- a/emacs/notmuch.el +++ b/emacs/notmuch.el @@ -190,6 +190,7 @@ there will be called at other points of notmuch execution." (define-key map (kbd "RET") 'notmuch-search-show-thread) (define-key map (kbd "M-RET") 'notmuch-tree-from-search-thread) (define-key map "Z" 'notmuch-tree-from-search-current-query) + (define-key map "U" 'notmuch-unthreaded-from-search-current-query) map) "Keymap for \"notmuch search\" buffers.") (fset 'notmuch-search-mode-map notmuch-search-mode-map) @@ -523,6 +524,11 @@ thread." (interactive) (notmuch-tree notmuch-search-query-string)) +(defun notmuch-unthreaded-from-search-current-query () + "Call notmuch tree with the current query" + (interactive) + (notmuch-unthreaded notmuch-search-query-string)) + (defun notmuch-tree-from-search-thread () "Show the selected thread with notmuch-tree" (interactive) From 98e9bda17f09e8ed45371b708477b6990f36a619 Mon Sep 17 00:00:00 2001 From: Mark Walters Date: Thu, 27 Feb 2020 17:16:52 +0000 Subject: [PATCH 099/427] notmuch-hello/jump: allow saved searches to specify unthreaded mode Saved searches in notmuch-hello and notmuch-jump can specify whether to use search mode or tree mode. This adds an option for them to specify unthreaded mode. --- emacs/notmuch-hello.el | 29 +++++++++++++++++++---------- emacs/notmuch-jump.el | 10 +++++++--- 2 files changed, 26 insertions(+), 13 deletions(-) diff --git a/emacs/notmuch-hello.el b/emacs/notmuch-hello.el index 858446df..ab6ee798 100644 --- a/emacs/notmuch-hello.el +++ b/emacs/notmuch-hello.el @@ -32,6 +32,9 @@ (declare-function notmuch-poll "notmuch" ()) (declare-function notmuch-tree "notmuch-tree" (&optional query query-context target buffer-name open-target unthreaded)) +(declare-function notmuch-unthreaded + (&optional query query-context target buffer-name open-target)) + (defun notmuch-saved-search-get (saved-search field) "Get FIELD from SAVED-SEARCH. @@ -99,7 +102,8 @@ searches so they still work in customize." (group :format "%v" :inline t (const :format "" :search-type) (choice :tag " Search Type" (const :tag "Search mode" nil) - (const :tag "Tree mode" tree)))))) + (const :tag "Tree mode" tree) + (const :tag "Unthreaded mode" unthreaded)))))) (defcustom notmuch-saved-searches `((:name "inbox" :query "tag:inbox" :key ,(kbd "i")) @@ -122,10 +126,10 @@ a plist. Supported properties are :sort-order Specify the sort order to be used for the search. Possible values are 'oldest-first 'newest-first or nil. Nil means use the default sort order. - :search-type Specify whether to run the search in search-mode - or tree mode. Set to 'tree to specify tree - mode, set to nil (or anything except tree) to - specify search mode. + :search-type Specify whether to run the search in search-mode, + tree mode or unthreaded mode. Set to 'tree to specify tree + mode, 'unthreaded to specify unthreaded mode, and set to nil + (or anything except tree and unthreaded) to specify search mode. Other accepted forms are a cons cell of the form (NAME . QUERY) or a list of the form (NAME QUERY COUNT-QUERY)." @@ -437,13 +441,18 @@ diagonal." append (notmuch-hello-reflect-generate-row ncols nrows row list)))) (defun notmuch-hello-widget-search (widget &rest ignore) - (if (widget-get widget :notmuch-search-type) - (notmuch-tree (widget-get widget - :notmuch-search-terms)) + (cond + ((eq (widget-get widget :notmuch-search-type) 'tree) + (notmuch-tree (widget-get widget + :notmuch-search-terms))) + ((eq (widget-get widget :notmuch-search-type) 'unthreaded) + (notmuch-unthreaded (widget-get widget + :notmuch-search-terms))) + (t (notmuch-search (widget-get widget :notmuch-search-terms) (widget-get widget - :notmuch-search-oldest-first)))) + :notmuch-search-oldest-first))))) (defun notmuch-saved-search-count (search) (car (process-lines notmuch-command "count" search))) @@ -579,7 +588,7 @@ with `notmuch-hello-query-counts'." (newest-first nil) (oldest-first t) (otherwise notmuch-search-oldest-first))) - (search-type (eq (plist-get elem :search-type) 'tree)) + (search-type (plist-get elem :search-type)) (msg-count (plist-get elem :count))) (widget-insert (format "%8s " (notmuch-hello-nice-number msg-count))) diff --git a/emacs/notmuch-jump.el b/emacs/notmuch-jump.el index 3e20b8c7..1cdf5b50 100644 --- a/emacs/notmuch-jump.el +++ b/emacs/notmuch-jump.el @@ -56,9 +56,13 @@ fast way to jump to a saved search from anywhere in Notmuch." (oldest-first t) (otherwise (default-value 'notmuch-search-oldest-first))))) (push (list key name - (if (eq (plist-get saved-search :search-type) 'tree) - `(lambda () (notmuch-tree ',query)) - `(lambda () (notmuch-search ',query ',oldest-first)))) + (cond + ((eq (plist-get saved-search :search-type) 'tree) + `(lambda () (notmuch-tree ',query))) + ((eq (plist-get saved-search :search-type) 'unthreaded) + `(lambda () (notmuch-unthreaded ',query))) + (t + `(lambda () (notmuch-search ',query ',oldest-first))))) action-map))))) (setq action-map (nreverse action-map)) From 7de3d77d2d31abaea78e70f4da9d9f2a5ef84a58 Mon Sep 17 00:00:00 2001 From: Daniel Kahn Gillmor Date: Thu, 19 Mar 2020 01:41:44 -0400 Subject: [PATCH 100/427] mime-node: rename decrypted_child to unwrapped_child When walking the MIME tree, we might need to extract a new MIME object. Thus far, we've only done it when decrypting multipart/encrypted messages, but PKCS#7 (RFC 8551, S/MIME) has several other transformations that warrant a comparable form of unwrapping. Make this member re-usable for PKCS#7 unwrappings as well as multipart/encrypted decryptions. This change is just a naming change, it has no effect on function. Signed-off-by: Daniel Kahn Gillmor --- mime-node.c | 10 +++++----- notmuch-client.h | 6 ++++-- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/mime-node.c b/mime-node.c index e531078c..2a823dfd 100644 --- a/mime-node.c +++ b/mime-node.c @@ -227,19 +227,19 @@ node_decrypt_and_verify (mime_node_t *node, GMimeObject *part) GMimeMultipartEncrypted *encrypteddata = GMIME_MULTIPART_ENCRYPTED (part); notmuch_message_t *message = NULL; - if (! node->decrypted_child) { + if (! node->unwrapped_child) { for (mime_node_t *parent = node; parent; parent = parent->parent) if (parent->envelope_file) { message = parent->envelope_file; break; } - node->decrypted_child = _notmuch_crypto_decrypt (&node->decrypt_attempted, + node->unwrapped_child = _notmuch_crypto_decrypt (&node->decrypt_attempted, node->ctx->crypto->decrypt, message, encrypteddata, &decrypt_result, &err); } - if (! node->decrypted_child) { + if (! node->unwrapped_child) { fprintf (stderr, "Failed to decrypt part: %s\n", err ? err->message : "no error explanation given"); goto DONE; @@ -380,8 +380,8 @@ mime_node_child (mime_node_t *parent, int child) return NULL; if (GMIME_IS_MULTIPART (parent->part)) { - if (child == GMIME_MULTIPART_ENCRYPTED_CONTENT && parent->decrypted_child) - sub = parent->decrypted_child; + if (child == GMIME_MULTIPART_ENCRYPTED_CONTENT && parent->unwrapped_child) + sub = parent->unwrapped_child; else sub = g_mime_multipart_get_part ( GMIME_MULTIPART (parent->part), child); diff --git a/notmuch-client.h b/notmuch-client.h index 74690054..89e15ba6 100644 --- a/notmuch-client.h +++ b/notmuch-client.h @@ -395,8 +395,10 @@ struct mime_node { struct mime_node_context *ctx; /* Internal: For successfully decrypted multipart parts, the - * decrypted part to substitute for the second child. */ - GMimeObject *decrypted_child; + * decrypted part to substitute for the second child; or, for + * PKCS#7 parts, the part returned after removing/processing the + * PKCS#7 transformation */ + GMimeObject *unwrapped_child; /* Internal: The next child for depth-first traversal and the part * number to assign it (or -1 if unknown). */ From 4dccb9928280bd60b79a662dc855187e7522ceba Mon Sep 17 00:00:00 2001 From: Daniel Kahn Gillmor Date: Thu, 19 Mar 2020 01:41:45 -0400 Subject: [PATCH 101/427] mime-node: Clean up unwrapped MIME parts correctly. Avoid a memory leak in the notmuch command line. gmime_multipart_encrypted_decrypt returns a GMimeObject marked by GMime as "transfer full", so we are supposed to clean up after it. When parsing a message, notmuch would leak one GMimeObject part per multipart/encrypted MIME layer. We clean it up by analogy with cleaning up the signature list associated with a MIME node. Signed-off-by: Daniel Kahn Gillmor --- mime-node.c | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/mime-node.c b/mime-node.c index 2a823dfd..ff6805bf 100644 --- a/mime-node.c +++ b/mime-node.c @@ -192,6 +192,26 @@ set_signature_list_destructor (mime_node_t *node) } } +/* Unwrapped MIME part destructor */ +static int +_unwrapped_child_free (GMimeObject **proxy) +{ + g_object_unref (*proxy); + return 0; +} + +/* Set up unwrapped MIME part destructor */ +static void +set_unwrapped_child_destructor (mime_node_t *node) +{ + GMimeObject **proxy = talloc (node, GMimeObject *); + + if (proxy) { + *proxy = node->unwrapped_child; + talloc_set_destructor (proxy, _unwrapped_child_free); + } +} + /* Verify a signed mime node */ static void node_verify (mime_node_t *node, GMimeObject *part) @@ -238,6 +258,8 @@ node_decrypt_and_verify (mime_node_t *node, GMimeObject *part) node->ctx->crypto->decrypt, message, encrypteddata, &decrypt_result, &err); + if (node->unwrapped_child) + set_unwrapped_child_destructor (node); } if (! node->unwrapped_child) { fprintf (stderr, "Failed to decrypt part: %s\n", From ea16b5ba850ae0cfad619ef2b0995a5b30ae3ebc Mon Sep 17 00:00:00 2001 From: Daniel Kahn Gillmor Date: Wed, 18 Mar 2020 13:11:53 -0400 Subject: [PATCH 102/427] emacs: avoid warning about notmuch-show-get-message-id MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Without this change, we see the following warning when compiling the elisp: ``` EMACS emacs/notmuch-crypto.elc In end of data: emacs/notmuch-crypto.el:266:1:Warning: the function ‘notmuch-show-get-message-id’ is not known to be defined. ``` Thanks to Örjan Ekeberg and David Edmondson for their followup about this. Signed-off-by: Daniel Kahn Gillmor --- emacs/notmuch-crypto.el | 1 + 1 file changed, 1 insertion(+) diff --git a/emacs/notmuch-crypto.el b/emacs/notmuch-crypto.el index 4035ee37..928de0bb 100644 --- a/emacs/notmuch-crypto.el +++ b/emacs/notmuch-crypto.el @@ -163,6 +163,7 @@ by user FROM." (recenter -1)))) (declare-function notmuch-show-refresh-view "notmuch-show" (&optional reset-state)) +(declare-function notmuch-show-get-message-id "notmuch-show" (&optional bare)) (defun notmuch-crypto--async-key-sentinel (process event) "When the user asks for a GPG key to be retrieved From 1fcf068e331b9b79e14f79c8b126711fc3d72cbb Mon Sep 17 00:00:00 2001 From: Greg Anders Date: Mon, 16 Mar 2020 12:27:42 -0600 Subject: [PATCH 103/427] Make notmuch-mutt script more portable The -D flag to install (used in the Makefile) is GNU-specific and does not work on BSD distributions (i.e. macOS). Likewise with the xargs -r flag. These changes use portable alternatives to these flags while preserving the exact behavior. --- contrib/notmuch-mutt/Makefile | 6 +++--- contrib/notmuch-mutt/notmuch-mutt | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/contrib/notmuch-mutt/Makefile b/contrib/notmuch-mutt/Makefile index 855438be..de933eaa 100644 --- a/contrib/notmuch-mutt/Makefile +++ b/contrib/notmuch-mutt/Makefile @@ -15,11 +15,11 @@ README.html: README markdown $< > $@ install: all - mkdir -p $(DESTDIR)$(prefix)/bin + mkdir -p $(DESTDIR)$(prefix)/bin $(DESTDIR)$(mandir)/man1 $(DESTDIR)$(sysconfdir)/Muttrc.d sed "1s|^#!.*|#! $(PERL_ABSOLUTE)|" < $(NAME) > $(DESTDIR)$(prefix)/bin/$(NAME) chmod 755 $(DESTDIR)$(prefix)/bin/$(NAME) - install -D -m 644 $(NAME).1 $(DESTDIR)$(mandir)/man1/$(NAME).1 - install -D -m 644 $(NAME).rc $(DESTDIR)$(sysconfdir)/Muttrc.d/$(NAME).rc + install -m 644 $(NAME).1 $(DESTDIR)$(mandir)/man1/ + install -m 644 $(NAME).rc $(DESTDIR)$(sysconfdir)/Muttrc.d/ clean: rm -f notmuch-mutt.1 README.html diff --git a/contrib/notmuch-mutt/notmuch-mutt b/contrib/notmuch-mutt/notmuch-mutt index 0e46a8c1..d33223bd 100755 --- a/contrib/notmuch-mutt/notmuch-mutt +++ b/contrib/notmuch-mutt/notmuch-mutt @@ -50,7 +50,7 @@ sub search($$$) { empty_maildir($maildir); system("notmuch search --output=files $dup_option $query" . " | sed -e 's: :\\\\ :g'" - . " | xargs -r -I searchoutput ln -s searchoutput $maildir/cur/"); + . " | while IFS= read -r searchoutput; do ln -s \$searchoutput $maildir/cur/; done"); } sub prompt($$) { From bd0b5abd5d6c6fe0c1a028192680f3a505a18ef2 Mon Sep 17 00:00:00 2001 From: Daniel Kahn Gillmor Date: Tue, 17 Mar 2020 12:28:26 -0400 Subject: [PATCH 104/427] doc: Drop obsolete MSCGEN_PATH, PERL_PATH from doxygen configuration Since doxygen 1.8.16, MSCGEN_PATH and PERL_PATH are obsolete: MSCGEN_PATH: https://github.com/doxygen/doxygen/commit/873e0ccfbe43ef98d4858beb08eef37631ae3cf6 PERL_PATH: https://github.com/doxygen/doxygen/commit/6d1535c38fe6bdaa2a00fff0e7e43774a740a4ce I don't think that the notmuch builds ever depended on them in the first place, and including them in the default config yields the following two warnings: ``` doxygen ./doc/doxygen.cfg warning: Tag 'PERL_PATH' at line 267 of file './doc/doxygen.cfg' has become obsolete. To avoid this warning please remove this line from your configuration file or upgrade it using "doxygen -u" warning: Tag 'MSCGEN_PATH' at line 272 of file './doc/doxygen.cfg' has become obsolete. To avoid this warning please remove this line from your configuration file or upgrade it using "doxygen -u" ``` Remove them to avoid the warnings. Signed-off-by: Daniel Kahn Gillmor --- doc/doxygen.cfg | 2 -- 1 file changed, 2 deletions(-) diff --git a/doc/doxygen.cfg b/doc/doxygen.cfg index 2ca15d41..a2c4fd07 100644 --- a/doc/doxygen.cfg +++ b/doc/doxygen.cfg @@ -264,12 +264,10 @@ GENERATE_TAGFILE = ALLEXTERNALS = NO EXTERNAL_GROUPS = NO EXTERNAL_PAGES = NO -PERL_PATH = /usr/bin/perl #--------------------------------------------------------------------------- # Configuration options related to the dot tool #--------------------------------------------------------------------------- CLASS_DIAGRAMS = NO -MSCGEN_PATH = HIDE_UNDOC_RELATIONS = YES HAVE_DOT = NO DOT_NUM_THREADS = 0 From 5bf5aa1cff84d54e72bfe9a92b46e166f2e7656e Mon Sep 17 00:00:00 2001 From: Daniel Kahn Gillmor Date: Mon, 23 Dec 2019 17:17:22 -0500 Subject: [PATCH 105/427] debian: Add packaging for python3-notmuch2 Ship a new debian package for the notmuch2 CFFI-based Python interface to notmuch. Unlike the notmuch python module, the new notmuch2 module is no longer arch-independent, because it builds and ships a shared object in addition to the python code. This patch encourages new downstream development to rely on notmuch2 instead of on notmuch, to get the benefits of the new module. I welcome any suggested improvements to this packaging, but it appears to me to be sufficient to get "import notmuch2" to work and do some basic tests. --- debian/control | 24 +++++++++++++++++++++++- debian/rules | 11 ++++++----- 2 files changed, 29 insertions(+), 6 deletions(-) diff --git a/debian/control b/debian/control index fb2b31c1..a1371fc8 100644 --- a/debian/control +++ b/debian/control @@ -98,6 +98,25 @@ Depends: libnotmuch5 (>= ${source:Version}), ${misc:Depends}, ${python3:Depends}, +Description: Python 3 legacy interface to the notmuch mail search and index library + Notmuch is a system for indexing, searching, reading, and tagging + large collections of email messages in maildir or mh format. It uses + the Xapian library to provide fast, full-text search with a very + convenient search syntax. + . + This package provides a legacy Python 3 interface to the notmuch + functionality, directly interfacing with a shared notmuch library. + . + New projects are encouraged to use python3-notmuch2 instead. + +Package: python3-notmuch2 +Architecture: any +Section: python +Depends: + libnotmuch5 (>= ${source:Version}), + ${misc:Depends}, + ${python3:Depends}, + ${shlibs:Depends}, Description: Python 3 interface to the notmuch mail search and index library Notmuch is a system for indexing, searching, reading, and tagging large collections of email messages in maildir or mh format. It uses @@ -105,7 +124,10 @@ Description: Python 3 interface to the notmuch mail search and index library convenient search syntax. . This package provides a Python 3 interface to the notmuch - functionality, directly interfacing with a shared notmuch library. + functionality using CFFI bindings, which interface with a shared + notmuch library. + . + This is the preferred way to use notmuch via Python. Package: ruby-notmuch Architecture: any diff --git a/debian/rules b/debian/rules index bf9d0bbd..8de49d0f 100755 --- a/debian/rules +++ b/debian/rules @@ -1,7 +1,5 @@ #!/usr/bin/make -f -export PYBUILD_NAME=notmuch - export DEB_BUILD_MAINT_OPTIONS = hardening=+all %: @@ -19,17 +17,20 @@ override_dh_auto_configure: override_dh_auto_build: dh_auto_build -- V=1 - dh_auto_build --buildsystem=pybuild --sourcedirectory bindings/python + PYBUILD_NAME=notmuch dh_auto_build --buildsystem=pybuild --sourcedirectory bindings/python + PYBUILD_NAME=notmuch2 dh_auto_build --buildsystem=pybuild --sourcedirectory bindings/python-cffi $(MAKE) -C contrib/notmuch-mutt override_dh_auto_clean: dh_auto_clean - dh_auto_clean --buildsystem=pybuild --sourcedirectory bindings/python + PYBUILD_NAME=notmuch dh_auto_clean --buildsystem=pybuild --sourcedirectory bindings/python + PYBUILD_NAME=notmuch2 dh_auto_clean --buildsystem=pybuild --sourcedirectory bindings/python-cffi dh_auto_clean --sourcedirectory bindings/ruby $(MAKE) -C contrib/notmuch-mutt clean override_dh_auto_install: dh_auto_install - dh_auto_install --buildsystem=pybuild --sourcedirectory bindings/python + PYBUILD_NAME=notmuch dh_auto_install --buildsystem=pybuild --sourcedirectory bindings/python + PYBUILD_NAME=notmuch2 dh_auto_install --buildsystem=pybuild --sourcedirectory bindings/python-cffi $(MAKE) -C contrib/notmuch-mutt DESTDIR=$(CURDIR)/debian/tmp install dh_auto_install --sourcedirectory bindings/ruby From 144cf30e2c71d57795b5fbdec10af0ef73aa01ff Mon Sep 17 00:00:00 2001 From: Daniel Kahn Gillmor Date: Thu, 8 Feb 2018 23:32:11 -0500 Subject: [PATCH 106/427] nmbug: explicitly prefer python3 nmbug and notmuch-report are developer tools. It's 2018, and all developers should have python3 available. Signed-off-by: Daniel Kahn Gillmor --- devel/nmbug/nmbug | 2 +- devel/nmbug/notmuch-report | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/devel/nmbug/nmbug b/devel/nmbug/nmbug index c35dd75d..043c1863 100755 --- a/devel/nmbug/nmbug +++ b/devel/nmbug/nmbug @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 # # Copyright (c) 2011-2014 David Bremner # W. Trevor King diff --git a/devel/nmbug/notmuch-report b/devel/nmbug/notmuch-report index eaceb2ce..18a0bc70 100755 --- a/devel/nmbug/notmuch-report +++ b/devel/nmbug/notmuch-report @@ -1,10 +1,9 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 # # Copyright (c) 2011-2012 David Bremner # # dependencies -# - python 2.6 for json -# - argparse; either python 2.7, or install separately +# - python3 or python2.7 # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by From 8eec15a90338afe0f1933466a90e53767a0a4ccb Mon Sep 17 00:00:00 2001 From: William Casarin Date: Sat, 4 Apr 2020 13:41:31 -0700 Subject: [PATCH 107/427] emacs/tree: return true if a thread was found in next-thread This will allow us to pop back to parent buffers when there are no more threads to jump to. Signed-off-by: William Casarin --- emacs/notmuch-tree.el | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/emacs/notmuch-tree.el b/emacs/notmuch-tree.el index 9a83292c..1bcf6cf9 100644 --- a/emacs/notmuch-tree.el +++ b/emacs/notmuch-tree.el @@ -679,10 +679,13 @@ message will be \"unarchived\", i.e. the tag changes in (notmuch-tree-thread-top)) (defun notmuch-tree-next-thread () + "Get the next thread in the current tree. Returns t if a thread was +found or nil if not." (interactive) (forward-line 1) (while (not (or (notmuch-tree-get-prop :first) (eobp))) - (forward-line 1))) + (forward-line 1)) + (not (eobp))) (defun notmuch-tree-thread-mapcar (function) "Iterate through all messages in the current thread From c0868d959d38d1178acd8b1cba14a0944ea0c2a4 Mon Sep 17 00:00:00 2001 From: William Casarin Date: Sat, 4 Apr 2020 13:41:32 -0700 Subject: [PATCH 108/427] emacs/tree: add notmuch-tree-goto-matching-message This function captures some common logic when jumping to matching messages in notmuch-tree mode. We also add a new return value (t or nil), that indicates if there was a next matching message in the thread to show. Signed-off-by: William Casarin --- emacs/notmuch-tree.el | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/emacs/notmuch-tree.el b/emacs/notmuch-tree.el index 1bcf6cf9..7f68efba 100644 --- a/emacs/notmuch-tree.el +++ b/emacs/notmuch-tree.el @@ -631,12 +631,23 @@ message will be \"unarchived\", i.e. the tag changes in (when (window-live-p notmuch-tree-message-window) (notmuch-tree-show-message-in))) +(defun notmuch-tree-goto-matching-message (&optional prev) + "Move to the next or previous matching message. + +Returns t if there was a next matching message in the thread to show, +nil otherwise." + (let ((dir (if prev -1 nil)) + (eobfn (if prev #'bobp #'eobp))) + (while (and (not (funcall eobfn)) + (not (notmuch-tree-get-match))) + (forward-line dir)) + (not (funcall eobfn)))) + (defun notmuch-tree-prev-matching-message () "Move to previous matching message." (interactive) (forward-line -1) - (while (and (not (bobp)) (not (notmuch-tree-get-match))) - (forward-line -1)) + (notmuch-tree-goto-matching-message t) (when (window-live-p notmuch-tree-message-window) (notmuch-tree-show-message-in))) @@ -644,8 +655,7 @@ message will be \"unarchived\", i.e. the tag changes in "Move to next matching message." (interactive) (forward-line) - (while (and (not (eobp)) (not (notmuch-tree-get-match))) - (forward-line)) + (notmuch-tree-goto-matching-message) (when (window-live-p notmuch-tree-message-window) (notmuch-tree-show-message-in))) From d9888b301c6e619daaa6e507c343af0ffc726b16 Mon Sep 17 00:00:00 2001 From: William Casarin Date: Sat, 4 Apr 2020 13:41:33 -0700 Subject: [PATCH 109/427] emacs/tree: add notmuch-tree-matching-message This functions removes some duplicate logic between notmuch-tree-{next,prev}-matching-message We do this because we will be adding some additional logic similar to the notmuch-show-next-open-message function, and it will help if this logic is all in one place. Signed-off-by: William Casarin --- emacs/notmuch-tree.el | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/emacs/notmuch-tree.el b/emacs/notmuch-tree.el index 7f68efba..e6a6e67f 100644 --- a/emacs/notmuch-tree.el +++ b/emacs/notmuch-tree.el @@ -643,21 +643,23 @@ nil otherwise." (forward-line dir)) (not (funcall eobfn)))) +(defun notmuch-tree-matching-message (&optional prev) + "Move to the next or previous matching message" + (interactive "P") + (forward-line (if prev -1 nil)) + (notmuch-tree-goto-matching-message prev) + (when (window-live-p notmuch-tree-message-window) + (notmuch-tree-show-message-in))) + (defun notmuch-tree-prev-matching-message () "Move to previous matching message." (interactive) - (forward-line -1) - (notmuch-tree-goto-matching-message t) - (when (window-live-p notmuch-tree-message-window) - (notmuch-tree-show-message-in))) + (notmuch-tree-matching-message t)) (defun notmuch-tree-next-matching-message () "Move to next matching message." (interactive) - (forward-line) - (notmuch-tree-goto-matching-message) - (when (window-live-p notmuch-tree-message-window) - (notmuch-tree-show-message-in))) + (notmuch-tree-matching-message)) (defun notmuch-tree-refresh-view (&optional view) "Refresh view." From c6f43306ccb35a5e885b186fbf87394d2f036813 Mon Sep 17 00:00:00 2001 From: William Casarin Date: Sat, 4 Apr 2020 13:41:34 -0700 Subject: [PATCH 110/427] emacs/tree: add kill-both prefix argument to notmuch-tree-quit This allows us to close both windows at the same time. Signed-off-by: William Casarin --- emacs/notmuch-tree.el | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/emacs/notmuch-tree.el b/emacs/notmuch-tree.el index e6a6e67f..b405e9e3 100644 --- a/emacs/notmuch-tree.el +++ b/emacs/notmuch-tree.el @@ -584,10 +584,10 @@ Shows in split pane or whole window according to value of (when (notmuch-tree-scroll-message-window) (notmuch-tree-next-matching-message))) -(defun notmuch-tree-quit () +(defun notmuch-tree-quit (&optional kill-both) "Close the split view or exit tree." - (interactive) - (unless (notmuch-tree-close-message-window) + (interactive "P") + (when (or (not (notmuch-tree-close-message-window)) kill-both) (kill-buffer (current-buffer)))) (defun notmuch-tree-close-message-window () From 1abe5a0c5b18bdbc0b25f3d138356ee73fe961d1 Mon Sep 17 00:00:00 2001 From: William Casarin Date: Sat, 4 Apr 2020 13:41:35 -0700 Subject: [PATCH 111/427] emacs/tree: add notmuch-tree-archive-message-than-next-or-exit This is the notmuch-tree version of notmuch-show-archive-message-than-next-or-exit. Signed-off-by: William Casarin --- emacs/notmuch-tree.el | 30 ++++++++++++++++++++---------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/emacs/notmuch-tree.el b/emacs/notmuch-tree.el index b405e9e3..02bdd1ee 100644 --- a/emacs/notmuch-tree.el +++ b/emacs/notmuch-tree.el @@ -617,6 +617,15 @@ message will be \"unarchived\", i.e. the tag changes in (notmuch-tree-archive-message unarchive) (notmuch-tree-next-matching-message)) +(defun notmuch-tree-archive-message-then-next-or-exit () + "Archive current message, then show next open message in current thread. + +If at the last open message in the current thread, then exit back +to search results." + (interactive) + (notmuch-tree-archive-message) + (notmuch-tree-next-matching-message t)) + (defun notmuch-tree-next-message () "Move to next message." (interactive) @@ -643,23 +652,24 @@ nil otherwise." (forward-line dir)) (not (funcall eobfn)))) -(defun notmuch-tree-matching-message (&optional prev) +(defun notmuch-tree-matching-message (&optional prev pop-at-end) "Move to the next or previous matching message" (interactive "P") (forward-line (if prev -1 nil)) - (notmuch-tree-goto-matching-message prev) - (when (window-live-p notmuch-tree-message-window) - (notmuch-tree-show-message-in))) + (if (and (not (notmuch-tree-goto-matching-message prev)) pop-at-end) + (notmuch-tree-quit pop-at-end) + (when (window-live-p notmuch-tree-message-window) + (notmuch-tree-show-message-in)))) -(defun notmuch-tree-prev-matching-message () +(defun notmuch-tree-prev-matching-message (&optional pop-at-end) "Move to previous matching message." - (interactive) - (notmuch-tree-matching-message t)) + (interactive "P") + (notmuch-tree-matching-message t pop-at-end)) -(defun notmuch-tree-next-matching-message () +(defun notmuch-tree-next-matching-message (&optional pop-at-end) "Move to next matching message." - (interactive) - (notmuch-tree-matching-message)) + (interactive "P") + (notmuch-tree-matching-message nil pop-at-end)) (defun notmuch-tree-refresh-view (&optional view) "Refresh view." From 86f3cc265a89d939facb3d29fec9b2d451500836 Mon Sep 17 00:00:00 2001 From: William Casarin Date: Sat, 4 Apr 2020 13:41:41 -0700 Subject: [PATCH 112/427] emacs/tree: add notmuch-tree-archive-thread-then-exit This is the notmuch-tree version of notmuch-show-archive-thread-then-exit Signed-off-by: William Casarin --- emacs/notmuch-tree.el | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/emacs/notmuch-tree.el b/emacs/notmuch-tree.el index 02bdd1ee..470cd517 100644 --- a/emacs/notmuch-tree.el +++ b/emacs/notmuch-tree.el @@ -617,6 +617,12 @@ message will be \"unarchived\", i.e. the tag changes in (notmuch-tree-archive-message unarchive) (notmuch-tree-next-matching-message)) +(defun notmuch-tree-archive-thread-then-exit () + "Archive all messages in the current buffer, then exit notmuch-tree." + (interactive) + (notmuch-tree-archive-thread) + (notmuch-tree-quit t)) + (defun notmuch-tree-archive-message-then-next-or-exit () "Archive current message, then show next open message in current thread. From 374217a01a5376c225af92c1dfc3f1f4d16d2011 Mon Sep 17 00:00:00 2001 From: William Casarin Date: Sat, 4 Apr 2020 13:41:42 -0700 Subject: [PATCH 113/427] emacs/tree: add x/X bindings Add x and X binds to notmuch-tree for functionally that we have in notmuch-show. The notmuch-tree-quit binding is somewhat redundant, since it is handled by notmuch-bury-or-kill-this-buffer which is bound to q. Signed-off-by: William Casarin --- emacs/notmuch-tree.el | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/emacs/notmuch-tree.el b/emacs/notmuch-tree.el index 470cd517..e5c23de2 100644 --- a/emacs/notmuch-tree.el +++ b/emacs/notmuch-tree.el @@ -319,7 +319,8 @@ FUNC." ;; The main tree view bindings (define-key map (kbd "RET") 'notmuch-tree-show-message) (define-key map [mouse-1] 'notmuch-tree-show-message) - (define-key map "x" 'notmuch-tree-quit) + (define-key map "x" 'notmuch-tree-archive-message-then-next-or-exit) + (define-key map "X" 'notmuch-tree-archive-thread-then-exit) (define-key map "A" 'notmuch-tree-archive-thread) (define-key map "a" 'notmuch-tree-archive-message-then-next) (define-key map "z" 'notmuch-tree-to-tree) From d50f41c0fd0bbd2ca2b364f49deaea8be63dff3c Mon Sep 17 00:00:00 2001 From: David Bremner Date: Sun, 12 Apr 2020 14:30:12 -0300 Subject: [PATCH 114/427] test: add known_broken test for dumping large stored queries 'qsx' reported a bug on #notmuch with notmuch-dump and large stored queries. This test will pass (on my machine) if the value of `repeat' is made smaller. Reported-By: Thomas Schneider --- test/T600-named-queries.sh | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/test/T600-named-queries.sh b/test/T600-named-queries.sh index abaee3b7..852f7530 100755 --- a/test/T600-named-queries.sh +++ b/test/T600-named-queries.sh @@ -36,6 +36,22 @@ cat< QUERIES.BEFORE EOF test_expect_equal_file QUERIES.BEFORE OUTPUT +test_begin_subtest 'dumping large queries' +test_subtest_known_broken +# This value is just large enough to trigger a limitation of gzprintf +# to 8191 bytes in total (by default). +repeat=1329 +notmuch config set query.big "$(seq -s' ' $repeat)" +notmuch dump --include=config > OUTPUT +notmuch config set query.big '' +printf "#notmuch-dump batch-tag:3 config\n#@ query.big " > EXPECTED +seq -s'%20' $repeat >> EXPECTED +cat <> EXPECTED +#@ query.test date%3a2009-11-18..2009-11-18%20and%20tag%3aunread +#@ query.test2 query%3atest%20and%20subject%3aMaildir +EOF +test_expect_equal_file EXPECTED OUTPUT + test_begin_subtest "delete named queries" notmuch dump > BEFORE notmuch config set query.test From 2c1f783f5f4ad28d89f2e83d7253bae7bba98440 Mon Sep 17 00:00:00 2001 From: David Bremner Date: Sun, 12 Apr 2020 20:00:31 -0300 Subject: [PATCH 115/427] don't pass NULL as second parameter to gzerror Although (as of 1.2.11) zlib checks this parameter before writing to it, the docs don't promise to keep doing so, so be safe. --- notmuch-dump.c | 6 +++--- notmuch-restore.c | 2 +- util/zlib-extra.c | 2 +- util/zlib-extra.h | 5 +++++ 4 files changed, 10 insertions(+), 5 deletions(-) diff --git a/notmuch-dump.c b/notmuch-dump.c index 65e02639..af346ba2 100644 --- a/notmuch-dump.c +++ b/notmuch-dump.c @@ -21,7 +21,7 @@ #include "notmuch-client.h" #include "hex-escape.h" #include "string-util.h" -#include +#include "zlib-extra.h" static int database_dump_config (notmuch_database_t *notmuch, gzFile output) @@ -316,7 +316,7 @@ notmuch_database_dump (notmuch_database_t *notmuch, ret = gzflush (output, Z_FINISH); if (ret) { - fprintf (stderr, "Error flushing output: %s\n", gzerror (output, NULL)); + fprintf (stderr, "Error flushing output: %s\n", gzerror_str (output)); goto DONE; } @@ -332,7 +332,7 @@ notmuch_database_dump (notmuch_database_t *notmuch, ret = gzclose_w (output); if (ret) { fprintf (stderr, "Error closing %s: %s\n", name_for_error, - gzerror (output, NULL)); + gzerror_str (output)); ret = EXIT_FAILURE; output = NULL; goto DONE; diff --git a/notmuch-restore.c b/notmuch-restore.c index 4b509d95..9a8b7fb5 100644 --- a/notmuch-restore.c +++ b/notmuch-restore.c @@ -450,7 +450,7 @@ notmuch_restore_command (notmuch_config_t *config, int argc, char *argv[]) if (input && gzclose_r (input)) { fprintf (stderr, "Error closing %s: %s\n", - name_for_error, gzerror (input, NULL)); + name_for_error, gzerror_str (input)); ret = EXIT_FAILURE; } diff --git a/util/zlib-extra.c b/util/zlib-extra.c index f691cccf..623f6d62 100644 --- a/util/zlib-extra.c +++ b/util/zlib-extra.c @@ -80,7 +80,7 @@ const char * gz_error_string (util_status_t status, gzFile file) { if (status == UTIL_GZERROR) - return gzerror (file, NULL); + return gzerror_str (file); else return util_error_string (status); } diff --git a/util/zlib-extra.h b/util/zlib-extra.h index 209fa998..296dc914 100644 --- a/util/zlib-extra.h +++ b/util/zlib-extra.h @@ -27,6 +27,11 @@ gz_getline (void *ctx, char **lineptr, ssize_t *bytes_read, gzFile stream); const char * gz_error_string (util_status_t status, gzFile stream); +/* Call gzerror with a dummy errno argument, the docs don't promise to + * support the NULL case */ +inline const char * +gzerror_str(gzFile file) { int dummy; return gzerror (file, &dummy); } + #ifdef __cplusplus } #endif From 02112728433cb08ea2501d937a2ca3cb73a0d85b Mon Sep 17 00:00:00 2001 From: David Bremner Date: Sun, 12 Apr 2020 20:17:20 -0300 Subject: [PATCH 116/427] status: add print_status_gzbytes This is in the client code, rather than libnotmuch_util, because it prints to stderr. Also it in pretends to generate notmuch status codes. --- notmuch-client.h | 8 +++++++- status.c | 14 ++++++++++++++ 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/notmuch-client.h b/notmuch-client.h index 89e15ba6..467e1d84 100644 --- a/notmuch-client.h +++ b/notmuch-client.h @@ -49,6 +49,7 @@ #include #include #include +#include #include "talloc-extra.h" #include "crypto.h" @@ -469,7 +470,7 @@ notmuch_database_dump (notmuch_database_t *notmuch, dump_include_t include, bool gzip_output); -/* If status is non-zero (i.e. error) print appropriate +/* If status indicates error print appropriate * messages to stderr. */ @@ -491,6 +492,11 @@ print_status_database (const char *loc, int status_to_exit (notmuch_status_t status); +notmuch_status_t +print_status_gzbytes (const char *loc, + gzFile file, + int bytes); + #include "command-line-arguments.h" extern const char *notmuch_requested_db_uuid; diff --git a/status.c b/status.c index d0ae47f4..09d82a17 100644 --- a/status.c +++ b/status.c @@ -72,3 +72,17 @@ status_to_exit (notmuch_status_t status) return EXIT_FAILURE; } } + +notmuch_status_t +print_status_gzbytes (const char *loc, gzFile file, int bytes) +{ + if (bytes <= 0) { + int errnum; + const char *errstr = gzerror (file, &errnum); + fprintf (stderr, "%s: zlib error %s (%d)\n", loc, errstr, errnum); + return NOTMUCH_STATUS_FILE_ERROR; + } else { + return NOTMUCH_STATUS_SUCCESS; + } +} + From 0d0918f604c5da419c08e3bfae005a4820395997 Mon Sep 17 00:00:00 2001 From: David Bremner Date: Sun, 12 Apr 2020 22:39:11 -0300 Subject: [PATCH 117/427] cli/dump: define GZPRINTF macro and use it in place of gzprintf This will at least catch errors, and can be replaced with more sophisticated error handling where appropriate. --- notmuch-client.h | 4 ++++ notmuch-dump.c | 24 ++++++++++++------------ 2 files changed, 16 insertions(+), 12 deletions(-) diff --git a/notmuch-client.h b/notmuch-client.h index 467e1d84..6cb81820 100644 --- a/notmuch-client.h +++ b/notmuch-client.h @@ -497,6 +497,10 @@ print_status_gzbytes (const char *loc, gzFile file, int bytes); +/* the __location__ macro is defined in talloc.h */ +#define ASSERT_GZBYTES(file, bytes) ((print_status_gzbytes (__location__, file, bytes)) ? exit (1) : 0) +#define GZPRINTF(file, fmt, ...) ASSERT_GZBYTES (file, gzprintf (file, fmt, ##__VA_ARGS__)); + #include "command-line-arguments.h" extern const char *notmuch_requested_db_uuid; diff --git a/notmuch-dump.c b/notmuch-dump.c index af346ba2..6c5c1433 100644 --- a/notmuch-dump.c +++ b/notmuch-dump.c @@ -42,7 +42,7 @@ database_dump_config (notmuch_database_t *notmuch, gzFile output) notmuch_config_list_key (list)); goto DONE; } - gzprintf (output, "#@ %s", buffer); + GZPRINTF (output, "#@ %s", buffer); if (hex_encode (notmuch, notmuch_config_list_value (list), &buffer, &buffer_size) != HEX_SUCCESS) { @@ -51,7 +51,7 @@ database_dump_config (notmuch_database_t *notmuch, gzFile output) goto DONE; } - gzprintf (output, " %s\n", buffer); + GZPRINTF (output, " %s\n", buffer); } ret = EXIT_SUCCESS; @@ -71,7 +71,7 @@ print_dump_header (gzFile output, int output_format, int include) { const char *sep = ""; - gzprintf (output, "#notmuch-dump %s:%d ", + GZPRINTF (output, "#notmuch-dump %s:%d ", (output_format == DUMP_FORMAT_SUP) ? "sup" : "batch-tag", NOTMUCH_DUMP_VERSION); @@ -80,11 +80,11 @@ print_dump_header (gzFile output, int output_format, int include) sep = ","; } if (include & DUMP_INCLUDE_PROPERTIES) { - gzprintf (output, "%sproperties", sep); + GZPRINTF (output, "%sproperties", sep); sep = ","; } if (include & DUMP_INCLUDE_TAGS) { - gzprintf (output, "%stags", sep); + GZPRINTF (output, "%stags", sep); } gzputs (output, "\n"); } @@ -115,7 +115,7 @@ dump_properties_message (void *ctx, fprintf (stderr, "Error: failed to hex-encode message-id %s\n", message_id); return 1; } - gzprintf (output, "#= %s", *buffer_p); + GZPRINTF (output, "#= %s", *buffer_p); first = false; } @@ -126,18 +126,18 @@ dump_properties_message (void *ctx, fprintf (stderr, "Error: failed to hex-encode key %s\n", key); return 1; } - gzprintf (output, " %s", *buffer_p); + GZPRINTF (output, " %s", *buffer_p); if (hex_encode (ctx, val, buffer_p, size_p) != HEX_SUCCESS) { fprintf (stderr, "Error: failed to hex-encode value %s\n", val); return 1; } - gzprintf (output, "=%s", *buffer_p); + GZPRINTF (output, "=%s", *buffer_p); } notmuch_message_properties_destroy (list); if (! first) - gzprintf (output, "\n", *buffer_p); + GZPRINTF (output, "\n", *buffer_p); return 0; } @@ -165,7 +165,7 @@ dump_tags_message (void *ctx, } if (output_format == DUMP_FORMAT_SUP) { - gzprintf (output, "%s (", message_id); + GZPRINTF (output, "%s (", message_id); } for (notmuch_tags_t *tags = notmuch_message_get_tags (message); @@ -187,7 +187,7 @@ dump_tags_message (void *ctx, tag_str); return EXIT_FAILURE; } - gzprintf (output, "+%s", *buffer_p); + GZPRINTF (output, "+%s", *buffer_p); } } @@ -200,7 +200,7 @@ dump_tags_message (void *ctx, message_id, strerror (errno)); return EXIT_FAILURE; } - gzprintf (output, " -- %s\n", *buffer_p); + GZPRINTF (output, " -- %s\n", *buffer_p); } return EXIT_SUCCESS; } From 24ff33082ab1a35c7a31835fba80ade145596422 Mon Sep 17 00:00:00 2001 From: David Bremner Date: Sun, 12 Apr 2020 22:59:12 -0300 Subject: [PATCH 118/427] cli/dump: define GZPUTS and use it in notmuch-dump Similarly to GZPRINTF, this is a drop in replacement that can be improved where needd. --- notmuch-client.h | 1 + notmuch-dump.c | 10 +++++----- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/notmuch-client.h b/notmuch-client.h index 6cb81820..ebd43e8d 100644 --- a/notmuch-client.h +++ b/notmuch-client.h @@ -500,6 +500,7 @@ print_status_gzbytes (const char *loc, /* the __location__ macro is defined in talloc.h */ #define ASSERT_GZBYTES(file, bytes) ((print_status_gzbytes (__location__, file, bytes)) ? exit (1) : 0) #define GZPRINTF(file, fmt, ...) ASSERT_GZBYTES (file, gzprintf (file, fmt, ##__VA_ARGS__)); +#define GZPUTS(file, str) ASSERT_GZBYTES(file, gzputs (file, str)); #include "command-line-arguments.h" diff --git a/notmuch-dump.c b/notmuch-dump.c index 6c5c1433..52a88283 100644 --- a/notmuch-dump.c +++ b/notmuch-dump.c @@ -76,7 +76,7 @@ print_dump_header (gzFile output, int output_format, int include) NOTMUCH_DUMP_VERSION); if (include & DUMP_INCLUDE_CONFIG) { - gzputs (output, "config"); + GZPUTS (output, "config"); sep = ","; } if (include & DUMP_INCLUDE_PROPERTIES) { @@ -86,7 +86,7 @@ print_dump_header (gzFile output, int output_format, int include) if (include & DUMP_INCLUDE_TAGS) { GZPRINTF (output, "%stags", sep); } - gzputs (output, "\n"); + GZPUTS (output, "\n"); } static int @@ -174,12 +174,12 @@ dump_tags_message (void *ctx, const char *tag_str = notmuch_tags_get (tags); if (! first) - gzputs (output, " "); + GZPUTS (output, " "); first = 0; if (output_format == DUMP_FORMAT_SUP) { - gzputs (output, tag_str); + GZPUTS (output, tag_str); } else { if (hex_encode (ctx, tag_str, buffer_p, size_p) != HEX_SUCCESS) { @@ -192,7 +192,7 @@ dump_tags_message (void *ctx, } if (output_format == DUMP_FORMAT_SUP) { - gzputs (output, ")\n"); + GZPUTS (output, ")\n"); } else { if (make_boolean_term (ctx, "id", message_id, buffer_p, size_p)) { From 690e36bacd5e53c45775586024ca70f069ca68ad Mon Sep 17 00:00:00 2001 From: David Bremner Date: Sun, 12 Apr 2020 23:01:38 -0300 Subject: [PATCH 119/427] cli/dump: replace use of gzprintf with gzputs for config values These can be large, and hit buffer limitations of gzprintf. --- notmuch-dump.c | 4 +++- test/T600-named-queries.sh | 1 - 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/notmuch-dump.c b/notmuch-dump.c index 52a88283..887ef7f0 100644 --- a/notmuch-dump.c +++ b/notmuch-dump.c @@ -51,7 +51,9 @@ database_dump_config (notmuch_database_t *notmuch, gzFile output) goto DONE; } - GZPRINTF (output, " %s\n", buffer); + GZPUTS (output, " "); + GZPUTS (output, buffer); + GZPUTS (output, "\n"); } ret = EXIT_SUCCESS; diff --git a/test/T600-named-queries.sh b/test/T600-named-queries.sh index 852f7530..421a11d4 100755 --- a/test/T600-named-queries.sh +++ b/test/T600-named-queries.sh @@ -37,7 +37,6 @@ EOF test_expect_equal_file QUERIES.BEFORE OUTPUT test_begin_subtest 'dumping large queries' -test_subtest_known_broken # This value is just large enough to trigger a limitation of gzprintf # to 8191 bytes in total (by default). repeat=1329 From f28e0a93379754456cf02830efcea499c6c2fc6f Mon Sep 17 00:00:00 2001 From: Keegan Carruthers-Smith Date: Mon, 13 Apr 2020 22:10:50 +0200 Subject: [PATCH 120/427] emacs: introduce notmuch-search-by-tag This is like notmuch-search-filter-by-tag, but creates a new search rather than filtering the current search. We add this to notmuch-common-keymap since this can be used by many contexts. We bind to the key "t", which is the same key used by notmuch-search-filter-by-tag in notmuch-search-mode-map. This is done intentionally since the keybinding for notmuch-search-mode-map can be seen as a specialization of creating a new search. This change was motivated for use in "notmuch-hello". It is a more convenient way to search a tag than expanding the list of all tags. I also noticed many saved searches people use are simply tags. --- devel/emacs-keybindings.org | 2 +- emacs/notmuch-lib.el | 1 + emacs/notmuch.el | 6 ++++++ 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/devel/emacs-keybindings.org b/devel/emacs-keybindings.org index 464b9467..65dfe0eb 100644 --- a/devel/emacs-keybindings.org +++ b/devel/emacs-keybindings.org @@ -20,7 +20,7 @@ | q | notmuch-bury-or-kill-this-buffer | notmuch-bury-or-kill-this-buffer | notmuch-bury-or-kill-this-buffer | | r | notmuch-search-reply-to-thread-sender | notmuch-show-reply-sender | notmuch-show-reply-sender | | s | notmuch-search | notmuch-search | notmuch-search | -| t | notmuch-search-filter-by-tag | toggle-truncate-lines | | +| t | notmuch-search-filter-by-tag | toggle-truncate-lines | notmuch-search-by-tag | | u | | | | | v | | | notmuch-show-view-all-mime-parts | | w | | notmuch-show-save-attachments | notmuch-show-save-attachments | diff --git a/emacs/notmuch-lib.el b/emacs/notmuch-lib.el index 73b165e4..e085a06b 100644 --- a/emacs/notmuch-lib.el +++ b/emacs/notmuch-lib.el @@ -153,6 +153,7 @@ For example, if you wanted to remove an \"inbox\" tag and add an (define-key map "?" 'notmuch-help) (define-key map "q" 'notmuch-bury-or-kill-this-buffer) (define-key map "s" 'notmuch-search) + (define-key map "t" 'notmuch-search-by-tag) (define-key map "z" 'notmuch-tree) (define-key map "u" 'notmuch-unthreaded) (define-key map "m" 'notmuch-mua-new-mail) diff --git a/emacs/notmuch.el b/emacs/notmuch.el index f4789b4f..f5f03244 100644 --- a/emacs/notmuch.el +++ b/emacs/notmuch.el @@ -1077,6 +1077,12 @@ current search results AND that are tagged with the given tag." (list (notmuch-select-tag-with-completion "Filter by tag: " notmuch-search-query-string))) (notmuch-search (concat notmuch-search-query-string " and tag:" tag) notmuch-search-oldest-first)) +(defun notmuch-search-by-tag (tag) + "Display threads matching TAG in a notmuch-search buffer." + (interactive + (list (notmuch-select-tag-with-completion "Notmuch search tag: "))) + (notmuch-search (concat "tag:" tag))) + ;;;###autoload (defun notmuch () "Run notmuch and display saved searches, known tags, etc." From e083987338ab058fff826242c0a7b533f5a453ba Mon Sep 17 00:00:00 2001 From: Keegan Carruthers-Smith Date: Mon, 13 Apr 2020 21:58:19 +0200 Subject: [PATCH 121/427] emacs: use def instead of initial-input for notmuch-show-browse-urls This is the non-deprecated way to use completing-read. Additionally the old use was broken when using ivy for completing-read. For user's using completing-read-default they won't see the default URL now, but if they hit enter it will be visited. Alternatively they can select it with M-n. From the completing-read documentation for initial-input: This feature is deprecated--it is best to pass nil for INITIAL-INPUT and supply the default value DEF instead. The user can yank the default value into the minibuffer easily using M-n. Additionally collection is now all urls, rather than all but the first. I'm not sure why "(cdr urls)" was previously done. --- emacs/notmuch-show.el | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/emacs/notmuch-show.el b/emacs/notmuch-show.el index 214e279f..079281c3 100644 --- a/emacs/notmuch-show.el +++ b/emacs/notmuch-show.el @@ -2559,7 +2559,7 @@ browsing." (prompt (if kill "Copy URL to kill ring: " "Browse URL: ")) (fn (if kill #'kill-new #'browse-url))) (if urls - (funcall fn (completing-read prompt (cdr urls) nil nil (car urls))) + (funcall fn (completing-read prompt urls nil nil nil nil (car urls))) (message "No URLs found.")))) (provide 'notmuch-show) From 8c718a8190eb0820b5e3891b4643c99da50d0c08 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Ta=C3=AFbi?= Date: Tue, 14 Apr 2020 19:36:27 +0200 Subject: [PATCH 122/427] cli/restore: gzerror() after gzclose_r() is a use after free Calling gzerror() (indirectly via gzerror_str()) after gzclose_r is a use after free, according to zlib's manual. amended by db: tidied commit message --- notmuch-restore.c | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/notmuch-restore.c b/notmuch-restore.c index 9a8b7fb5..e2dc3d45 100644 --- a/notmuch-restore.c +++ b/notmuch-restore.c @@ -237,6 +237,7 @@ notmuch_restore_command (notmuch_config_t *config, int argc, char *argv[]) int opt_index; int include = 0; int input_format = DUMP_FORMAT_AUTO; + int errnum; if (notmuch_database_open (notmuch_config_get_database_path (config), NOTMUCH_DATABASE_MODE_READ_WRITE, ¬much)) @@ -448,10 +449,13 @@ notmuch_restore_command (notmuch_config_t *config, int argc, char *argv[]) if (notmuch) notmuch_database_destroy (notmuch); - if (input && gzclose_r (input)) { - fprintf (stderr, "Error closing %s: %s\n", - name_for_error, gzerror_str (input)); - ret = EXIT_FAILURE; + if (input) { + errnum = gzclose_r (input); + if (errnum) { + fprintf (stderr, "Error closing %s: %d\n", + name_for_error, errnum); + ret = EXIT_FAILURE; + } } return ret ? EXIT_FAILURE : EXIT_SUCCESS; From b4f593e0e6288666e64d4f3d2651076f5eef1074 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Ta=C3=AFbi?= Date: Tue, 14 Apr 2020 19:38:40 +0200 Subject: [PATCH 123/427] util: after gzgets(), Z_STREAM_END means EOF, not error Context: I am compiling notmuch on OpenBSD which has a rather old zlib 1.2.3. It seems that the behaviour of gzgets() changed slightly between this version and more recent versions, but the manual does not reflect that change. Note that zlib's manual: - does not specify which error code (Z_OK or Z_STREAM_END) is set when EOF is reached, - does not indicate the meaning of Z_STREAM_END after gzgets(), but based on its meaning as a possible return value of inflate(), I would guess that it means EOF. amended by db: tidy commit message --- util/zlib-extra.c | 1 + 1 file changed, 1 insertion(+) diff --git a/util/zlib-extra.c b/util/zlib-extra.c index 623f6d62..2d2d2414 100644 --- a/util/zlib-extra.c +++ b/util/zlib-extra.c @@ -47,6 +47,7 @@ gz_getline (void *talloc_ctx, char **bufptr, ssize_t *bytes_read, gzFile stream) int zlib_status = 0; (void) gzerror (stream, &zlib_status); switch (zlib_status) { + case Z_STREAM_END: case Z_OK: /* no data read before EOF */ if (offset == 0) From 89f7a3c3e45edc80ccf5b09c4d8db8654a61b70c Mon Sep 17 00:00:00 2001 From: Jonas Bernoulli Date: Wed, 15 Apr 2020 20:28:19 +0200 Subject: [PATCH 124/427] gitignore: Ignore generated python-cffi files --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 468b660a..1c8705ec 100644 --- a/.gitignore +++ b/.gitignore @@ -16,3 +16,4 @@ tags /releases /.stamps *.stamp +/bindings/python-cffi/build/ From b4b558ac38db3e5da4240e0e26850a3a8ef38566 Mon Sep 17 00:00:00 2001 From: Jonas Bernoulli Date: Wed, 15 Apr 2020 20:28:20 +0200 Subject: [PATCH 125/427] emacs: Declare function notmuch-show-get-message-id --- emacs/notmuch-crypto.el | 2 ++ 1 file changed, 2 insertions(+) diff --git a/emacs/notmuch-crypto.el b/emacs/notmuch-crypto.el index 928de0bb..c201d0d7 100644 --- a/emacs/notmuch-crypto.el +++ b/emacs/notmuch-crypto.el @@ -24,6 +24,8 @@ (require 'epg) (require 'notmuch-lib) +(declare-function notmuch-show-get-message-id "notmuch-show" (&optional bare)) + (defcustom notmuch-crypto-process-mime t "Should cryptographic MIME parts be processed? From e02bb7e9fdbc96c66f32ce531509824f8afd4686 Mon Sep 17 00:00:00 2001 From: Jonas Bernoulli Date: Wed, 15 Apr 2020 20:28:21 +0200 Subject: [PATCH 126/427] emacs: Explicitly depend on Emacs 24 We use various things that were not available in earlier versions. --- emacs/notmuch-pkg.el.tmpl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/emacs/notmuch-pkg.el.tmpl b/emacs/notmuch-pkg.el.tmpl index de97baac..3eb0e04e 100644 --- a/emacs/notmuch-pkg.el.tmpl +++ b/emacs/notmuch-pkg.el.tmpl @@ -3,4 +3,4 @@ "notmuch" %VERSION% "Emacs based front-end (MUA) for notmuch" - nil) + '((emacs "24"))) From 00cdfe10717020423870fdaf56e973db9aba9f5a Mon Sep 17 00:00:00 2001 From: Tomi Ollila Date: Wed, 22 Apr 2020 00:07:29 +0300 Subject: [PATCH 127/427] build: drop support for xapian versions less than 1.4 Xapian 1.4 is over 3 years old now (1.4.0 released 2016-06-24), and 1.2 has been deprecated in Notmuch version 0.27 (2018-06-13). Xapian 1.4 supports compaction, field processors and retry locking; conditionals checking compaction and field processors were removed but user may want to disable retry locking at configure time so it is kept. --- configure | 101 +++++++---------------------------- lib/built-with.c | 4 +- lib/database.cc | 10 ---- lib/parse-time-vrp.cc | 2 - lib/parse-time-vrp.h | 3 +- lib/query-fp.cc | 3 -- lib/query-fp.h | 3 +- lib/regexp-fields.cc | 2 - lib/regexp-fields.h | 4 +- lib/thread-fp.cc | 3 -- lib/thread-fp.h | 3 +- test/T020-compact.sh | 12 ----- test/T500-search-date.sh | 3 -- test/T585-thread-subquery.sh | 12 ----- test/T600-named-queries.sh | 6 --- test/T650-regexp-query.sh | 4 -- test/T670-duplicate-mid.sh | 6 +-- 17 files changed, 28 insertions(+), 153 deletions(-) diff --git a/configure b/configure index 70031d14..0cfdaa6f 100755 --- a/configure +++ b/configure @@ -422,15 +422,22 @@ else have_pkg_config=0 fi -printf "Checking for Xapian development files... " + + +printf "Checking for Xapian development files (>= 1.4.0)... " have_xapian=0 -for xapian_config in ${XAPIAN_CONFIG} xapian-config xapian-config-1.3; do +for xapian_config in ${XAPIAN_CONFIG} xapian-config; do if ${xapian_config} --version > /dev/null 2>&1; then xapian_version=$(${xapian_config} --version | sed -e 's/.* //') - printf "Yes (%s).\n" ${xapian_version} - have_xapian=1 - xapian_cxxflags=$(${xapian_config} --cxxflags) - xapian_ldflags=$(${xapian_config} --libs) + case $xapian_version in + 1.[4-9]* | 1.[1-9][0-9]* | [2-9]* | [1-9][0-9]*) + printf "Yes (%s).\n" ${xapian_version} + have_xapian=1 + xapian_cxxflags=$(${xapian_config} --cxxflags) + xapian_ldflags=$(${xapian_config} --libs) + ;; + *) printf "Xapian $xapian_version not supported... " + esac break fi done @@ -439,59 +446,8 @@ if [ ${have_xapian} = "0" ]; then errors=$((errors + 1)) fi -have_xapian_compact=0 -have_xapian_field_processor=0 if [ ${have_xapian} = "1" ]; then - printf "Checking for Xapian compaction support... " - cat>_compact.cc< -class TestCompactor : public Xapian::Compactor { }; -EOF - if ${CXX} ${CXXFLAGS_for_sh} ${xapian_cxxflags} -c _compact.cc -o _compact.o > /dev/null 2>&1 - then - have_xapian_compact=1 - printf "Yes.\n" - else - printf "No.\n" - errors=$((errors + 1)) - fi - - rm -f _compact.o _compact.cc - - printf "Checking for Xapian FieldProcessor API... " - cat>_field_processor.cc< -class TitleFieldProcessor : public Xapian::FieldProcessor { }; -EOF - if ${CXX} ${CXXFLAGS_for_sh} ${xapian_cxxflags} -c _field_processor.cc -o _field_processor.o > /dev/null 2>&1 - then - have_xapian_field_processor=1 - printf "Yes.\n" - else - printf "No. (optional)\n" - fi - - rm -f _field_processor.o _field_processor.cc - default_xapian_backend="" - # DB_RETRY_LOCK is only supported on Xapian > 1.3.2 - have_xapian_db_retry_lock=0 - if [ $WITH_RETRY_LOCK = "1" ]; then - printf "Checking for Xapian lock retry support... " - cat>_retry.cc< -int flag = Xapian::DB_RETRY_LOCK; -EOF - if ${CXX} ${CXXFLAGS_for_sh} ${xapian_cxxflags} -c _retry.cc -o _retry.o > /dev/null 2>&1 - then - have_xapian_db_retry_lock=1 - printf "Yes.\n" - else - printf "No. (optional)\n" - fi - rm -f _retry.o _retry.cc - fi - printf "Testing default Xapian backend... " cat >_default_backend.cc < @@ -879,8 +835,8 @@ EOF if [ $have_python -eq 0 ]; then echo " python interpreter" fi - if [ $have_xapian -eq 0 -o $have_xapian_compact -eq 0 ]; then - echo " Xapian library (>= version 1.2.6, including development files such as headers)" + if [ $have_xapian -eq 0 ]; then + echo " Xapian library (>= version 1.4.0, including development files such as headers)" echo " https://xapian.org/" fi if [ $have_zlib -eq 0 ]; then @@ -1278,14 +1234,8 @@ HAVE_TIMEGM = ${have_timegm} # Whether struct dirent has d_type (if not, then notmuch will use stat) HAVE_D_TYPE = ${have_d_type} -# Whether the Xapian version in use supports compaction -HAVE_XAPIAN_COMPACT = ${have_xapian_compact} - -# Whether the Xapian version in use supports field processors -HAVE_XAPIAN_FIELD_PROCESSOR = ${have_xapian_field_processor} - -# Whether the Xapian version in use supports DB_RETRY_LOCK -HAVE_XAPIAN_DB_RETRY_LOCK = ${have_xapian_db_retry_lock} +# Whether to have Xapian retry lock +HAVE_XAPIAN_DB_RETRY_LOCK = ${WITH_RETRY_LOCK} # Whether the getpwuid_r function is standards-compliant # (if not, then notmuch will #define _POSIX_PTHREAD_SEMANTICS @@ -1309,9 +1259,6 @@ LINKER_RESOLVES_LIBRARY_DEPENDENCIES = ${linker_resolves_library_dependencies} XAPIAN_CXXFLAGS = ${xapian_cxxflags} XAPIAN_LDFLAGS = ${xapian_ldflags} -# Which backend will Xapian use by default? -DEFAULT_XAPIAN_BACKEND = ${default_xapian_backend} - # Flags needed to compile and link against GMime GMIME_CFLAGS = ${gmime_cflags} GMIME_LDFLAGS = ${gmime_ldflags} @@ -1364,16 +1311,14 @@ COMMON_CONFIGURE_CFLAGS = \\ -DHAVE_D_TYPE=\$(HAVE_D_TYPE) \\ -DSTD_GETPWUID=\$(STD_GETPWUID) \\ -DSTD_ASCTIME=\$(STD_ASCTIME) \\ - -DHAVE_XAPIAN_COMPACT=\$(HAVE_XAPIAN_COMPACT) \\ -DSILENCE_XAPIAN_DEPRECATION_WARNINGS \\ - -DHAVE_XAPIAN_FIELD_PROCESSOR=\$(HAVE_XAPIAN_FIELD_PROCESSOR) \\ -DHAVE_XAPIAN_DB_RETRY_LOCK=\$(HAVE_XAPIAN_DB_RETRY_LOCK) CONFIGURE_CFLAGS = \$(COMMON_CONFIGURE_CFLAGS) CONFIGURE_CXXFLAGS = \$(COMMON_CONFIGURE_CFLAGS) \$(XAPIAN_CXXFLAGS) -CONFIGURE_LDFLAGS = \$(GMIME_LDFLAGS) \$(TALLOC_LDFLAGS) \$(ZLIB_LDFLAGS) \$(XAPIAN_LDFLAGS) +CONFIGURE_LDFLAGS = \$(GMIME_LDFLAGS) \$(TALLOC_LDFLAGS) \$(ZLIB_LDFLAGS) \$(XAPIAN_LDFLAGS) EOF # construct the sh.config @@ -1383,14 +1328,8 @@ cat > sh.config < -#if HAVE_XAPIAN_FIELD_PROCESSOR - Xapian::Query QueryFieldProcessor::operator() (const std::string & name) { @@ -40,4 +38,3 @@ QueryFieldProcessor::operator() (const std::string & name) return parser.parse_query (expansion, NOTMUCH_QUERY_PARSER_FLAGS); } -#endif diff --git a/lib/query-fp.h b/lib/query-fp.h index 8a8bde62..beaaf405 100644 --- a/lib/query-fp.h +++ b/lib/query-fp.h @@ -26,7 +26,6 @@ #include #include "notmuch.h" -#if HAVE_XAPIAN_FIELD_PROCESSOR class QueryFieldProcessor : public Xapian::FieldProcessor { protected: Xapian::QueryParser &parser; @@ -40,5 +39,5 @@ public: Xapian::Query operator() (const std::string & str); }; -#endif + #endif /* NOTMUCH_QUERY_FP_H */ diff --git a/lib/regexp-fields.cc b/lib/regexp-fields.cc index 198eb32f..0feb50e5 100644 --- a/lib/regexp-fields.cc +++ b/lib/regexp-fields.cc @@ -26,7 +26,6 @@ #include "notmuch-private.h" #include "database-private.h" -#if HAVE_XAPIAN_FIELD_PROCESSOR static void compile_regex (regex_t ®exp, const char *str) { @@ -208,4 +207,3 @@ RegexpFieldProcessor::operator() (const std::string & str) } } } -#endif diff --git a/lib/regexp-fields.h b/lib/regexp-fields.h index 97778a1d..a8cca243 100644 --- a/lib/regexp-fields.h +++ b/lib/regexp-fields.h @@ -24,7 +24,7 @@ #ifndef NOTMUCH_REGEXP_FIELDS_H #define NOTMUCH_REGEXP_FIELDS_H -#if HAVE_XAPIAN_FIELD_PROCESSOR + #include #include #include "database-private.h" @@ -79,5 +79,5 @@ public: Xapian::Query operator() (const std::string & str); }; -#endif + #endif /* NOTMUCH_REGEXP_FIELDS_H */ diff --git a/lib/thread-fp.cc b/lib/thread-fp.cc index 73277006..97a65211 100644 --- a/lib/thread-fp.cc +++ b/lib/thread-fp.cc @@ -24,8 +24,6 @@ #include "thread-fp.h" #include -#if HAVE_XAPIAN_FIELD_PROCESSOR - Xapian::Query ThreadFieldProcessor::operator() (const std::string & str) { @@ -64,4 +62,3 @@ ThreadFieldProcessor::operator() (const std::string & str) } } -#endif diff --git a/lib/thread-fp.h b/lib/thread-fp.h index de837d3e..00bf1aa2 100644 --- a/lib/thread-fp.h +++ b/lib/thread-fp.h @@ -26,7 +26,6 @@ #include #include "notmuch.h" -#if HAVE_XAPIAN_FIELD_PROCESSOR class ThreadFieldProcessor : public Xapian::FieldProcessor { protected: Xapian::QueryParser &parser; @@ -40,5 +39,5 @@ public: Xapian::Query operator() (const std::string & str); }; -#endif + #endif /* NOTMUCH_THREAD_FP_H */ diff --git a/test/T020-compact.sh b/test/T020-compact.sh index 58cd2ba7..02f8738f 100755 --- a/test/T020-compact.sh +++ b/test/T020-compact.sh @@ -10,18 +10,6 @@ notmuch tag +tag1 \* notmuch tag +tag2 subject:Two notmuch tag -tag1 +tag3 subject:Three -if [ $NOTMUCH_HAVE_XAPIAN_COMPACT -eq 0 ]; then - test_begin_subtest "Compact unsupported: error message" - output=$(notmuch compact --quiet 2>&1) - test_expect_equal "$output" "notmuch was compiled against a xapian version lacking compaction support. -Compaction failed: Unsupported operation" - - test_begin_subtest "Compact unsupported: status code" - test_expect_code 1 "notmuch compact" - - test_done -fi - test_begin_subtest "Running compact" test_expect_success "notmuch compact --backup=${TEST_DIRECTORY}/xapian.old" diff --git a/test/T500-search-date.sh b/test/T500-search-date.sh index f84b0962..85ff831f 100755 --- a/test/T500-search-date.sh +++ b/test/T500-search-date.sh @@ -14,9 +14,6 @@ test_expect_equal "$output" "thread:XXX 2010-12-16 [1/1] Olivier Berger; Essai test_begin_subtest "Absolute date field" output=$(notmuch search date:2010-12-16 | notmuch_search_sanitize) -if [ $NOTMUCH_HAVE_XAPIAN_FIELD_PROCESSOR -ne 1 ]; then - test_subtest_known_broken -fi test_expect_equal "$output" "thread:XXX 2010-12-16 [1/1] Olivier Berger; Essai accentué (inbox unread)" test_begin_subtest "Absolute time range with TZ" diff --git a/test/T585-thread-subquery.sh b/test/T585-thread-subquery.sh index bf9894d3..71ced149 100755 --- a/test/T585-thread-subquery.sh +++ b/test/T585-thread-subquery.sh @@ -14,9 +14,6 @@ count=$(notmuch count from:keithp and to:keithp) test_expect_equal 0 "$count" test_begin_subtest "Same query against threads" -if [ $NOTMUCH_HAVE_XAPIAN_FIELD_PROCESSOR -eq 0 ]; then - test_subtest_known_broken -fi notmuch search thread:{from:keithp} and thread:{to:keithp} | notmuch_search_sanitize > OUTPUT cat< EXPECTED thread:XXX 2009-11-18 [7/7] Lars Kellogg-Stedman, Mikhail Gusarov, Keith Packard, Carl Worth; [notmuch] Working with Maildir storage? (inbox signed unread) @@ -24,9 +21,6 @@ EOF test_expect_equal_file EXPECTED OUTPUT test_begin_subtest "Mix thread and non-threads query" -if [ $NOTMUCH_HAVE_XAPIAN_FIELD_PROCESSOR -eq 0 ]; then - test_subtest_known_broken -fi notmuch search thread:{from:keithp} and to:keithp | notmuch_search_sanitize > OUTPUT cat< EXPECTED thread:XXX 2009-11-18 [1/7] Lars Kellogg-Stedman| Mikhail Gusarov, Keith Packard, Carl Worth; [notmuch] Working with Maildir storage? (inbox signed unread) @@ -34,9 +28,6 @@ EOF test_expect_equal_file EXPECTED OUTPUT test_begin_subtest "Compound subquery" -if [ $NOTMUCH_HAVE_XAPIAN_FIELD_PROCESSOR -eq 0 ]; then - test_subtest_known_broken -fi notmuch search 'thread:"{from:keithp and date:2009}" and thread:{to:keithp}' | notmuch_search_sanitize > OUTPUT cat< EXPECTED thread:XXX 2009-11-18 [7/7] Lars Kellogg-Stedman, Mikhail Gusarov, Keith Packard, Carl Worth; [notmuch] Working with Maildir storage? (inbox signed unread) @@ -44,9 +35,6 @@ EOF test_expect_equal_file EXPECTED OUTPUT test_begin_subtest "Syntax/quoting error in subquery" -if [ $NOTMUCH_HAVE_XAPIAN_FIELD_PROCESSOR -eq 0 ]; then - test_subtest_known_broken -fi notmuch search 'thread:{from:keithp and date:2009} and thread:{to:keithp}' 1>OUTPUT 2>&1 cat< EXPECTED notmuch search: A Xapian exception occurred diff --git a/test/T600-named-queries.sh b/test/T600-named-queries.sh index 421a11d4..0ae8b83d 100755 --- a/test/T600-named-queries.sh +++ b/test/T600-named-queries.sh @@ -68,17 +68,11 @@ test_expect_equal_file QUERIES.BEFORE OUTPUT test_begin_subtest "search named query" notmuch search query:test > OUTPUT notmuch search $QUERYSTR > EXPECTED -if [ $NOTMUCH_HAVE_XAPIAN_FIELD_PROCESSOR -ne 1 ]; then - test_subtest_known_broken -fi test_expect_equal_file EXPECTED OUTPUT test_begin_subtest "search named query with other terms" notmuch search query:test and subject:Maildir > OUTPUT notmuch search $QUERYSTR and subject:Maildir > EXPECTED -if [ $NOTMUCH_HAVE_XAPIAN_FIELD_PROCESSOR -ne 1 ]; then - test_subtest_known_broken -fi test_expect_equal_file EXPECTED OUTPUT test_begin_subtest "search nested named query" diff --git a/test/T650-regexp-query.sh b/test/T650-regexp-query.sh index 43af3b47..55dc6c88 100755 --- a/test/T650-regexp-query.sh +++ b/test/T650-regexp-query.sh @@ -2,10 +2,6 @@ test_description='regular expression searches' . $(dirname "$0")/test-lib.sh || exit 1 -if [ $NOTMUCH_HAVE_XAPIAN_FIELD_PROCESSOR -eq 0 ]; then - test_done -fi - add_message '[dir]=bad' '[subject]="To the bone"' add_message '[dir]=.' '[subject]="Top level"' add_message '[dir]=bad/news' '[subject]="Bears"' diff --git a/test/T670-duplicate-mid.sh b/test/T670-duplicate-mid.sh index c17ccb69..4e5672ab 100755 --- a/test/T670-duplicate-mid.sh +++ b/test/T670-duplicate-mid.sh @@ -48,11 +48,7 @@ notmuch search --output=files subject:'"message 2"' | notmuch_dir_sanitize > OUT test_expect_equal_file EXPECTED OUTPUT test_begin_subtest 'Regexp search for second subject' -# Note that missing field processor support really means the test -# doesn't make sense, but it happens to pass. -if [ $NOTMUCH_HAVE_XAPIAN_FIELD_PROCESSOR -eq 1 ]; then - test_subtest_known_broken -fi +test_subtest_known_broken cat <EXPECTED MAIL_DIR/copy0 MAIL_DIR/copy1 From 7b756d1e3885b70e81647a85432e0f2d043167c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Ta=C3=AFbi?= Date: Sun, 19 Apr 2020 18:06:19 +0200 Subject: [PATCH 128/427] test: sort the output of the "prefix" test in T610-message-property This test extracts values from a (key,value) map where multiple entries can have the same key, and the entries are sorted by key, but not by value. The test incorrectly assumes that the values will be sorted as well, so sort the output. --- test/T610-message-property.sh | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/test/T610-message-property.sh b/test/T610-message-property.sh index 53a0be3b..b8774230 100755 --- a/test/T610-message-property.sh +++ b/test/T610-message-property.sh @@ -186,6 +186,18 @@ EXPECT0(notmuch_message_add_property (message, "testkey3", "testvalue3")); EXPECT0(notmuch_message_add_property (message, "testkey3", "alice3")); print_properties (message, "testkey", FALSE); EOF +# expected: 4 values for testkey1, 3 values for testkey3 +# they are not guaranteed to be sorted, so sort them, leaving the first +# line '== stdout ==' and the end ('== stderr ==' and whatever error +# may have been printed) alone +mv OUTPUT unsorted_OUTPUT +awk ' NR == 1 { print; next } \ + NR < 6 { print | "sort"; next } \ + NR == 6 { close("sort") } \ + NR < 9 { print | "sort"; next } \ + NR == 9 { close("sort") } \ + { print }' unsorted_OUTPUT > OUTPUT +rm unsorted_OUTPUT cat <<'EOF' >EXPECTED == stdout == alice From 11ac932a4503872c19987b843d58513c4b9ef76f Mon Sep 17 00:00:00 2001 From: Jonas Bernoulli Date: Sat, 25 Apr 2020 22:18:07 +0200 Subject: [PATCH 129/427] emacs: Use `cl-lib' instead of deprecated `cl' Starting with Emacs 27 the old `cl' implementation is finally considered obsolete. Previously its use was strongly discouraged at run-time but one was still allowed to use it at compile-time. For the most part the transition is very simple and boils down to adding the "cl-" prefix to some symbols. A few replacements do not follow that simple pattern; e.g. `first' is replaced with `car', even though the alias `cl-first' exists, because the latter is not idiomatic emacs-lisp. In a few cases we start using `pcase-let' or `pcase-lambda' instead of renaming e.g. `first' to `car'. That way we can remind the reader of the meaning of the various parts of the data that is being deconstructed. An obsolete `lexical-let' and a `lexical-let*' are replaced with their regular variants `let' and `let*' even though we do not at the same time enable `lexical-binding' for that file. That is the right thing to do because it does not actually make a difference in those cases whether lexical bindings are used or not, and because this should be enabled in a separate commit. We need to explicitly depend on the `cl-lib' package because Emacs 24.1 and 24.2 lack that library. When using these releases we end up using the backport from GNU Elpa. We need to explicitly require the `pcase' library because `pcase-dolist' was not autoloaded until Emacs 25.1. --- emacs/notmuch-company.el | 5 +- emacs/notmuch-draft.el | 2 +- emacs/notmuch-hello.el | 147 +++++++++++++++--------------- emacs/notmuch-jump.el | 45 +++++---- emacs/notmuch-lib.el | 18 ++-- emacs/notmuch-maildir-fcc.el | 35 +++---- emacs/notmuch-mua.el | 76 +++++++-------- emacs/notmuch-parser.el | 18 ++-- emacs/notmuch-pkg.el.tmpl | 3 +- emacs/notmuch-show.el | 103 +++++++++++---------- emacs/notmuch-tag.el | 45 +++++---- emacs/notmuch-tree.el | 20 ++-- emacs/notmuch.el | 62 ++++++------- test/T450-emacs-show.sh | 2 +- test/emacs-attachment-warnings.el | 4 +- test/test-lib.el | 18 ++-- 16 files changed, 304 insertions(+), 299 deletions(-) diff --git a/emacs/notmuch-company.el b/emacs/notmuch-company.el index 3e12e7a9..ac998f9b 100644 --- a/emacs/notmuch-company.el +++ b/emacs/notmuch-company.el @@ -27,7 +27,8 @@ ;;; Code: -(eval-when-compile (require 'cl)) +(eval-when-compile (require 'cl-lib)) + (require 'notmuch-lib) (defvar notmuch-company-last-prefix nil) @@ -65,7 +66,7 @@ (require 'company) (let ((case-fold-search t) (completion-ignore-case t)) - (case command + (cl-case command (interactive (company-begin-backend 'notmuch-company)) (prefix (and (derived-mode-p 'message-mode) (looking-back (concat notmuch-address-completion-headers-regexp ".*") diff --git a/emacs/notmuch-draft.el b/emacs/notmuch-draft.el index e22e0d16..504b33be 100644 --- a/emacs/notmuch-draft.el +++ b/emacs/notmuch-draft.el @@ -152,7 +152,7 @@ Used when a new version is saved, or the message is sent." "Checks if we should save a message that should be encrypted. `notmuch-draft-save-plaintext' controls the behaviour." - (case notmuch-draft-save-plaintext + (cl-case notmuch-draft-save-plaintext ((ask) (unless (yes-or-no-p "(Customize `notmuch-draft-save-plaintext' to avoid this warning) This message contains mml tags that suggest it is intended to be encrypted. diff --git a/emacs/notmuch-hello.el b/emacs/notmuch-hello.el index ab6ee798..bdf584e6 100644 --- a/emacs/notmuch-hello.el +++ b/emacs/notmuch-hello.el @@ -21,7 +21,8 @@ ;;; Code: -(eval-when-compile (require 'cl)) +(eval-when-compile (require 'cl-lib)) + (require 'widget) (require 'wid-edit) ; For `widget-forward'. @@ -47,17 +48,19 @@ lists (NAME QUERY COUNT-QUERY)." ((keywordp (car saved-search)) (plist-get saved-search field)) ;; It is not a plist so it is an old-style entry. - ((consp (cdr saved-search)) ;; It is a list (NAME QUERY COUNT-QUERY) - (case field - (:name (first saved-search)) - (:query (second saved-search)) - (:count-query (third saved-search)) - (t nil))) - (t ;; It is a cons-cell (NAME . QUERY) - (case field - (:name (car saved-search)) - (:query (cdr saved-search)) - (t nil))))) + ((consp (cdr saved-search)) + (pcase-let ((`(,name ,query ,count-query) saved-search)) + (cl-case field + (:name name) + (:query query) + (:count-query count-query) + (t nil)))) + (t + (pcase-let ((`(,name . ,query) saved-search)) + (cl-case field + (:name name) + (:query query) + (t nil)))))) (defun notmuch-hello-saved-search-to-plist (saved-search) "Return a copy of SAVED-SEARCH in plist form. @@ -66,7 +69,7 @@ If saved search is a plist then just return a copy. In other cases, for backwards compatibility, convert to plist form and return that." (if (keywordp (car saved-search)) - (copy-seq saved-search) + (copy-sequence saved-search) (let ((fields (list :name :query :count-query)) plist-search) (dolist (field fields plist-search) @@ -396,10 +399,10 @@ afterwards.") notmuch-saved-searches))) ;; If an existing saved search with this name exists, remove it. (setq notmuch-saved-searches - (loop for elem in notmuch-saved-searches - if (not (equal name - (notmuch-saved-search-get elem :name))) - collect elem)) + (cl-loop for elem in notmuch-saved-searches + if (not (equal name + (notmuch-saved-search-get elem :name))) + collect elem)) ;; Add the new one. (customize-save-variable 'notmuch-saved-searches (add-to-list 'notmuch-saved-searches @@ -417,28 +420,28 @@ afterwards.") (notmuch-hello-update))) (defun notmuch-hello-longest-label (searches-alist) - (or (loop for elem in searches-alist - maximize (length (notmuch-saved-search-get elem :name))) + (or (cl-loop for elem in searches-alist + maximize (length (notmuch-saved-search-get elem :name))) 0)) (defun notmuch-hello-reflect-generate-row (ncols nrows row list) (let ((len (length list))) - (loop for col from 0 to (- ncols 1) - collect (let ((offset (+ (* nrows col) row))) - (if (< offset len) - (nth offset list) - ;; Don't forget to insert an empty slot in the - ;; output matrix if there is no corresponding - ;; value in the input matrix. - nil))))) + (cl-loop for col from 0 to (- ncols 1) + collect (let ((offset (+ (* nrows col) row))) + (if (< offset len) + (nth offset list) + ;; Don't forget to insert an empty slot in the + ;; output matrix if there is no corresponding + ;; value in the input matrix. + nil))))) (defun notmuch-hello-reflect (list ncols) "Reflect a `ncols' wide matrix represented by `list' along the diagonal." ;; Not very lispy... (let ((nrows (ceiling (length list) ncols))) - (loop for row from 0 to (- nrows 1) - append (notmuch-hello-reflect-generate-row ncols nrows row list)))) + (cl-loop for row from 0 to (- nrows 1) + append (notmuch-hello-reflect-generate-row ncols nrows row list)))) (defun notmuch-hello-widget-search (widget &rest ignore) (cond @@ -584,7 +587,7 @@ with `notmuch-hello-query-counts'." (widget-insert (make-string column-indent ? ))) (let* ((name (plist-get elem :name)) (query (plist-get elem :query)) - (oldest-first (case (plist-get elem :sort-order) + (oldest-first (cl-case (plist-get elem :sort-order) (newest-first nil) (oldest-first t) (otherwise notmuch-search-oldest-first))) @@ -812,48 +815,48 @@ Complete list of currently available key bindings: "clear") (widget-insert "\n\n") (let ((start (point))) - (loop for i from 1 to notmuch-hello-recent-searches-max - for search in notmuch-search-history do - (let ((widget-symbol (intern (format "notmuch-hello-search-%d" i)))) - (set widget-symbol - (widget-create 'editable-field - ;; Don't let the search boxes be - ;; less than 8 characters wide. - :size (max 8 - (- (window-width) - ;; Leave some space - ;; at the start and - ;; end of the - ;; boxes. - (* 2 notmuch-hello-indent) - ;; 1 for the space - ;; before the - ;; `[save]' button. 6 - ;; for the `[save]' - ;; button. - 1 6 - ;; 1 for the space - ;; before the `[del]' - ;; button. 5 for the - ;; `[del]' button. - 1 5)) - :action (lambda (widget &rest ignore) - (notmuch-hello-search (widget-value widget))) - search)) - (widget-insert " ") - (widget-create 'push-button - :notify (lambda (widget &rest ignore) - (notmuch-hello-add-saved-search widget)) - :notmuch-saved-search-widget widget-symbol - "save") - (widget-insert " ") - (widget-create 'push-button - :notify (lambda (widget &rest ignore) - (when (y-or-n-p "Are you sure you want to delete this search? ") - (notmuch-hello-delete-search-from-history widget))) - :notmuch-saved-search-widget widget-symbol - "del")) - (widget-insert "\n")) + (cl-loop for i from 1 to notmuch-hello-recent-searches-max + for search in notmuch-search-history do + (let ((widget-symbol (intern (format "notmuch-hello-search-%d" i)))) + (set widget-symbol + (widget-create 'editable-field + ;; Don't let the search boxes be + ;; less than 8 characters wide. + :size (max 8 + (- (window-width) + ;; Leave some space + ;; at the start and + ;; end of the + ;; boxes. + (* 2 notmuch-hello-indent) + ;; 1 for the space + ;; before the + ;; `[save]' button. 6 + ;; for the `[save]' + ;; button. + 1 6 + ;; 1 for the space + ;; before the `[del]' + ;; button. 5 for the + ;; `[del]' button. + 1 5)) + :action (lambda (widget &rest ignore) + (notmuch-hello-search (widget-value widget))) + search)) + (widget-insert " ") + (widget-create 'push-button + :notify (lambda (widget &rest ignore) + (notmuch-hello-add-saved-search widget)) + :notmuch-saved-search-widget widget-symbol + "save") + (widget-insert " ") + (widget-create 'push-button + :notify (lambda (widget &rest ignore) + (when (y-or-n-p "Are you sure you want to delete this search? ") + (notmuch-hello-delete-search-from-history widget))) + :notmuch-saved-search-widget widget-symbol + "del")) + (widget-insert "\n")) (indent-rigidly start (point) notmuch-hello-indent)) nil)) diff --git a/emacs/notmuch-jump.el b/emacs/notmuch-jump.el index 1cdf5b50..92a5a2cc 100644 --- a/emacs/notmuch-jump.el +++ b/emacs/notmuch-jump.el @@ -22,7 +22,9 @@ ;;; Code: -(eval-when-compile (require 'cl)) +(eval-when-compile + (require 'cl-lib) + (require 'pcase)) (require 'notmuch-lib) (require 'notmuch-hello) @@ -51,7 +53,7 @@ fast way to jump to a saved search from anywhere in Notmuch." (let ((name (plist-get saved-search :name)) (query (plist-get saved-search :query)) (oldest-first - (case (plist-get saved-search :sort-order) + (cl-case (plist-get saved-search :sort-order) (newest-first nil) (oldest-first t) (otherwise (default-value 'notmuch-search-oldest-first))))) @@ -127,18 +129,16 @@ buffer." ;; Compute the maximum key description width (let ((key-width 1)) - (dolist (entry action-map) + (pcase-dolist (`(,key ,desc) action-map) (setq key-width (max key-width - (string-width (format-kbd-macro (first entry)))))) + (string-width (format-kbd-macro key))))) ;; Format each action - (mapcar (lambda (entry) - (let ((key (format-kbd-macro (first entry))) - (desc (second entry))) - (concat - (propertize key 'face 'minibuffer-prompt) - (make-string (- key-width (length key)) ? ) - " " desc))) + (mapcar (pcase-lambda (`(,key ,desc)) + (setq key (format-kbd-macro key)) + (concat (propertize key 'face 'minibuffer-prompt) + (make-string (- key-width (length key)) ? ) + " " desc)) action-map))) (defun notmuch-jump--insert-items (width items) @@ -173,28 +173,25 @@ buffer." "Translate ACTION-MAP into a minibuffer keymap." (let ((map (make-sparse-keymap))) (set-keymap-parent map notmuch-jump-minibuffer-map) - (dolist (action action-map) - (if (= (length (first action)) 1) - (define-key map (first action) + (pcase-dolist (`(,key ,name ,fn) action-map) + (if (= (length key) 1) + (define-key map key `(lambda () (interactive) - (setq notmuch-jump--action ',(third action)) + (setq notmuch-jump--action ',fn) (exit-minibuffer))))) ;; By doing this in two passes (and checking if we already have a ;; binding) we avoid problems if the user specifies a binding which ;; is a prefix of another binding. - (dolist (action action-map) - (if (> (length (first action)) 1) - (let* ((key (elt (first action) 0)) + (pcase-dolist (`(,key ,name ,fn) action-map) + (if (> (length key) 1) + (let* ((key (elt key 0)) (keystr (string key)) (new-prompt (concat prompt (format-kbd-macro keystr) " ")) (action-submap nil)) (unless (lookup-key map keystr) - (dolist (act action-map) - (when (= key (elt (first act) 0)) - (push (list (substring (first act) 1) - (second act) - (third act)) - action-submap))) + (pcase-dolist (`(,k ,n ,f) action-map) + (when (= key (elt k 0)) + (push (list (substring k 1) n f) action-submap))) ;; We deal with backspace specially (push (list (kbd "DEL") "Backup" diff --git a/emacs/notmuch-lib.el b/emacs/notmuch-lib.el index e085a06b..01862f44 100644 --- a/emacs/notmuch-lib.el +++ b/emacs/notmuch-lib.el @@ -23,10 +23,12 @@ ;;; Code: +(require 'cl-lib) + (require 'mm-util) (require 'mm-view) (require 'mm-decode) -(require 'cl) + (require 'notmuch-compat) (unless (require 'notmuch-version nil t) @@ -574,7 +576,7 @@ for this message, if present." (defun notmuch-parts-filter-by-type (parts type) "Given a list of message parts, return a list containing the ones matching the given type." - (remove-if-not + (cl-remove-if-not (lambda (part) (notmuch-match-content-type (plist-get part :content-type) type)) parts)) @@ -685,8 +687,8 @@ current buffer, if possible." ;; have symbols of the form :Header as keys, and the resulting alist will have ;; symbols of the form 'Header as keys. (defun notmuch-headers-plist-to-alist (plist) - (loop for (key value . rest) on plist by #'cddr - collect (cons (intern (substring (symbol-name key) 1)) value))) + (cl-loop for (key value . rest) on plist by #'cddr + collect (cons (intern (substring (symbol-name key) 1)) value))) (defun notmuch-face-ensure-list-form (face) "Return FACE in face list form. @@ -780,7 +782,7 @@ arguments passed to the sentinel. COMMAND and ERR, if provided, are passed to `notmuch-check-exit-status'. If COMMAND is not provided, it is taken from `process-command'." (let ((exit-status - (case (process-status proc) + (cl-case (process-status proc) ((exit) (process-exit-status proc)) ((signal) msg)))) (when exit-status @@ -848,7 +850,7 @@ for `call-process'. ARGS is as described for (let (stdin-string) (while (keywordp (car args)) - (case (car args) + (cl-case (car args) (:stdin-string (setq stdin-string (cadr args) args (cddr args))) (otherwise @@ -1026,8 +1028,4 @@ region if the region is active, or both `point' otherwise." (provide 'notmuch-lib) -;; Local Variables: -;; byte-compile-warnings: (not cl-functions) -;; End: - ;;; notmuch-lib.el ends here diff --git a/emacs/notmuch-maildir-fcc.el b/emacs/notmuch-maildir-fcc.el index ae56bacd..b9cca543 100644 --- a/emacs/notmuch-maildir-fcc.el +++ b/emacs/notmuch-maildir-fcc.el @@ -22,7 +22,8 @@ ;;; Code: -(eval-when-compile (require 'cl)) +(eval-when-compile (require 'cl-lib)) + (require 'message) (require 'notmuch-lib) @@ -251,12 +252,12 @@ If CREATE is non-nil then create the folder if necessary." (let ((response (notmuch-read-char-choice "Insert failed: (r)etry, (c)reate folder, (i)gnore, or (e)dit the header? " '(?r ?c ?i ?e)))) - (case response - (?r (notmuch-maildir-fcc-with-notmuch-insert fcc-header)) - (?c (notmuch-maildir-fcc-with-notmuch-insert fcc-header 't)) - (?i 't) - (?e (notmuch-maildir-fcc-with-notmuch-insert - (read-from-minibuffer "Fcc header: " fcc-header))))))))) + (cl-case response + (?r (notmuch-maildir-fcc-with-notmuch-insert fcc-header)) + (?c (notmuch-maildir-fcc-with-notmuch-insert fcc-header 't)) + (?i 't) + (?e (notmuch-maildir-fcc-with-notmuch-insert + (read-from-minibuffer "Fcc header: " fcc-header))))))))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; @@ -335,16 +336,16 @@ if needed." (let* ((prompt (format "Fcc %s is not a maildir: (r)etry, (c)reate folder, (i)gnore, or (e)dit the header? " fcc-header)) (response (notmuch-read-char-choice prompt '(?r ?c ?i ?e)))) - (case response - (?r (notmuch-maildir-fcc-file-fcc fcc-header)) - (?c (if (file-writable-p fcc-header) - (notmuch-maildir-fcc-create-maildir fcc-header) - (message "No permission to create %s." fcc-header) - (sit-for 2)) - (notmuch-maildir-fcc-file-fcc fcc-header)) - (?i 't) - (?e (notmuch-maildir-fcc-file-fcc - (read-from-minibuffer "Fcc header: " fcc-header))))))) + (cl-case response + (?r (notmuch-maildir-fcc-file-fcc fcc-header)) + (?c (if (file-writable-p fcc-header) + (notmuch-maildir-fcc-create-maildir fcc-header) + (message "No permission to create %s." fcc-header) + (sit-for 2)) + (notmuch-maildir-fcc-file-fcc fcc-header)) + (?i 't) + (?e (notmuch-maildir-fcc-file-fcc + (read-from-minibuffer "Fcc header: " fcc-header))))))) (defun notmuch-maildir-fcc-write-buffer-to-maildir (destdir &optional mark-seen) "Writes the current buffer to maildir destdir. If mark-seen is diff --git a/emacs/notmuch-mua.el b/emacs/notmuch-mua.el index 76572b87..40a1e6bc 100644 --- a/emacs/notmuch-mua.el +++ b/emacs/notmuch-mua.el @@ -21,6 +21,8 @@ ;;; Code: +(eval-when-compile (require 'cl-lib)) + (require 'message) (require 'mm-view) (require 'format-spec) @@ -30,8 +32,6 @@ (require 'notmuch-draft) (require 'notmuch-message) -(eval-when-compile (require 'cl)) - (declare-function notmuch-show-insert-body "notmuch-show" (msg body depth)) (declare-function notmuch-fcc-header-setup "notmuch-maildir-fcc" ()) (declare-function notmuch-maildir-message-do-fcc "notmuch-maildir-fcc" ()) @@ -140,17 +140,18 @@ Typically this is added to `notmuch-mua-send-hook'." ;; Limit search from reaching other possible parts of the message (let ((search-limit (search-forward "\n<#" nil t))) (message-goto-body) - (loop while (re-search-forward notmuch-mua-attachment-regexp search-limit t) - ;; For every instance of the "attachment" string - ;; found, examine the text properties. If the text - ;; has either a `face' or `syntax-table' property - ;; then it is quoted text and should *not* cause the - ;; user to be asked about a missing attachment. - if (let ((props (text-properties-at (match-beginning 0)))) - (not (or (memq 'syntax-table props) - (memq 'face props)))) - return t - finally return nil))) + (cl-loop while (re-search-forward notmuch-mua-attachment-regexp + search-limit t) + ;; For every instance of the "attachment" string + ;; found, examine the text properties. If the text + ;; has either a `face' or `syntax-table' property + ;; then it is quoted text and should *not* cause the + ;; user to be asked about a missing attachment. + if (let ((props (text-properties-at (match-beginning 0)))) + (not (or (memq 'syntax-table props) + (memq 'face props)))) + return t + finally return nil))) ;; ...but doesn't have a part with a filename... (save-excursion (message-goto-body) @@ -203,11 +204,11 @@ Typically this is added to `notmuch-mua-send-hook'." (defun notmuch-mua-reply-crypto (parts) "Add mml sign-encrypt flag if any part of original message is encrypted." - (loop for part in parts - if (notmuch-match-content-type (plist-get part :content-type) "multipart/encrypted") - do (mml-secure-message-sign-encrypt) - else if (notmuch-match-content-type (plist-get part :content-type) "multipart/*") - do (notmuch-mua-reply-crypto (plist-get part :content)))) + (cl-loop for part in parts + if (notmuch-match-content-type (plist-get part :content-type) "multipart/encrypted") + do (mml-secure-message-sign-encrypt) + else if (notmuch-match-content-type (plist-get part :content-type) "multipart/*") + do (notmuch-mua-reply-crypto (plist-get part :content)))) ;; There is a bug in emacs 23's message.el that results in a newline ;; not being inserted after the References header, so the next header @@ -252,14 +253,14 @@ Typically this is added to `notmuch-mua-send-hook'." ;; We modify message-header-format-alist to get around a bug in message.el. ;; See the comment above on notmuch-mua-insert-references. (let ((message-header-format-alist - (loop for pair in message-header-format-alist - if (eq (car pair) 'References) - collect (cons 'References - (apply-partially - 'notmuch-mua-insert-references - (cdr pair))) - else - collect pair))) + (cl-loop for pair in message-header-format-alist + if (eq (car pair) 'References) + collect (cons 'References + (apply-partially + 'notmuch-mua-insert-references + (cdr pair))) + else + collect pair))) (notmuch-mua-mail (plist-get reply-headers :To) (notmuch-sanitize (plist-get reply-headers :Subject)) (notmuch-headers-plist-to-alist reply-headers) @@ -309,10 +310,10 @@ Typically this is added to `notmuch-mua-send-hook'." ;; Don't indent multipart sub-parts. (notmuch-show-indent-multipart nil)) ;; We don't want sigstatus buttons (an information leak and usually wrong anyway). - (letf (((symbol-function 'notmuch-crypto-insert-sigstatus-button) #'ignore) - ((symbol-function 'notmuch-crypto-insert-encstatus-button) #'ignore)) - (notmuch-show-insert-body original (plist-get original :body) 0) - (buffer-substring-no-properties (point-min) (point-max)))))) + (cl-letf (((symbol-function 'notmuch-crypto-insert-sigstatus-button) #'ignore) + ((symbol-function 'notmuch-crypto-insert-encstatus-button) #'ignore)) + (notmuch-show-insert-body original (plist-get original :body) 0) + (buffer-substring-no-properties (point-min) (point-max)))))) (set-mark (point)) (goto-char start) @@ -526,10 +527,9 @@ the From: address." ;; Create a buffer-local queue for tag changes triggered when sending the message (when notmuch-message-forwarded-tags (setq-local notmuch-message-queued-tag-changes - (loop for id in forward-queries - collect - (cons id - notmuch-message-forwarded-tags)))) + (cl-loop for id in forward-queries + collect + (cons id notmuch-message-forwarded-tags)))) ;; `message-forward-make-body' shows the User-agent header. Hide ;; it again. @@ -609,10 +609,10 @@ unencrypted. Really send? ")))) (run-hooks 'notmuch-mua-send-hook) (when (and (notmuch-mua-check-no-misplaced-secure-tag) (notmuch-mua-check-secure-tag-has-newline)) - (letf (((symbol-function 'message-do-fcc) #'notmuch-maildir-message-do-fcc)) - (if exit - (message-send-and-exit arg) - (message-send arg))))) + (cl-letf (((symbol-function 'message-do-fcc) #'notmuch-maildir-message-do-fcc)) + (if exit + (message-send-and-exit arg) + (message-send arg))))) (defun notmuch-mua-send-and-exit (&optional arg) (interactive "P") diff --git a/emacs/notmuch-parser.el b/emacs/notmuch-parser.el index bb0379c1..dc9fbe2f 100644 --- a/emacs/notmuch-parser.el +++ b/emacs/notmuch-parser.el @@ -21,7 +21,7 @@ ;;; Code: -(require 'cl) +(eval-when-compile (require 'cl-lib)) (defun notmuch-sexp-create-parser () "Return a new streaming S-expression parser. @@ -70,7 +70,7 @@ returns the value." ;; error to be consistent with all other code paths. (read (current-buffer)) ;; Go up a level and return an end token - (decf (notmuch-sexp--depth sp)) + (cl-decf (notmuch-sexp--depth sp)) (forward-char) 'end)) ((= (char-after) ?\() @@ -94,8 +94,8 @@ returns the value." (notmuch-sexp--partial-state sp))) ;; A complete value is available if we've ;; reached depth 0. - (depth (first new-state))) - (assert (>= depth 0)) + (depth (car new-state))) + (cl-assert (>= depth 0)) (if (= depth 0) ;; Reset partial parse state (setf (notmuch-sexp--partial-state sp) nil @@ -139,7 +139,7 @@ beginning of a list, throw invalid-read-syntax." (cond ((eobp) 'retry) ((= (char-after) ?\() (forward-char) - (incf (notmuch-sexp--depth sp)) + (cl-incf (notmuch-sexp--depth sp)) t) (t ;; Skip over the bad character like `read' does @@ -181,7 +181,7 @@ move point in the input buffer." (set (make-local-variable 'notmuch-sexp--state) 'begin)) (let (done) (while (not done) - (case notmuch-sexp--state + (cl-case notmuch-sexp--state (begin ;; Enter the list (if (eq (notmuch-sexp-begin-list notmuch-sexp--parser) 'retry) @@ -190,7 +190,7 @@ move point in the input buffer." (result ;; Parse a result (let ((result (notmuch-sexp-read notmuch-sexp--parser))) - (case result + (cl-case result (retry (setq done t)) (end (setq notmuch-sexp--state 'end)) (t (with-current-buffer result-buffer @@ -204,8 +204,4 @@ move point in the input buffer." (provide 'notmuch-parser) -;; Local Variables: -;; byte-compile-warnings: (not cl-functions) -;; End: - ;;; notmuch-parser.el ends here diff --git a/emacs/notmuch-pkg.el.tmpl b/emacs/notmuch-pkg.el.tmpl index 3eb0e04e..9d0999c1 100644 --- a/emacs/notmuch-pkg.el.tmpl +++ b/emacs/notmuch-pkg.el.tmpl @@ -3,4 +3,5 @@ "notmuch" %VERSION% "Emacs based front-end (MUA) for notmuch" - '((emacs "24"))) + '((emacs "24") + (cl-lib "0.6.1"))) diff --git a/emacs/notmuch-show.el b/emacs/notmuch-show.el index 079281c3..59931453 100644 --- a/emacs/notmuch-show.el +++ b/emacs/notmuch-show.el @@ -23,7 +23,10 @@ ;;; Code: -(eval-when-compile (require 'cl)) +(eval-when-compile + (require 'cl-lib) + (require 'pcase)) + (require 'mm-view) (require 'message) (require 'mm-decode) @@ -429,17 +432,16 @@ parsing fails." (setq p-name (replace-regexp-in-string "\\\\" "" p-name)) ;; Outer single and double quotes, which might be nested. - (loop - with start-of-loop - do (setq start-of-loop p-name) + (cl-loop with start-of-loop + do (setq start-of-loop p-name) - when (string-match "^\"\\(.*\\)\"$" p-name) - do (setq p-name (match-string 1 p-name)) + when (string-match "^\"\\(.*\\)\"$" p-name) + do (setq p-name (match-string 1 p-name)) - when (string-match "^'\\(.*\\)'$" p-name) - do (setq p-name (match-string 1 p-name)) + when (string-match "^'\\(.*\\)'$" p-name) + do (setq p-name (match-string 1 p-name)) - until (string= start-of-loop p-name))) + until (string= start-of-loop p-name))) ;; If the address is 'foo@bar.com ' then show just ;; 'foo@bar.com'. @@ -573,13 +575,13 @@ message at DEPTH in the current thread." ;; Recurse on sub-parts (let ((ctype (notmuch-split-content-type (downcase (plist-get part :content-type))))) - (cond ((equal (first ctype) "multipart") + (cond ((equal (car ctype) "multipart") (mapc (apply-partially #'notmuch-show--register-cids msg) (plist-get part :content))) ((equal ctype '("message" "rfc822")) (notmuch-show--register-cids msg - (first (plist-get (first (plist-get part :content)) :body))))))) + (car (plist-get (car (plist-get part :content)) :body))))))) (defun notmuch-show--get-cid-content (cid) "Return a list (CID-content content-type) or nil. @@ -590,8 +592,8 @@ enclosing angle brackets, a cid: prefix, or URL encoding. This will return nil if the CID is unknown or cannot be retrieved." (let ((descriptor (cdr (assoc cid notmuch-show--cids)))) (when descriptor - (let* ((msg (first descriptor)) - (part (second descriptor)) + (let* ((msg (car descriptor)) + (part (cadr descriptor)) ;; Request caching for this content, as some messages ;; reference the same cid: part many times (hundreds!). (content (notmuch-get-bodypart-binary @@ -616,8 +618,8 @@ will return nil if the CID is unknown or cannot be retrieved." (with-current-buffer w3m-current-buffer (notmuch-show--get-cid-content cid)))) (when content-and-type - (insert (first content-and-type)) - (second content-and-type)))) + (insert (car content-and-type)) + (cadr content-and-type)))) ;; MIME part renderers @@ -785,7 +787,7 @@ will return nil if the CID is unknown or cannot be retrieved." ;; is defined before it will be shadowed by the letf below. Otherwise the version ;; in enriched.el may be loaded a bit later and used instead (for the first time). (require 'enriched) - (letf (((symbol-function 'enriched-decode-display-prop) + (cl-letf (((symbol-function 'enriched-decode-display-prop) (lambda (start end &optional param) (list start end)))) (notmuch-show-insert-part-*/* msg part content-type nth depth button)))) @@ -843,7 +845,7 @@ will return nil if the CID is unknown or cannot be retrieved." ;; shr strips the "cid:" part of URL, but doesn't ;; URL-decode it (see RFC 2392). (let ((cid (url-unhex-string url))) - (first (notmuch-show--get-cid-content cid)))))) + (car (notmuch-show--get-cid-content cid)))))) (shr-insert-document dom) t)) @@ -873,15 +875,16 @@ will return nil if the CID is unknown or cannot be retrieved." (defun notmuch-show-insert-bodypart-internal (msg part content-type nth depth button) ;; Run the handlers until one of them succeeds. - (loop for handler in (notmuch-show-handlers-for content-type) - until (condition-case err - (funcall handler msg part content-type nth depth button) - ;; Specifying `debug' here lets the debugger run if - ;; `debug-on-error' is non-nil. - ((debug error) - (insert "!!! Bodypart handler `" (prin1-to-string handler) "' threw an error:\n" - "!!! " (error-message-string err) "\n") - nil)))) + (cl-loop for handler in (notmuch-show-handlers-for content-type) + until (condition-case err + (funcall handler msg part content-type nth depth button) + ;; Specifying `debug' here lets the debugger run if + ;; `debug-on-error' is non-nil. + ((debug error) + (insert "!!! Bodypart handler `" (prin1-to-string handler) + "' threw an error:\n" + "!!! " (error-message-string err) "\n") + nil)))) (defun notmuch-show-create-part-overlays (button beg end) "Add an overlay to the part between BEG and END" @@ -907,13 +910,15 @@ will return nil if the CID is unknown or cannot be retrieved." ;; watch out for sticky specs of t, which means all properties are ;; front-sticky/rear-nonsticky. (notmuch-map-text-property beg end 'front-sticky - (lambda (v) (if (listp v) - (pushnew :notmuch-part v) - v))) + (lambda (v) + (if (listp v) + (cl-pushnew :notmuch-part v) + v))) (notmuch-map-text-property beg end 'rear-nonsticky - (lambda (v) (if (listp v) - (pushnew :notmuch-part v) - v)))) + (lambda (v) + (if (listp v) + (cl-pushnew :notmuch-part v) + v)))) (defun notmuch-show-lazy-part (part-args button) ;; Insert the lazy part after the button for the part. We would just @@ -941,7 +946,7 @@ will return nil if the CID is unknown or cannot be retrieved." (indent-rigidly part-beg part-end (* notmuch-show-indent-messages-width depth))) (goto-char part-end) (delete-char 1) - (notmuch-show-record-part-information (second part-args) + (notmuch-show-record-part-information (cadr part-args) (button-start button) part-end) ;; Create the overlay. If the lazy-part turned out to be empty/not @@ -1037,7 +1042,7 @@ is t, hide the part initially and show the button." ;; Register all content IDs for this message. According to RFC ;; 2392, content IDs are *global*, but it's okay if an MUA treats ;; them as only global within a message. - (notmuch-show--register-cids msg (first body)) + (notmuch-show--register-cids msg (car body)) (mapc (lambda (part) (notmuch-show-insert-bodypart msg part depth)) body)) @@ -1220,13 +1225,13 @@ buttons for a corresponding notmuch search." (url-unhex-string (match-string 0 mid-cid))))) (push (list (match-beginning 0) (match-end 0) (notmuch-id-to-query mid)) links))) - (dolist (link links) + (pcase-dolist (`(,beg ,end ,link) links) ;; Remove the overlay created by goto-address-mode - (remove-overlays (first link) (second link) 'goto-address t) - (make-text-button (first link) (second link) + (remove-overlays beg end 'goto-address t) + (make-text-button beg end :type 'notmuch-button-type 'action `(lambda (arg) - (notmuch-show ,(third link) current-prefix-arg)) + (notmuch-show ,link current-prefix-arg)) 'follow-link t 'help-echo "Mouse-1, RET: search for this message" 'face goto-address-mail-face))))) @@ -1387,9 +1392,9 @@ This includes: (defun notmuch-show-goto-message (msg-id) "Go to message with msg-id." (goto-char (point-min)) - (unless (loop if (string= msg-id (notmuch-show-get-message-id)) - return t - until (not (notmuch-show-goto-message-next))) + (unless (cl-loop if (string= msg-id (notmuch-show-get-message-id)) + return t + until (not (notmuch-show-goto-message-next))) (goto-char (point-min)) (message "Message-id not found.")) (notmuch-show-message-adjust)) @@ -1406,9 +1411,9 @@ This includes: ;; Open those that were open. (goto-char (point-min)) - (loop do (notmuch-show-message-visible (notmuch-show-get-message-properties) - (member (notmuch-show-get-message-id) open)) - until (not (notmuch-show-goto-message-next))) + (cl-loop do (notmuch-show-message-visible (notmuch-show-get-message-properties) + (member (notmuch-show-get-message-id) open)) + until (not (notmuch-show-goto-message-next))) (dolist (win-msg-pair win-msg-alist) (with-selected-window (car win-msg-pair) @@ -1620,8 +1625,8 @@ of the current message." effects." (save-excursion (goto-char (point-min)) - (loop do (funcall function) - while (notmuch-show-goto-message-next)))) + (cl-loop do (funcall function) + while (notmuch-show-goto-message-next)))) ;; Functions relating to the visibility of messages and their ;; components. @@ -2177,9 +2182,9 @@ argument, hide all of the messages." (interactive) (save-excursion (goto-char (point-min)) - (loop do (notmuch-show-message-visible (notmuch-show-get-message-properties) - (not current-prefix-arg)) - until (not (notmuch-show-goto-message-next)))) + (cl-loop do (notmuch-show-message-visible (notmuch-show-get-message-properties) + (not current-prefix-arg)) + until (not (notmuch-show-goto-message-next)))) (force-window-update)) (defun notmuch-show-next-button () diff --git a/emacs/notmuch-tag.el b/emacs/notmuch-tag.el index 0500927d..bc83e3de 100644 --- a/emacs/notmuch-tag.el +++ b/emacs/notmuch-tag.el @@ -24,8 +24,12 @@ ;;; Code: ;; -(require 'cl) +(require 'cl-lib) +(eval-when-compile + (require 'pcase)) + (require 'crm) + (require 'notmuch-lib) (declare-function notmuch-search-tag "notmuch" tag-changes) @@ -277,10 +281,10 @@ This can be used with `notmuch-tag-format-image-data'." (save-match-data ;; Don't use assoc-default since there's no way to distinguish a ;; missing key from a present key with a null cdr. - (assoc* tag format-alist - :test (lambda (tag key) - (and (eq (string-match key tag) 0) - (= (match-end 0) (length tag))))))) + (cl-assoc tag format-alist + :test (lambda (tag key) + (and (eq (string-match key tag) 0) + (= (match-end 0) (length tag))))))) (defun notmuch-tag--do-format (tag formatted-tag formats) "Apply a tag-formats entry to TAG." @@ -315,7 +319,7 @@ changed (the normal case) are shown using formats from (formatted-tag (gethash (cons tag tag-state) notmuch-tag--format-cache 'missing))) (when (eq formatted-tag 'missing) (let ((base (notmuch-tag--get-formats tag notmuch-tag-formats)) - (over (case tag-state + (over (cl-case tag-state (deleted (notmuch-tag--get-formats tag notmuch-tag-deleted-formats)) (added (notmuch-tag--get-formats @@ -436,7 +440,7 @@ from TAGS if present." (dolist (tag-change tag-changes) (let ((op (string-to-char tag-change)) (tag (unless (string= tag-change "") (substring tag-change 1)))) - (case op + (cl-case op (?+ (unless (member tag result-tags) (push tag result-tags))) (?- (setq result-tags (delete tag result-tags))) @@ -511,22 +515,21 @@ and vice versa." ;; REVERSE is specified. (interactive "P") (let (action-map) - (dolist (binding notmuch-tagging-keys) - (let* ((tag-function (case major-mode + (pcase-dolist (`(,key ,tag ,name) notmuch-tagging-keys) + (let* ((tag-function (cl-case major-mode (notmuch-search-mode #'notmuch-search-tag) (notmuch-show-mode #'notmuch-show-tag) (notmuch-tree-mode #'notmuch-tree-tag))) - (key (first binding)) - (forward-tag-change (if (symbolp (second binding)) - (symbol-value (second binding)) - (second binding))) + (tag (if (symbolp tag) + (symbol-value tag) + tag)) (tag-change (if reverse - (notmuch-tag-change-list forward-tag-change 't) - forward-tag-change)) - (name (or (and (not (string= (third binding) "")) - (third binding)) - (and (symbolp (second binding)) - (symbol-name (second binding))))) + (notmuch-tag-change-list tag 't) + tag)) + (name (or (and (not (string= name "")) + name) + (and (symbolp name) + (symbol-name name)))) (name-string (if name (if reverse (concat "Reverse " name) name) @@ -546,7 +549,3 @@ and vice versa." ;; (provide 'notmuch-tag) - -;; Local Variables: -;; byte-compile-warnings: (not cl-functions) -;; End: diff --git a/emacs/notmuch-tree.el b/emacs/notmuch-tree.el index e5c23de2..254664c4 100644 --- a/emacs/notmuch-tree.el +++ b/emacs/notmuch-tree.el @@ -24,6 +24,8 @@ ;;; Code: +(eval-when-compile (require 'cl-lib)) + (require 'mail-parse) (require 'notmuch-lib) @@ -32,7 +34,6 @@ (require 'notmuch-tag) (require 'notmuch-parser) -(eval-when-compile (require 'cl)) (declare-function notmuch-search "notmuch" (&optional query oldest-first target-thread target-line)) (declare-function notmuch-call-notmuch-process "notmuch" (&rest args)) (declare-function notmuch-read-query "notmuch" (prompt)) @@ -721,10 +722,10 @@ found or nil if not." and call FUNCTION for side effects." (save-excursion (notmuch-tree-thread-top) - (loop collect (funcall function) - do (forward-line) - while (and (notmuch-tree-get-message-properties) - (not (notmuch-tree-get-prop :first)))))) + (cl-loop collect (funcall function) + do (forward-line) + while (and (notmuch-tree-get-message-properties) + (not (notmuch-tree-get-prop :first)))))) (defun notmuch-tree-get-messages-ids-thread-search () "Return a search string for all message ids of messages in the current thread." @@ -905,10 +906,11 @@ message together with all its descendents." (defun notmuch-tree-insert-thread (thread depth tree-status) "Insert the collection of sibling sub-threads THREAD at depth DEPTH in the current forest." (let ((n (length thread))) - (loop for tree in thread - for count from 1 to n - - do (notmuch-tree-insert-tree tree depth tree-status (eq count 1) (eq count n))))) + (cl-loop for tree in thread + for count from 1 to n + do (notmuch-tree-insert-tree tree depth tree-status + (eq count 1) + (eq count n))))) (defun notmuch-tree-insert-forest-thread (forest-thread) "Insert a single complete thread." diff --git a/emacs/notmuch.el b/emacs/notmuch.el index f5f03244..a980c7a2 100644 --- a/emacs/notmuch.el +++ b/emacs/notmuch.el @@ -65,7 +65,8 @@ ;; ;;; Code: -(eval-when-compile (require 'cl)) +(eval-when-compile (require 'cl-lib)) + (require 'mm-view) (require 'message) @@ -132,7 +133,7 @@ there will be called at other points of notmuch execution." (or (equal (car disposition) "attachment") (and (equal (car disposition) "inline") (assq 'filename disposition))) - (incf count)))) + (cl-incf count)))) mm-handle) count)) @@ -429,14 +430,13 @@ character position of the beginning of each result that overlaps the region between points BEG and END. As a special case, if (= BEG END), FN will be applied to the result containing point BEG." - - (lexical-let ((pos (notmuch-search-result-beginning beg)) - ;; End must be a marker in case fn changes the - ;; text. - (end (copy-marker end)) - ;; Make sure we examine at least one result, even if - ;; (= beg end). - (first t)) + (let ((pos (notmuch-search-result-beginning beg)) + ;; End must be a marker in case fn changes the + ;; text. + (end (copy-marker end)) + ;; Make sure we examine at least one result, even if + ;; (= beg end). + (first t)) ;; We have to be careful if the region extends beyond the results. ;; In this case, pos could be null or there could be no result at ;; pos. @@ -478,10 +478,10 @@ is nil, include both matched and unmatched messages. If there are no messages in the region then return nil." (let ((query-list nil) (all (not only-matched))) (dolist (queries (notmuch-search-properties-in-region :query beg end)) - (when (first queries) - (push (first queries) query-list)) - (when (and all (second queries)) - (push (second queries) query-list))) + (when (car queries) + (push (car queries) query-list)) + (when (and all (cadr queries)) + (push (cadr queries) query-list))) (when query-list (concat "(" (mapconcat 'identity query-list ") or (") ")")))) @@ -568,12 +568,11 @@ thread." "Prompt for tag changes for the current thread or region. Returns (TAG-CHANGES REGION-BEGIN REGION-END)." - (let* ((region (notmuch-interactive-region)) - (beg (first region)) (end (second region)) - (prompt (if (= beg end) "Tag thread" "Tag region"))) - (cons (notmuch-read-tag-changes - (notmuch-search-get-tags-region beg end) prompt initial-input) - region))) + (pcase-let ((`(,beg ,end) (notmuch-interactive-region))) + (list (notmuch-read-tag-changes (notmuch-search-get-tags-region beg end) + (if (= beg end) "Tag thread" "Tag region") + initial-input) + beg end))) (defun notmuch-search-tag (tag-changes &optional beg end only-matched) "Change tags for the currently selected thread or region. @@ -891,12 +890,13 @@ See `notmuch-tag' for information on the format of TAG-CHANGES." (let* ((saved-search (let (longest (longest-length 0)) - (loop for tuple in notmuch-saved-searches - if (let ((quoted-query (regexp-quote (notmuch-saved-search-get tuple :query)))) - (and (string-match (concat "^" quoted-query) query) - (> (length (match-string 0 query)) - longest-length))) - do (setq longest tuple)) + (cl-loop for tuple in notmuch-saved-searches + if (let ((quoted-query + (regexp-quote (notmuch-saved-search-get tuple :query)))) + (and (string-match (concat "^" quoted-query) query) + (> (length (match-string 0 query)) + longest-length))) + do (setq longest tuple)) longest)) (saved-search-name (notmuch-saved-search-get saved-search :name)) (saved-search-query (notmuch-saved-search-get saved-search :query))) @@ -917,7 +917,7 @@ See `notmuch-tag' for information on the format of TAG-CHANGES." "Read a notmuch-query from the minibuffer with completion. PROMPT is the string to prompt with." - (lexical-let* + (let* ((all-tags (mapcar (lambda (tag) (notmuch-escape-boolean-term tag)) (process-lines notmuch-command "search" "--output=tags" "*"))) @@ -928,7 +928,7 @@ PROMPT is the string to prompt with." (mapcar (lambda (tag) (concat "is:" tag)) all-tags) (mapcar (lambda (mimetype) (concat "mimetype:" mimetype)) (mailcap-mime-types))))) (let ((keymap (copy-keymap minibuffer-local-map)) - (current-query (case major-mode + (current-query (cl-case major-mode (notmuch-search-mode (notmuch-search-get-query)) (notmuch-show-mode (notmuch-show-get-query)) (notmuch-tree-mode (notmuch-tree-get-query)))) @@ -1114,9 +1114,9 @@ notmuch buffers exist, run `notmuch'." (bury-buffer)) ;; Find the first notmuch buffer. - (setq first (loop for buffer in (buffer-list) - if (notmuch-interesting-buffer buffer) - return buffer)) + (setq first (cl-loop for buffer in (buffer-list) + if (notmuch-interesting-buffer buffer) + return buffer)) (if first ;; If the first one we found is any other than the starting diff --git a/test/T450-emacs-show.sh b/test/T450-emacs-show.sh index de1755d2..cca56ca3 100755 --- a/test/T450-emacs-show.sh +++ b/test/T450-emacs-show.sh @@ -177,7 +177,7 @@ test_emacs "(let ((notmuch-command \"$PWD/notmuch_fail\")) (let ((inhibit-read-only t)) (erase-buffer))) (condition-case err (notmuch-show \"*\") - (error (message \"%s\" (second err)))) + (error (message \"%s\" (cadr err)))) (notmuch-test-wait) (with-current-buffer \"*Messages*\" (test-output \"MESSAGES\")) diff --git a/test/emacs-attachment-warnings.el b/test/emacs-attachment-warnings.el index a3067b14..a23692d7 100644 --- a/test/emacs-attachment-warnings.el +++ b/test/emacs-attachment-warnings.el @@ -1,3 +1,4 @@ +(require 'cl-lib) (require 'notmuch-mua) (defun attachment-check-test (&optional fn) @@ -12,7 +13,8 @@ Return `t' if the message would be sent, otherwise `nil'" (condition-case nil ;; Force `y-or-n-p' to always return `nil', as if the user ;; pressed "n". - (letf (((symbol-function 'y-or-n-p) (lambda (&rest args) nil))) + (cl-letf (((symbol-function 'y-or-n-p) + (lambda (&rest args) nil))) (notmuch-mua-attachment-check) t) ('error nil)) diff --git a/test/test-lib.el b/test/test-lib.el index 9946010b..3ae7a090 100644 --- a/test/test-lib.el +++ b/test/test-lib.el @@ -20,7 +20,7 @@ ;; ;; Authors: Dmitry Kurochkin -(require 'cl) ;; This code is generally used uncompiled. +(require 'cl-lib) ;; `read-file-name' by default uses `completing-read' function to read ;; user input. It does not respect `standard-input' variable which we @@ -116,10 +116,10 @@ nothing." (defadvice notmuch-search-process-filter (around pessimal activate disable) "Feed notmuch-search-process-filter one character at a time." (let ((string (ad-get-arg 1))) - (loop for char across string - do (progn - (ad-set-arg 1 (char-to-string char)) - ad-do-it)))) + (cl-loop for char across string + do (progn + (ad-set-arg 1 (char-to-string char)) + ad-do-it)))) (defun notmuch-test-mark-links () "Enclose links in the current buffer with << and >>." @@ -162,10 +162,10 @@ nothing." ;; reporting differing elements of OUTPUT and EXPECTED ;; pairwise. This is expected to make analysis of failures ;; simpler. - (apply #'concat (loop for o in output - for e in expected - if (not (equal o e)) - collect (notmuch-test-report-unexpected o e)))) + (apply #'concat (cl-loop for o in output + for e in expected + if (not (equal o e)) + collect (notmuch-test-report-unexpected o e)))) (t (notmuch-test-report-unexpected output expected))))) From ad9c2e91a012920bebfe70bc472d44678abc3259 Mon Sep 17 00:00:00 2001 From: David Bremner Date: Mon, 27 Apr 2020 09:24:22 -0300 Subject: [PATCH 130/427] util/zlib-extra: de-inline gzerror_str It turns out that putting inline functions in C header files is not a good idea, and can cause linking problems if the compiler decides not to inline them. In principle this is solvable by using a "static inline" declaration, but this potentially makes a copy in every compilation unit. Since we don't actually care about the performance of this function, just use a non-inline function. --- util/zlib-extra.c | 7 +++++++ util/zlib-extra.h | 4 ++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/util/zlib-extra.c b/util/zlib-extra.c index 2d2d2414..3a75e504 100644 --- a/util/zlib-extra.c +++ b/util/zlib-extra.c @@ -85,3 +85,10 @@ gz_error_string (util_status_t status, gzFile file) else return util_error_string (status); } + +const char * +gzerror_str(gzFile file) +{ + int dummy; + return gzerror (file, &dummy); +} diff --git a/util/zlib-extra.h b/util/zlib-extra.h index 296dc914..e9925c98 100644 --- a/util/zlib-extra.h +++ b/util/zlib-extra.h @@ -29,8 +29,8 @@ gz_error_string (util_status_t status, gzFile stream); /* Call gzerror with a dummy errno argument, the docs don't promise to * support the NULL case */ -inline const char * -gzerror_str(gzFile file) { int dummy; return gzerror (file, &dummy); } +const char * +gzerror_str(gzFile file); #ifdef __cplusplus } From 055e0917d73619bb78c6ed9a6068422f40967dcd Mon Sep 17 00:00:00 2001 From: Daniel Kahn Gillmor Date: Tue, 28 Apr 2020 14:57:09 -0400 Subject: [PATCH 131/427] tests: move add_gpgsm_home to test-lib.sh This allows us to test S/MIME messages in other tests. Signed-off-by: Daniel Kahn Gillmor --- test/T355-smime.sh | 13 ------------- test/test-lib.sh | 13 +++++++++++++ 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/test/T355-smime.sh b/test/T355-smime.sh index 11a4d6cd..84be515a 100755 --- a/test/T355-smime.sh +++ b/test/T355-smime.sh @@ -3,19 +3,6 @@ test_description='S/MIME signature verification and decryption' . $(dirname "$0")/test-lib.sh || exit 1 -add_gpgsm_home () -{ - local fpr - [ -d ${GNUPGHOME} ] && return - _gnupg_exit () { gpgconf --kill all 2>/dev/null || true; } - at_exit_function _gnupg_exit - mkdir -m 0700 "$GNUPGHOME" - gpgsm --no-tty --no-common-certs-import --disable-dirmngr --import < $NOTMUCH_SRCDIR/test/smime/test.crt >"$GNUPGHOME"/import.log 2>&1 - fpr=$(gpgsm --list-key test_suite@notmuchmail.org | sed -n 's/.*fingerprint: //p') - echo "$fpr S relax" >> $GNUPGHOME/trustlist.txt - test_debug "cat $GNUPGHOME/import.log" -} - test_require_external_prereq openssl test_require_external_prereq gpgsm diff --git a/test/test-lib.sh b/test/test-lib.sh index 7f8a3a4d..6a62b5c1 100644 --- a/test/test-lib.sh +++ b/test/test-lib.sh @@ -129,6 +129,19 @@ add_gnupg_home () printf '%s:6:\n' "$FINGERPRINT" | gpg --quiet --batch --no-tty --import-ownertrust } +add_gpgsm_home () +{ + local fpr + [ -d ${GNUPGHOME} ] && return + _gnupg_exit () { gpgconf --kill all 2>/dev/null || true; } + at_exit_function _gnupg_exit + mkdir -m 0700 "$GNUPGHOME" + gpgsm --no-tty --no-common-certs-import --disable-dirmngr --import < $NOTMUCH_SRCDIR/test/smime/test.crt >"$GNUPGHOME"/import.log 2>&1 + fpr=$(gpgsm --list-key test_suite@notmuchmail.org | sed -n 's/.*fingerprint: //p') + echo "$fpr S relax" >> $GNUPGHOME/trustlist.txt + test_debug "cat $GNUPGHOME/import.log" +} + # Each test should start with something like this, after copyright notices: # # test_description='Description of this test... From 6d843b8199b7f20c5fb9ebfb5e874467fa7ff3fe Mon Sep 17 00:00:00 2001 From: Daniel Kahn Gillmor Date: Tue, 28 Apr 2020 14:57:10 -0400 Subject: [PATCH 132/427] tests/smime: Always use --batch with gpgsm GnuPG's gpgsm, like gpg, should always be used with --batch when it is invoked in a non-interactive environment. Signed-off-by: Daniel Kahn Gillmor --- test/test-lib.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/test-lib.sh b/test/test-lib.sh index 6a62b5c1..d4fcea5a 100644 --- a/test/test-lib.sh +++ b/test/test-lib.sh @@ -136,8 +136,8 @@ add_gpgsm_home () _gnupg_exit () { gpgconf --kill all 2>/dev/null || true; } at_exit_function _gnupg_exit mkdir -m 0700 "$GNUPGHOME" - gpgsm --no-tty --no-common-certs-import --disable-dirmngr --import < $NOTMUCH_SRCDIR/test/smime/test.crt >"$GNUPGHOME"/import.log 2>&1 - fpr=$(gpgsm --list-key test_suite@notmuchmail.org | sed -n 's/.*fingerprint: //p') + gpgsm --batch --no-tty --no-common-certs-import --disable-dirmngr --import < $NOTMUCH_SRCDIR/test/smime/test.crt >"$GNUPGHOME"/import.log 2>&1 + fpr=$(gpgsm --batch --list-key test_suite@notmuchmail.org | sed -n 's/.*fingerprint: //p') echo "$fpr S relax" >> $GNUPGHOME/trustlist.txt test_debug "cat $GNUPGHOME/import.log" } From 1f21465205f92f68d25ef039b1e111bc26c70b4a Mon Sep 17 00:00:00 2001 From: Daniel Kahn Gillmor Date: Thu, 30 Apr 2020 15:33:29 -0400 Subject: [PATCH 133/427] tests/smime: Include the Sample LAMPS Certificate Authority This CA is useful for test suites and the like, but is not an actually-secure CA, because its secret key material is also published. I plan to use it for its intended purpose in the notmuch test suite. It was copied from this Internet Draft: https://tools.ietf.org/id/draft-dkg-lamps-samples-01.html#name-certificate-authority-certi Signed-off-by: Daniel Kahn Gillmor --- test/smime/README | 2 ++ test/smime/ca.crt | 20 ++++++++++++++++++++ test/test-lib.sh | 2 ++ 3 files changed, 24 insertions(+) create mode 100644 test/smime/ca.crt diff --git a/test/smime/README b/test/smime/README index 46211922..6f276398 100644 --- a/test/smime/README +++ b/test/smime/README @@ -5,3 +5,5 @@ key+cert.pem: cert + unencryped private % gpgsm --import test.crt % gpgsm --export-private-key-p12 -out foo.p12 (no passphrase) % openssl pkcs12 -in ns.p12 -clcerts -nodes > key+cert.pem + +ca.crt: from https://tools.ietf.org/id/draft-dkg-lamps-samples-01.html#name-certificate-authority-certi diff --git a/test/smime/ca.crt b/test/smime/ca.crt new file mode 100644 index 00000000..b33d087f --- /dev/null +++ b/test/smime/ca.crt @@ -0,0 +1,20 @@ +-----BEGIN CERTIFICATE----- +MIIDLTCCAhWgAwIBAgIULXcNXGI2bZp38sV7cF6VcQfnKDwwDQYJKoZIhvcNAQEN +BQAwLTErMCkGA1UEAxMiU2FtcGxlIExBTVBTIENlcnRpZmljYXRlIEF1dGhvcml0 +eTAgFw0xOTExMjAwNjU0MThaGA8yMDUyMDkyNzA2NTQxOFowLTErMCkGA1UEAxMi +U2FtcGxlIExBTVBTIENlcnRpZmljYXRlIEF1dGhvcml0eTCCASIwDQYJKoZIhvcN +AQEBBQADggEPADCCAQoCggEBAMUfZ8+NYSh6h36zQcXBo5B6ficAcBJ1f3aLxyN8 +QXB83XuP8aDRWQ9uJvJpQkWVH4zx96/E/zI0t0lDMYtZNqra16h+gxbHJgoq2pRw +RCOiyYu/p2vzvvZ1dtFTMc/mIigjA/73kokui62j1EFy//fNVIihkVS3rAweq+fI +8qJHSMhdc2aYa9wOP0eGe/HTiDYgT4L4f2HTGMGGwQgj1vub0gpR4YHmNqr0GyEA +63mHUQUZpnmN1FEl+nVFA5Ntu4uF++qf/tkTji89/eXYBdKX2yUdTeTIKoCI65IL +EXxezjTc8aFjf/8E0aWGVZR/DtCsjWOh/s/mV7n/YPyb4+ECAwEAAaNDMEEwDwYD +VR0TAQH/BAUwAwEB/zAPBgNVHQ8BAf8EBQMDBwYAMB0GA1UdDgQWBBS3Uk1zwIg9 +ssN6WgzzlPf3gKJ32zANBgkqhkiG9w0BAQ0FAAOCAQEALsU91Bmhc6EgCNr7inY2 +2gYPnosJ+kZ1eC0hvHIK9e0Tx74RmhTOe8M2C9YXQKehHpRaX+DLcjup6scoH/bT +u0THbmzeOy29TTiFcyV9BK+SEKQWW4s98Fwdk9fPWcflHtYvqxjooAV3vHbt6Xmp +KrKDz/jdg7t0ptI4zSqAf3wNppiJoswlOHBUnH2W1MIYkWQ4jYj5socblVlklHOr +ykKUiEZAbjU+C1+0FhT4HgLjBB9R4H1H0JRKsggWiZBBJ6UpN0dTN4iD0mDVa0jy +sJqqWnIViy/xaSDcNaWJmU3o2KmkMkdpinoJ5uLkAHQqXjFaujdU1PkufeA7v3uG +Rw== +-----END CERTIFICATE----- diff --git a/test/test-lib.sh b/test/test-lib.sh index d4fcea5a..1ffedb25 100644 --- a/test/test-lib.sh +++ b/test/test-lib.sh @@ -139,6 +139,8 @@ add_gpgsm_home () gpgsm --batch --no-tty --no-common-certs-import --disable-dirmngr --import < $NOTMUCH_SRCDIR/test/smime/test.crt >"$GNUPGHOME"/import.log 2>&1 fpr=$(gpgsm --batch --list-key test_suite@notmuchmail.org | sed -n 's/.*fingerprint: //p') echo "$fpr S relax" >> $GNUPGHOME/trustlist.txt + gpgsm --quiet --batch --no-tty --no-common-certs-import --disable-dirmngr --import < $NOTMUCH_SRCDIR/test/smime/ca.crt + echo "4D:E0:FF:63:C0:E9:EC:01:29:11:C8:7A:EE:DA:3A:9A:7F:6E:C1:0D S" >> "$GNUPGHOME/trustlist.txt" test_debug "cat $GNUPGHOME/import.log" } From f7921e6e1cdbfc1d16488f5b08cf3c9e2bd36b1d Mon Sep 17 00:00:00 2001 From: Daniel Kahn Gillmor Date: Tue, 28 Apr 2020 14:57:12 -0400 Subject: [PATCH 134/427] tests/smime: consistently quote $GNUPGHOME Signed-off-by: Daniel Kahn Gillmor --- test/test-lib.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/test-lib.sh b/test/test-lib.sh index 1ffedb25..31f37ed7 100644 --- a/test/test-lib.sh +++ b/test/test-lib.sh @@ -132,13 +132,13 @@ add_gnupg_home () add_gpgsm_home () { local fpr - [ -d ${GNUPGHOME} ] && return + [ -d "$GNUPGHOME" ] && return _gnupg_exit () { gpgconf --kill all 2>/dev/null || true; } at_exit_function _gnupg_exit mkdir -m 0700 "$GNUPGHOME" gpgsm --batch --no-tty --no-common-certs-import --disable-dirmngr --import < $NOTMUCH_SRCDIR/test/smime/test.crt >"$GNUPGHOME"/import.log 2>&1 fpr=$(gpgsm --batch --list-key test_suite@notmuchmail.org | sed -n 's/.*fingerprint: //p') - echo "$fpr S relax" >> $GNUPGHOME/trustlist.txt + echo "$fpr S relax" >> "$GNUPGHOME/trustlist.txt" gpgsm --quiet --batch --no-tty --no-common-certs-import --disable-dirmngr --import < $NOTMUCH_SRCDIR/test/smime/ca.crt echo "4D:E0:FF:63:C0:E9:EC:01:29:11:C8:7A:EE:DA:3A:9A:7F:6E:C1:0D S" >> "$GNUPGHOME/trustlist.txt" test_debug "cat $GNUPGHOME/import.log" From 2e351d10c2aba786656715e8334fc2296e22527d Mon Sep 17 00:00:00 2001 From: Daniel Kahn Gillmor Date: Tue, 28 Apr 2020 14:57:13 -0400 Subject: [PATCH 135/427] tests/smime: Use gpgsm instead of openssl for mml creation of S/MIME msgs The documentation for message mode clearly states that EasyPG (which uses GnuPG) is the default and recommended way to use S/MIME with mml-secure: [0] https://www.gnu.org/software/emacs/manual/html_node/message/Using-S_002fMIME.html To ensure that this mode works, we just need to import the secret key in question into gpgsm in addition to the public key. gpgsm should be able pick the right keys+certificates to use based on To/From headers, so we don't have to specify anything manually in the #secure mml tag. The import process from the OpenSSL-preferred form (cert+secretkey) is rather ugly, because gpgsm wants to see a PKCS#12 object when importing secret keys. Note that EasyPG generates the more modern Content-Type: application/pkcs7-signature instead of application/x-pkcs7-signature for the detached signature. We are also obliged to manually set gpgsm's include-certs setting to 1 because gpgsm defaults to send "everything but the root cert". In our weird test case, the certificate we're using is self-signed, so it *is* the root cert, which means that gpgsm doesn't include it by default. Setting it to 1 forces inclusion of the signer's cert, which satisfies openssl's smime subcommand. See https://dev.gnupg.org/T4878 for more details. Signed-off-by: Daniel Kahn Gillmor --- test/T355-smime.sh | 4 ++-- test/test-lib.el | 10 ---------- test/test-lib.sh | 6 +++++- 3 files changed, 7 insertions(+), 13 deletions(-) diff --git a/test/T355-smime.sh b/test/T355-smime.sh index 84be515a..9debf2da 100755 --- a/test/T355-smime.sh +++ b/test/T355-smime.sh @@ -24,7 +24,7 @@ test_begin_subtest "emacs delivery of S/MIME encrypted + signed message" test_expect_success \ 'emacs_fcc_message \ "test encrypted message 001" \ - "<#secure method=smime mode=signencrypt keyfile=\\\"test_suite.pem\\\" certfile=\\\"test_suite.pem\\\">\nThis is a test encrypted message.\n"' + "<#secure method=smime mode=signencrypt>\nThis is a test encrypted message.\n"' test_begin_subtest "Signature verification (openssl)" notmuch show --format=raw subject:"test signed message 001" |\ @@ -65,7 +65,7 @@ expected='[[[{"id": "XXXXX", "content-disposition": "attachment", "content-length": "NONZERO", "content-transfer-encoding": "base64", - "content-type": "application/x-pkcs7-signature", + "content-type": "application/pkcs7-signature", "filename": "smime.p7s"}]}]}, []]]]' test_expect_equal_json \ diff --git a/test/test-lib.el b/test/test-lib.el index 3ae7a090..b47b388e 100644 --- a/test/test-lib.el +++ b/test/test-lib.el @@ -193,13 +193,3 @@ nothing." ;; environments (setq mm-text-html-renderer 'html2text) - -;; Set some variables for S/MIME tests. - -(setq smime-keys '(("" "test_suite.pem" nil))) - -(setq mml-smime-use 'openssl) - -;; all test keys are without passphrase -(eval-after-load 'smime - '(defun smime-ask-passphrase (cache) nil)) diff --git a/test/test-lib.sh b/test/test-lib.sh index 31f37ed7..ac1b9315 100644 --- a/test/test-lib.sh +++ b/test/test-lib.sh @@ -136,11 +136,15 @@ add_gpgsm_home () _gnupg_exit () { gpgconf --kill all 2>/dev/null || true; } at_exit_function _gnupg_exit mkdir -m 0700 "$GNUPGHOME" - gpgsm --batch --no-tty --no-common-certs-import --disable-dirmngr --import < $NOTMUCH_SRCDIR/test/smime/test.crt >"$GNUPGHOME"/import.log 2>&1 + openssl pkcs12 -export -passout pass: -inkey "$NOTMUCH_SRCDIR/test/smime/key+cert.pem" \ + < "$NOTMUCH_SRCDIR/test/smime/test.crt" | \ + gpgsm --batch --no-tty --no-common-certs-import --pinentry-mode=loopback --passphrase-fd 3 \ + --disable-dirmngr --import >"$GNUPGHOME"/import.log 2>&1 3<<<'' fpr=$(gpgsm --batch --list-key test_suite@notmuchmail.org | sed -n 's/.*fingerprint: //p') echo "$fpr S relax" >> "$GNUPGHOME/trustlist.txt" gpgsm --quiet --batch --no-tty --no-common-certs-import --disable-dirmngr --import < $NOTMUCH_SRCDIR/test/smime/ca.crt echo "4D:E0:FF:63:C0:E9:EC:01:29:11:C8:7A:EE:DA:3A:9A:7F:6E:C1:0D S" >> "$GNUPGHOME/trustlist.txt" + echo include-certs::1 | gpgconf --output /dev/null --change-options gpgsm test_debug "cat $GNUPGHOME/import.log" } From 7eaac52ddbb7c95b7e92c24d84eecdb3e9691021 Mon Sep 17 00:00:00 2001 From: Daniel Kahn Gillmor Date: Tue, 28 Apr 2020 14:57:14 -0400 Subject: [PATCH 136/427] tests/smime: avoid copying the key+cert.pem around No functional change. We no longer need to identify the key and cert to mml-mode when sending an S/MIME message, so making a copy of key+cert.pem to test_suite.pem is superfluous. Get rid of the extra file. Signed-off-by: Daniel Kahn Gillmor --- test/T355-smime.sh | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/test/T355-smime.sh b/test/T355-smime.sh index 9debf2da..e92a7b16 100755 --- a/test/T355-smime.sh +++ b/test/T355-smime.sh @@ -6,9 +6,7 @@ test_description='S/MIME signature verification and decryption' test_require_external_prereq openssl test_require_external_prereq gpgsm -cp $NOTMUCH_SRCDIR/test/smime/key+cert.pem test_suite.pem - -FINGERPRINT=$(openssl x509 -fingerprint -in test_suite.pem -noout | sed -e 's/^.*=//' -e s/://g) +FINGERPRINT=$(openssl x509 -fingerprint -in "$NOTMUCH_SRCDIR/test/smime/key+cert.pem" -noout | sed -e 's/^.*=//' -e s/://g) add_gpgsm_home @@ -74,7 +72,7 @@ test_expect_equal_json \ test_begin_subtest "Decryption and signature verification (openssl)" notmuch show --format=raw subject:"test encrypted message 001" |\ - openssl smime -decrypt -recip test_suite.pem |\ + openssl smime -decrypt -recip $NOTMUCH_SRCDIR/test/smime/key+cert.pem |\ openssl smime -verify -CAfile $NOTMUCH_SRCDIR/test/smime/test.crt 2>OUTPUT cat < EXPECTED Verification successful From 51c43d0e925486b2c7b9a708ba9aebc22dde57a6 Mon Sep 17 00:00:00 2001 From: Daniel Kahn Gillmor Date: Thu, 30 Apr 2020 15:34:21 -0400 Subject: [PATCH 137/427] test: Allow tests to have both gpg and gpgsm active at once Without this fix, we couldn't run both add_gnupg_home and add_gpgsm_home in the same test script. Signed-off-by: Daniel Kahn Gillmor --- test/test-lib.sh | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/test-lib.sh b/test/test-lib.sh index ac1b9315..1baa2d20 100644 --- a/test/test-lib.sh +++ b/test/test-lib.sh @@ -110,10 +110,10 @@ unset ALTERNATE_EDITOR add_gnupg_home () { local output - [ -d ${GNUPGHOME} ] && return + [ -e "${GNUPGHOME}/gpg.conf" ] && return _gnupg_exit () { gpgconf --kill all 2>/dev/null || true; } at_exit_function _gnupg_exit - mkdir -m 0700 "$GNUPGHOME" + mkdir -p -m 0700 "$GNUPGHOME" gpg --no-tty --import <$NOTMUCH_SRCDIR/test/gnupg-secret-key.asc >"$GNUPGHOME"/import.log 2>&1 test_debug "cat $GNUPGHOME/import.log" if (gpg --quick-random --version >/dev/null 2>&1) ; then @@ -132,10 +132,10 @@ add_gnupg_home () add_gpgsm_home () { local fpr - [ -d "$GNUPGHOME" ] && return + [ -e "$GNUPGHOME/gpgsm.conf" ] && return _gnupg_exit () { gpgconf --kill all 2>/dev/null || true; } at_exit_function _gnupg_exit - mkdir -m 0700 "$GNUPGHOME" + mkdir -p -m 0700 "$GNUPGHOME" openssl pkcs12 -export -passout pass: -inkey "$NOTMUCH_SRCDIR/test/smime/key+cert.pem" \ < "$NOTMUCH_SRCDIR/test/smime/test.crt" | \ gpgsm --batch --no-tty --no-common-certs-import --pinentry-mode=loopback --passphrase-fd 3 \ From 7c7cebffe6f05c8bbb07c3714fde08562444c72e Mon Sep 17 00:00:00 2001 From: Daniel Kahn Gillmor Date: Thu, 30 Apr 2020 15:35:21 -0400 Subject: [PATCH 138/427] tests/smime: include secret key material for Bob This is taken from the same Internet Draft that test/smime/ca.crt comes from. See that draft for more details. https://www.ietf.org/id/draft-dkg-lamps-samples-02.html#name-pkcs12-object-for-bob We don't use it yet, but it will be used to decrypt other messages in the test suite. Note that we include it here with an empty passphrase, rather than with the passphrase "bob" that it is supplied with in the I-D. The underlying cryptographic material is the same, but this way we can import cleanly into gpgsm without having a passphrase set on it (gpgsm converts an empty-string passphrase into no passphrase at all on import). Signed-off-by: Daniel Kahn Gillmor --- test/smime/bob.p12 | 58 ++++++++++++++++++++++++++++++++++++++++++++++ test/test-lib.sh | 2 ++ 2 files changed, 60 insertions(+) create mode 100644 test/smime/bob.p12 diff --git a/test/smime/bob.p12 b/test/smime/bob.p12 new file mode 100644 index 00000000..774c77d0 --- /dev/null +++ b/test/smime/bob.p12 @@ -0,0 +1,58 @@ +-----BEGIN PKCS12----- +MIIKWAIBAzCCCh4GCSqGSIb3DQEHAaCCCg8EggoLMIIKBzCCBGcGCSqGSIb3DQEH +BqCCBFgwggRUAgEAMIIETQYJKoZIhvcNAQcBMBwGCiqGSIb3DQEMAQYwDgQICE8J +3kMad9UCAggAgIIEIPvHjK0eRQrnowMUsz1z1x/IxslNvG6DjPZjNHCkNYYmiRsg +Leu5nqKf4emWVvYpnlh+4Gql7pyJm3G3zSNhobPkW+P1Eh80tTBoUk7TIvvvmtrE +YEc/nRR1p1MgjISq4Q/CM6ccCCw6YEiQcj/0mSS7gmHUegD5glcWbVuqAT8M/p1z +98OP3z37G8ARRLNj1yyp0SVlt59Sx3WNbmYBqkQ96iukjMJvmjV7o6BFYUx46Llb +tphhdRgKXbK2r1R0TUlvE659TUwlrpGlaFpaGj1kLdzVAnjh1ZWnWO2a2BSj0LzG +qRyiLwqDFPLJLQEckfV+RPWiRrSewME8URNKdk6eewtHdhrehMo4ZJnOIum8qcSz +giW61SSyZJsFvILpmMYghIxWmPd/8cNIHBrdFEa7z3QKh5jcJNTCxz6yO9f8F830 +d+WDK7DbGkUW4mVTGg/lEYnCFZDF6S1mr0hx+cew1FbKjLpxfQllIIrLf5d2BF8H +0STpuylQDVVBFdTRHyeS6td5nulANgOProrRzy3aAKQmZ6iullKl+i2t/2TwfVP/ +gG+yszpOEf8U9txuvbiZ7j4XV158zdaaGiduDqMKLOvbdctwHAsR9ecx5C3NTRDl +ZlttNoXN9zhT4CkWk1w4sFk2KUurjVraIcjWVT7yOreaaK+6N09M0tnLPDJDTrow +8WwP/rZhA+t+CMrhqkFBxXsyo5VTM0jWJGO/NLpYXPhDPBsRq8rs1OCrUoVr34aR +cpUTNhyXkvJUarWDHs88lg0ps0G9/1dXI1AbEsQQg8u+QT2ztGYrg2OQxQyi1Mo4 +u/FkAcEbtlYYLmJjj/S2qVRPJgBALVjw9k5hnYRdAXWVDCJ96PMn1SKORvlMxnZ7 +djlhaztOhTLsiDzywVDYWLvQElunWcAGeDZykWNytwcEagc0VjWKHMibc0JOZQ1T +crGyOzTlt09xHj1NrItYefIwdtKuJfkAh03B5xI6rJ9ZbK9xidcVxyeRX0lEqdo9 +WHQrhHefAmeyo0TlfsN67kFDp5FLpwEtNaN0lyzpkl30aWZdtP5vkvtfmy5ugYIO +bXoVa+tO6k5V/VfUFUKdaY7xAX7XRzUUg4jB0D0CuaX+YS+GL+5wuQwIY1y2ihBb +CuCxlcP1lVEU4CVQba60VTudJtWyE7QpPhf+y81f1wRjwIihFvwzpUFWf8JVEppe +v3Yot3OWGBmhEqLkC9LELth8o5gLfyYHaXTYNd9aRTiI+0ZC5U3O4wUwYLTG3exM +rIDTzEMk/p4DYIHkNKVUiRJfGYdAwuRxf3IMcYWARTXlSzl1C3hWmZfvTPlKs1bB +OHTHP/P+qdOFjxOh+fbyqXPJauBAhHvHgrp3iI6t834wJou26oWNihM7OnWuyQRt +9DVxG4l+1VjtbQZfTDCCBZgGCSqGSIb3DQEHAaCCBYkEggWFMIIFgTCCBX0GCyqG +SIb3DQEMCgECoIIE7jCCBOowHAYKKoZIhvcNAQwBAzAOBAjqo0x2p5SqLAICCAAE +ggTIe6Ws+lu0CoNlCXGM2BEPV09wuRHTJe+KnesrmRbXPF9linG3d6G++tTkBHz/ +yr77/DV5aDYciV1pGAbLuX2lMwuqdxzJ4OBPBAjuX5H+IPRaTbxfHYYIwhG8oZzy +aHyVhHr9j0h7lzW7xSTYJuBNEJ58L42dfzpNRw9dyRPmcuhZqW14Z3xyDm8yjHfB +2p99y9/A4qSyJJSUM3O3nLdtIar3ktSTRAijgqq+s9wnsfozQRzWpYaqiRrdzwfO +HqXk54l3/lMSyLpfPl9LW7er6JbGI4jEyQ3x8WijATM5h/lkZKejh/mOaWCvs6G6 +fGzV4P35EsToYbOk9GX4jl4SyDBt3iEHYm5teDUhJmTcR39lAQuAfxN6rOn/TkoO +YLxtdD5DLiTfYZPCFyavLEsamr8A4p93torF6Rs7GsaHE6PmCcprzqx71KV0DZKv +tMY86RoiWPKLFxZcYt1yz9/95c1SO1s4i1GvLpJTEgQxLM2OhfEwDNKd2rMJoq1I +YIRPSP204dIVwwNdXN1vB2slhN2+/QMOqsEkWtTOpW2QoTGSze49hfmJGdu+91jd +XZBBMJQfY4q066/eE4IOW7ZZId5uMYxDRnGdEQjJsxyW8YHWLRGQvBC8gMkdbj8e +0wkXbe+jML7vG7t3hDhLEbj5sTquIMTWrTirPw4SxLCuGZAyJHFN3/nCaOSMFlCG +wEZHrAozgQXPBYU7p+uIkJ4lDc2ZtW8NM8U15gKZLDFfAE6Vg0jAtfFMqvNnX630 +xfo1z4jBd7VXbBFrPzrmvlTnb1XxNFcPycowzW9tgtN4YnNroCq98VpMC914tdpJ ++C/PI0eJ7M2ir3ajN0RabSm02JO9Hdwoa5OgqLwPYDwiFyQvKFGKqAF8Ph6pSEiZ +10OnH+DVgEY70A+Le+ZSDosMdrhZfHbCcIFitZJ3sYV/7Q118QckW3szcjmLHS5g +M6Whl2HhjLsAfsmCnoRlIwjx4g0TiuZcb4hGysq8QjD3Z8qqFK28m6OMHbASQfWg +U+Qg3vmEvVsnBxStFEIImS3QYQoaT0pk6zKUYsI/fOBnEgxsY0XwTfXzVw7hZDct +yhNIQVWmfgVZwUw0wLoNu3A5hupjUwQzQr4TPnKkFPI8qHmRrJgP8EA0U0019y3W +MlK0h/LAJEaUBS0goLJCJ8+1EWr6femjnyuU5hMizOm+3j0JexjWz5TQttioS7Q/ +vcxt5pA9yAWQdH9j72saKEoKmDi+kIPr4mimKJz99LhKp9A6Hj0f1P2V3As8JWyW +ZKmJKW7qMMCFADlALolobqzA60j6Zeo5jiEj/j2lVlUPPz47WO+uKeb+rx+hgTUc +Xrhq0+an5tvEXt/8wy3PJFqP+qqHGhOIuPLuhqPyzNowuXirIXsiWnI44/X48W91 +HPEoL3xaebQ6oyTP8dI4CCkkHgiLWL5mskjHMEXvcdR6k0ygmu8DGQCPfUweUZqZ +wfkhD/jwbVpLR5Y3chpatW0cJ2bsAWdxwtuxF05+fVEePUsR0x+2/v/8eDEHKYwt +aYlAhI48nyrKKVMmqvqcXnzmJlUaq05GnEcglFbv4MUExL7CxClls6QnVNiZFPrV +ffVsYT2A300xrm4pan89n3nuavjJn7L1JJdmMXwwVQYJKoZIhvcNAQkUMUgeRgBH +AG4AdQBQAEcAIABlAHgAcABvAHIAdABlAGQAIABjAGUAcgB0AGkAZgBpAGMAYQB0 +AGUAIAA0ADIAYgBiADIANAAwADYwIwYJKoZIhvcNAQkVMRYEFGaI9k+ZdE9/rxBZ +4rSdH1BCuyQGMDEwITAJBgUrDgMCGgUABBRJfL4XyIHpXmjbziCGCbSAOK9jKgQI +drOMeIgXcCYCAggA +-----END PKCS12----- diff --git a/test/test-lib.sh b/test/test-lib.sh index 1baa2d20..cb5bb894 100644 --- a/test/test-lib.sh +++ b/test/test-lib.sh @@ -145,6 +145,8 @@ add_gpgsm_home () gpgsm --quiet --batch --no-tty --no-common-certs-import --disable-dirmngr --import < $NOTMUCH_SRCDIR/test/smime/ca.crt echo "4D:E0:FF:63:C0:E9:EC:01:29:11:C8:7A:EE:DA:3A:9A:7F:6E:C1:0D S" >> "$GNUPGHOME/trustlist.txt" echo include-certs::1 | gpgconf --output /dev/null --change-options gpgsm + gpgsm --batch --no-tty --no-common-certs-import --pinentry-mode=loopback --passphrase-fd 3 \ + --disable-dirmngr --import "$NOTMUCH_SRCDIR/test/smime/bob.p12" >>"$GNUPGHOME"/import.log 2>&1 3<<<'' test_debug "cat $GNUPGHOME/import.log" } From 482af5a031911cf946fce5fc69c9e422ed2ff68f Mon Sep 17 00:00:00 2001 From: Daniel Kahn Gillmor Date: Tue, 28 Apr 2020 14:57:17 -0400 Subject: [PATCH 139/427] tests: Add S/MIME messages to protected-headers corpus These sample messages are taken directly from the Protected Headers draft: https://www.ietf.org/id/draft-autocrypt-lamps-protected-headers-02.html Note that this commit doesn't strictly pass the common git pre-commit hook due to introducing some trailing whitespace. That's just the nature of the corpus, though. We should have that trailing whitespace, so I've made this commit with --no-verify. Signed-off-by: Daniel Kahn Gillmor --- .../smime-enc+legacy-disp.eml | 50 +++++++++ .../smime-multipart-signed.eml | 68 ++++++++++++ .../smime-onepart-signed.eml | 54 ++++++++++ .../smime-sign+enc+legacy-disp.eml | 102 ++++++++++++++++++ .../protected-headers/smime-sign+enc.eml | 95 ++++++++++++++++ 5 files changed, 369 insertions(+) create mode 100644 test/corpora/protected-headers/smime-enc+legacy-disp.eml create mode 100644 test/corpora/protected-headers/smime-multipart-signed.eml create mode 100644 test/corpora/protected-headers/smime-onepart-signed.eml create mode 100644 test/corpora/protected-headers/smime-sign+enc+legacy-disp.eml create mode 100644 test/corpora/protected-headers/smime-sign+enc.eml diff --git a/test/corpora/protected-headers/smime-enc+legacy-disp.eml b/test/corpora/protected-headers/smime-enc+legacy-disp.eml new file mode 100644 index 00000000..6f5c9417 --- /dev/null +++ b/test/corpora/protected-headers/smime-enc+legacy-disp.eml @@ -0,0 +1,50 @@ +Received: from localhost (localhost [127.0.0.1]); Wed, 27 Nov 2019 + 01:27:28 -0700 (UTC-07:00) +MIME-Version: 1.0 +Content-Transfer-Encoding: base64 +Content-Type: application/pkcs7-mime; name="smime.p7m"; + smime-type="enveloped-data" +From: Alice Lovelace +To: Bob Babbage +Date: Wed, 27 Nov 2019 01:27:00 -0700 +Message-ID: +Subject: ... + +MIIG5QYJKoZIhvcNAQcDoIIG1jCCBtICAQAxggLCMIIBXQIBADBFMC0xKzApBgNV +BAMTIlNhbXBsZSBMQU1QUyBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkCFCJT7jBtAgsf +As31ycE+Ot95phvCMA0GCSqGSIb3DQEBAQUABIIBADEhlzhFzYj6tUAdsRCrSiLl +d9cgKtlAesJ4cDY4szFWAbnwrCmEcFxjFDUOjbfQCYCG80Sxd+xntni73I7PI2rR +QLjk3w9VhLwFRyzy7qyJi2CavjKTxysX9f36+FXA+THfVQRM5ypiyYJg91X51PNX +hJj3DHrnxqKeSl/z1hdt9r+s6XAUCBSvL99BGnODWhNIZtPDzt8fMNcgarfw+D5F +IZJb6+wX30tkztHkpHHKrrDPveyfnlS/p06Gi3ekrrhBtMQMRb9PA/E+ivDPktsm +aKg0Oauw4oZSKW3f4ukYhbnndbbagNsnTfs/QFy/p+hhKTrfCd0h1N8mTzedVX0w +ggFdAgEAMEUwLTErMCkGA1UEAxMiU2FtcGxlIExBTVBTIENlcnRpZmljYXRlIEF1 +dGhvcml0eQIUZ4K0WXNSS8H0cUcZavD9EYqqTAswDQYJKoZIhvcNAQEBBQAEggEA +FaK5QaPXJ133D2uybQt//oeDm6PkCAFW9YVOgjnLLz6FD54Dt2i1KCQu1Xlg9W3P +1zJdYXOftDgilylNfmt/muEsvbRfFtMWUq0VGirHz//BWmY2cW/ocinFO514iviL +MLE1umsXRNwVIVIk/uh7AmqXjPkRZgRgIMUbSbtmW4DDja+ZM0vmqFQ1iUIlApth +FpjFfPDHHD8isLTbGi2iK6dEN3DIJFGbg5o3nK6yAhVZ7x3LfFNSNVDDSY5mPFG9 +Vm6uRgEE3Y5P6DbXXo6MHTgg0XY2f4y6MEWhOg37NT9aFAfzBBxJ1oSBWpOOfZnV +K1DvAwPaemSRz9oWDcBM8DCCBAUGCSqGSIb3DQEHATAUBggqhkiG9w0DBwQIsFkN +8DEx8muAggPgWGF2WsPq3/a9jUa5GA0YFPiINuETCGTNaEXiVxnT0h0CF+EhZ0T2 +HFCiZEM0dzO05zt9WdVvAREaCSh7ZWG9D9wJF9x+tqQbzMuJ2AdKuoOH73kClvkx +pHxANLhkY7hzIqRb/eLG5D7Xh8iCDiFecXDh7EHqD/R+sfLN9aHKOcKyY36kesBQ +R8aHZbbFnnD+oXSDNIPcntGG3BSGMxsWuOp+rpTKeIHWFIungDNKsLIy3kWleENw +FVIcjUF6QhI1HYW6BeXuVq40GV2OOkmB24rYEW1Jg0hAtY+5rn2mRoyxvUC87bjQ +hLu6xgPmhun9J324eM5aYVwkmVBnRW9hyxClZ7Sv0zlL7lGQ0VQG+zWHeJ+h/M2j +mQpLgAUEGxxNCm5ASHuXPIN6pSvrOVplrT8kKLPpmMYEwmTX2/rBO4P8I8uNrqYD +AyX8p0/l2ArczkWzGTz2luBahrD+cTZPApe5SeyXOxWBl1Lmb0G8o4twBeeBLiHP +XwYvttx0JYG/hc/lmMpEemJqwj9uZ3wGD03dIhhDX2Oj4ek/7jT6yqJh8C1H+PqA ++HNfNXsFQDrRORoqJS8YVEiYRDQNyePy2ugzLTh88nPtJp92hY7bk9zl3AYaiVFH ++szlLoyzfM9D+geZemR8XfI2ijGnrWMlnyPah/zA6J6RwemhuiMklZGYG85hMU9H +K4CFVM+m7xYxKpwFVnmkVZjzWInirJhehElhtCXpx/IFGxH9CPbCyEZV1WVStrl/ +0fWTGicMXez6hVQCadWCXy96/eLIXOrC54gSoIJX2TD6jdVEu1YptutyGI6KdQ2p +yXwhs98Uj7DM3nmFeAcjjN3e8pPoX7aG8eP+MfmHlWN6jA44jMaJmIdp9J20g74J +MdjvnHa/cGibW/RamPiFObN0F94A83vcpUfU/zZ8cFHi/3/lN6Rm9+3/giGRZa9E +Y6e2/CEq1cUbPQ09fPwRJmjZCfDce71DKe+ZFGdYtFR7JwDEeZ6BB4Ff4rXctcWD +PgUJqUGv/SXBcFn4cNUK9MYYqVu1ovd/T7FMf+i3c5MH6BRCvft/i5aeBR+A26Gk +2awtBPYdHW6+AslrFjncBbtPDlU6vX9AWuC0k0MQYnNkTWS8gTvsriXJZ6Zu5iFE +ExNuFz7YcnMKnguOn2ph5azzeMm83AYzWXzZPu3mdr5Siuu/Ke38oADKP+BZ08Za +XVvKvvfnRPXO9kG9hgvEMRU9KOcxn82XoGPNZib+9SPa2zYx5P6HX1Bqe/cmKAen +FKEiJLSTP2/pc6AWAICqJl978HaUHfMFiN7jEUppAifpAWqNcIGSW5w= + diff --git a/test/corpora/protected-headers/smime-multipart-signed.eml b/test/corpora/protected-headers/smime-multipart-signed.eml new file mode 100644 index 00000000..f05d2d98 --- /dev/null +++ b/test/corpora/protected-headers/smime-multipart-signed.eml @@ -0,0 +1,68 @@ +Received: from localhost (localhost [127.0.0.1]); Tue, 26 Nov 2019 + 20:03:17 -0400 (UTC-04:00) +MIME-Version: 1.0 +Content-Type: multipart/signed; boundary="179"; + protocol="application/pkcs7-signature"; micalg="sha-256" +From: Alice Lovelace +To: Bob Babbage +Date: Tue, 26 Nov 2019 20:03:00 -0400 +Subject: The FooCorp contract +Message-ID: + +--179 +Content-Type: text/plain; charset="us-ascii"; protected-headers="v1" +From: Alice Lovelace +To: Bob Babbage +Date: Tue, 26 Nov 2019 20:03:00 -0400 +Subject: The FooCorp contract +Message-ID: + +Bob, we need to cancel this contract. + +Please start the necessary processes to make that happen today. + +(this is the 'smime-multipart-signed' message) + +Thanks, Alice +-- +Alice Lovelace +President +Example Corp + +--179 +Content-Transfer-Encoding: base64 +Content-Type: application/pkcs7-signature; name="smime.p7s" + +MIIFhQYJKoZIhvcNAQcCoIIFdjCCBXICAQExDTALBglghkgBZQMEAgEwCwYJKoZI +hvcNAQcBoIIDcjCCA24wggJWoAMCAQICFGeCtFlzUkvB9HFHGWrw/RGKqkwLMA0G +CSqGSIb3DQEBDQUAMC0xKzApBgNVBAMTIlNhbXBsZSBMQU1QUyBDZXJ0aWZpY2F0 +ZSBBdXRob3JpdHkwIBcNMTkxMTIwMDY1NDE4WhgPMjA1MjA5MjcwNjU0MThaMBkx +FzAVBgNVBAMTDkFsaWNlIExvdmVsYWNlMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A +MIIBCgKCAQEAw+6t+WXRtiQM8yRjWQ2fbFewCodIZUX6BY02TeZuEXoEAGEsmoON +6LlotcUTdGr39FE2K8IytOKkXVexswgAqBCqv8YjVDrI3yV82wrm5Td32TDlw7IS +igak4ZSu+UowPQs8YO3oxqImP4onZNHvdZ3it9EggmgUyZX0dmQ6z5O9yDzHpLMa +E2rXxfYcPXQwPvx4tcqbTf2htEP7PYnBa8a+sts0F7I7kD5ozGYI9dGg/XGs1lYE +WAoH5YZgNFdbkJdcKG2FPAwFcVZ/hoGm6soxkDKMrYSCtBp+fqH8MV11DP821PoO +vtSEnaF8UURbaths2yKpAB2WUJvgW5xa4QIDAQABo4GXMIGUMAwGA1UdEwEB/wQC +MAAwHgYDVR0RBBcwFYETYWxpY2VAc21pbWUuZXhhbXBsZTATBgNVHSUEDDAKBggr +BgEFBQcDBDAPBgNVHQ8BAf8EBQMDB6AAMB0GA1UdDgQWBBSsLlRapP1VGK8u6GZE +ONEl0dcAeTAfBgNVHSMEGDAWgBS3Uk1zwIg9ssN6WgzzlPf3gKJ32zANBgkqhkiG +9w0BAQ0FAAOCAQEAe+qOGM+8q1UhXKV6i63BrXSOKvd2iglxAggszUC6eMnrIem6 +6mmRzSbcGHCeU6m1MpvYSe9IiROIxjTfsgGUdZbbXtBxSmCASjOBCbphvvtoam1G +i8+LZdOgR2kDwr//TYjWO6vUfXPwerNWMx4cKpFobdmvgLYCeAZKRvoPjJmTEFfw +KO0cCxSifTpTFiwZhFxXKSCTdB6T2rE9JxJfzJqLUrvvEZwpQIt8hX8kym/vKw+1 +cbsl3rag2enVP/f4qg/0mUuzkCI8sLXd+N5gAs9wdUZRcTB0gOnUAH9m7RrpqkdC +ogKdypGEQHj6GiamJAe2WndOp4BZdBtBRzjfuzGCAdkwggHVAgEBMEUwLTErMCkG +A1UEAxMiU2FtcGxlIExBTVBTIENlcnRpZmljYXRlIEF1dGhvcml0eQIUZ4K0WXNS +S8H0cUcZavD9EYqqTAswCwYJYIZIAWUDBAIBoGkwGAYJKoZIhvcNAQkDMQsGCSqG +SIb3DQEHATAcBgkqhkiG9w0BCQUxDxcNMTkxMTI3MDAwMzAwWjAvBgkqhkiG9w0B +CQQxIgQgGeoQw8WDmjB606EKGR5n1oMuV7Te1VjfA2oB2ebW390wDQYJKoZIhvcN +AQEBBQAEggEABblYEWSnYyzL3jTS3AoPr93YKksIZr5q/b8Y5/1rMxdYxPm+iReO +RHRgpbFQeiqZXzRXtMohfoIkh7RmdQoSV4OpwiUmNU+f0ZEAu8cMVJM6gdyUD+1D +JwDNr+YNLV/1UUGhqx0FExOa/4O92KYBD4eRQw4KDWrkfh9dlSj0Bsl4thrZYGLz +e7ut3FN5TBruZfmqMy50xZ9yUW91YyQUBLiIcuF185y5ZW/aQCxBKBbrNNGXLJbo +8yKFJqSPiWZvwUmVQvfgL182hg823OJTtP4VImcUakTF0+k+BM//qqKXYrlX/tZn +QzG+4ZH/XM1vgHl7ShjHS6TSOHz2ODqD6Q== + +--179-- + diff --git a/test/corpora/protected-headers/smime-onepart-signed.eml b/test/corpora/protected-headers/smime-onepart-signed.eml new file mode 100644 index 00000000..028e02c8 --- /dev/null +++ b/test/corpora/protected-headers/smime-onepart-signed.eml @@ -0,0 +1,54 @@ +Received: from localhost (localhost [127.0.0.1]); Tue, 26 Nov 2019 + 20:06:17 -0400 (UTC-04:00) +Content-Transfer-Encoding: base64 +Content-Type: application/pkcs7-mime; name="smime.p7m"; + smime-type="signed-data" +MIME-Version: 1.0 +From: Alice Lovelace +To: Bob Babbage +Date: Tue, 26 Nov 2019 20:06:00 -0400 +Subject: The FooCorp contract +Message-ID: + +MIIHhQYJKoZIhvcNAQcCoIIHdjCCB3ICAQExDTALBglghkgBZQMEAgEwggIJBgkq +hkiG9w0BBwGgggH6BIIB9kNvbnRlbnQtVHlwZTogdGV4dC9wbGFpbjsgY2hhcnNl +dD0idXMtYXNjaWkiOyBwcm90ZWN0ZWQtaGVhZGVycz0idjEiDQpGcm9tOiBBbGlj +ZSBMb3ZlbGFjZSA8YWxpY2VAc21pbWUuZXhhbXBsZT4NClRvOiBCb2IgQmFiYmFn +ZSA8Ym9iQHNtaW1lLmV4YW1wbGU+DQpEYXRlOiBUdWUsIDI2IE5vdiAyMDE5IDIw +OjA2OjAwIC0wNDAwDQpTdWJqZWN0OiBUaGUgRm9vQ29ycCBjb250cmFjdA0KTWVz +c2FnZS1JRDogPHNtaW1lLW9uZXBhcnQtc2lnbmVkQHByb3RlY3RlZC1oZWFkZXJz +LmV4YW1wbGU+DQoNCkJvYiwgd2UgbmVlZCB0byBjYW5jZWwgdGhpcyBjb250cmFj +dC4NCg0KUGxlYXNlIHN0YXJ0IHRoZSBuZWNlc3NhcnkgcHJvY2Vzc2VzIHRvIG1h +a2UgdGhhdCBoYXBwZW4gdG9kYXkuDQoNCih0aGlzIGlzIHRoZSAnc21pbWUtb25l +cGFydC1zaWduZWQnIG1lc3NhZ2UpDQoNClRoYW5rcywgQWxpY2UNCi0tIA0KQWxp +Y2UgTG92ZWxhY2UNClByZXNpZGVudA0KRXhhbXBsZSBDb3JwDQqgggNyMIIDbjCC +AlagAwIBAgIUZ4K0WXNSS8H0cUcZavD9EYqqTAswDQYJKoZIhvcNAQENBQAwLTEr +MCkGA1UEAxMiU2FtcGxlIExBTVBTIENlcnRpZmljYXRlIEF1dGhvcml0eTAgFw0x +OTExMjAwNjU0MThaGA8yMDUyMDkyNzA2NTQxOFowGTEXMBUGA1UEAxMOQWxpY2Ug +TG92ZWxhY2UwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDD7q35ZdG2 +JAzzJGNZDZ9sV7AKh0hlRfoFjTZN5m4RegQAYSyag43ouWi1xRN0avf0UTYrwjK0 +4qRdV7GzCACoEKq/xiNUOsjfJXzbCublN3fZMOXDshKKBqThlK75SjA9Czxg7ejG +oiY/iidk0e91neK30SCCaBTJlfR2ZDrPk73IPMeksxoTatfF9hw9dDA+/Hi1yptN +/aG0Q/s9icFrxr6y2zQXsjuQPmjMZgj10aD9cazWVgRYCgflhmA0V1uQl1wobYU8 +DAVxVn+GgabqyjGQMoythIK0Gn5+ofwxXXUM/zbU+g6+1ISdoXxRRFtq2GzbIqkA +HZZQm+BbnFrhAgMBAAGjgZcwgZQwDAYDVR0TAQH/BAIwADAeBgNVHREEFzAVgRNh +bGljZUBzbWltZS5leGFtcGxlMBMGA1UdJQQMMAoGCCsGAQUFBwMEMA8GA1UdDwEB +/wQFAwMHoAAwHQYDVR0OBBYEFKwuVFqk/VUYry7oZkQ40SXR1wB5MB8GA1UdIwQY +MBaAFLdSTXPAiD2yw3paDPOU9/eAonfbMA0GCSqGSIb3DQEBDQUAA4IBAQB76o4Y +z7yrVSFcpXqLrcGtdI4q93aKCXECCCzNQLp4yesh6brqaZHNJtwYcJ5TqbUym9hJ +70iJE4jGNN+yAZR1ltte0HFKYIBKM4EJumG++2hqbUaLz4tl06BHaQPCv/9NiNY7 +q9R9c/B6s1YzHhwqkWht2a+AtgJ4BkpG+g+MmZMQV/Ao7RwLFKJ9OlMWLBmEXFcp +IJN0HpPasT0nEl/MmotSu+8RnClAi3yFfyTKb+8rD7VxuyXetqDZ6dU/9/iqD/SZ +S7OQIjywtd343mACz3B1RlFxMHSA6dQAf2btGumqR0KiAp3KkYRAePoaJqYkB7Za +d06ngFl0G0FHON+7MYIB2TCCAdUCAQEwRTAtMSswKQYDVQQDEyJTYW1wbGUgTEFN +UFMgQ2VydGlmaWNhdGUgQXV0aG9yaXR5AhRngrRZc1JLwfRxRxlq8P0RiqpMCzAL +BglghkgBZQMEAgGgaTAYBgkqhkiG9w0BCQMxCwYJKoZIhvcNAQcBMBwGCSqGSIb3 +DQEJBTEPFw0xOTExMjcwMDA2MDBaMC8GCSqGSIb3DQEJBDEiBCAKDM98nuDl98sK +i4SDvP2xlxr2SdV/xNVYs6SeGCBRuTANBgkqhkiG9w0BAQEFAASCAQAcryWkSIbG +rrc/aDF1Z4KRnoRpr+fOutQSLV7k0Tgezt+X/kJCIiuLvjUxLrTux1yUWCKUPb6T +KLYASPJpwDXrNzqmGs1pJmWHTZwUhbFVXt16FaQZkDSATtvhQU39Rsot2j1pP/UV +J7+5FPQwNc4dt7MFW7jU4TBHo2VrzjZ2K8ioELPxsixOCAp3ytkhf1Umw6bC5M/u +oWjsa6xzAl4fw5+pxZw0JdbrYn5kmPiekSsYy2/+yOwzrtIYtHW5dY7DoWWXDXtD +cmCGHkO8qry+MnMy3PwvXiX0warQo1fnhXB5tlk2K9YdiDcOtnAshEBXAudnxlPK +JGzeJVUfbfM0 + diff --git a/test/corpora/protected-headers/smime-sign+enc+legacy-disp.eml b/test/corpora/protected-headers/smime-sign+enc+legacy-disp.eml new file mode 100644 index 00000000..7ec33968 --- /dev/null +++ b/test/corpora/protected-headers/smime-sign+enc+legacy-disp.eml @@ -0,0 +1,102 @@ +Received: from localhost (localhost [127.0.0.1]); Wed, 27 Nov 2019 + 01:24:28 -0700 (UTC-07:00) +MIME-Version: 1.0 +Content-Transfer-Encoding: base64 +Content-Type: application/pkcs7-mime; name="smime.p7m"; + smime-type="enveloped-data" +From: Alice Lovelace +To: Bob Babbage +Date: Wed, 27 Nov 2019 01:24:00 -0700 +Message-ID: +Subject: ... + +MIIQjQYJKoZIhvcNAQcDoIIQfjCCEHoCAQAxggLCMIIBXQIBADBFMC0xKzApBgNV +BAMTIlNhbXBsZSBMQU1QUyBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkCFCJT7jBtAgsf +As31ycE+Ot95phvCMA0GCSqGSIb3DQEBAQUABIIBAFbDR6j4ZB/Mo9BQygYItwFc +P+4rO4d1ak51hc1DpSqyhiMcGahA3yxDRbZ4W1rbmC/s3d5+OWXKYgs1nNMQJ48F +f45BtNTNslPZ1+NZVbkoVJO8Bxv1rjB8/qWuSUsroqzn9enS8DUBxxPL5aSWKQQN +G2IaH9BUkMXLPUYA46GATly94IS4fZqwBtNNBP5eiIIPc9Ogjy+7At5GG7rVMN0M +G5FL0oq52SYUe1167jp378JI+2dkA1q5+Cru/ZE2Rdw3DrMDAFO5GwC7fWKg4zPm +IHZj92caVj1IyfTmGogT2o5tLMqn61BkptqxZwHDr3FI/aYo4vcHgmlKR/TdbHww +ggFdAgEAMEUwLTErMCkGA1UEAxMiU2FtcGxlIExBTVBTIENlcnRpZmljYXRlIEF1 +dGhvcml0eQIUZ4K0WXNSS8H0cUcZavD9EYqqTAswDQYJKoZIhvcNAQEBBQAEggEA +hXeYVSUsT1EBZ/+AjwyEcnlM0kuFMaNvGlBMhAZzAsy012rrZTWbqWkcA3abgm/M +CuZX7mQL0I79KZdmClGpLx6gQFjLemHaClQV0ZNdX4DxakWuME/kCMqbo4MZXStT +a0MHlKUdoMt72Rz4YBzNQCL7ePaii5w6Nd2KD7yJAirLYUMJEjVweVaMI9y9LmbO +vb0g0iuoUe0vp9B20LRcIX37nN5D1GG4tHLPjBD43gC8iqxZQf0uah2cWD1mAG5R +oBgIDKXPy2eVbcMdSaOirDKYZ49WFe9Lad9q3mHHbFs6K6/yuBm/thMEdCJKZTHo +jiPvYdYF8IJfEd368I+DujCCDa0GCSqGSIb3DQEHATAUBggqhkiG9w0DBwQIsb1a +JX/RU9aAgg2I0VXWfs5fc/Yad2qvawUVNX+LObjA6/+t9WxuV2emOeBYzQGjo7q+ +xaIXQwbbF1ej27efGhxUYDwBNS56c0uI0Ta7jxv5OFZhzQGLRzoFp0bbZ+uVC4eP +bFHarRQiPzlg900XASO0RW+UOtqN5raZ3Ry2lKwXxuStZ0pX666Rz4c8PrmMb4/B +aQYn6iKcT6fDU2TpSbWY9iph6kZczSeewK+pIj9nXfjDKXScs8D2Raezev2ciq/V +ZRpRH8JxieimI2yeBmEzTCq11TDYycDfMHB6reGaiCGX//8kAWtskzRyNlV61unY +ZKSNhVKLwKmCQh1V1Nd3oLApT41EeM2oWedUqNBYqB+XGCD4DUYdm1e+4h73d4dn +JTkCdadxEn+9RRvZ4YMlw3mvT997Dy3rTXT29dj14TstZZf2O63pY0TpYy0HZy6Z +Jug1qoe/vdcJ9SPOSfJE6VWCeVjxB+eGgheFLKqzK8Hs/Bm0/wDKpSFgEpOPnkJ4 +HJ2Uzgn1Emo6gBDJt+qn3s2UnowcMsTgellhKvgzVq59LTyRyWL5U8XMBsXT4qjm +0LkRvDkOIjMQH7kqvWbpPlnWpLKo/VVoxifldEegWAqFVrP7f5Y+nNQttAYV79uk +MXvR+5YFkvmQAerfllPqXBJdbB65ovikSVsy/kAboGpRG1oAZ4ODdwdGyiGIzyyc +lE0x/8+gY8BqWzRtWX4GySKyZ50/+xkJe5ss0IXPCgq/09bdihsRn57v4V4SpdDO +k3g/Dce+LzCRL8uTbUhrhZnjKSjRc3fFaD/BpLYjEDbnGF0ICslN3vb2xWUK1u4M +uUH9r7lH/DCb0+TxIBtxOnP7W02bz8gGJAxEVEqk6pjxxOYqfS9/uBrrAY8P21Y9 +PFLdeHzEdYemq3il+4S7OU3uNUuAYijxmCRs7JQxZ9puA0iaTME9gK1yikzsLtVZ +f+9osk2nYgfXvlL0AiYabd5cU2GNW33TkdDMNBsB7lx77J9erVLZpPKNo4vgHA7b +owrDaYe0AgcZm79fvmR0RdtIZI91MouEhkdhaPiXmypmszjR/M0Ot3Y+oU/ks+yV +Sle0S0h4V8wJRJYG/9VVurm8012ke2U3EGFlVnSv/IYtpssC+U4McRCmakKCrGU7 +OhL5JKBQN/DFTu4pV39IQlLLhg3wzA2FSkyIL5gEbS6sP9GTPo5LlNm2nYfJQX9A +sHKSrfh68dvjSNExxi/8hdmFnnRwbAnUCI/WObGOkKdheOfdQ1AAHtLO7G65X1Cx +RctbAJWa93M+iRUN6qnB+vIbPPnI1Mc7i6mPYzgtPrM9bYqEZz69pQtHcGTfxOrU +tm+/h36CRzJBfXodBZbwQ9mZAzfkKdlArlZYIeBUw3ORQnQ7UlJgG8KsZpUhTxCc +gvMoExtlvkXcYLRUBFfZWyOi6FePzQjuCK1w58OdweJgXprEAWSvyxhmVdg4jUpX +MYKE0tZI9xwujyWjACO0myYqTdmsqyds+BgfBn96XiA9OFUH2C0/GAomhNs8uPSO +T3Gt7Ld/FByxEVrtl9A37X6bAwZO01j5tHmdXFPmMVep0R8zsWtPn3RyGAjcgcq6 +50wJRwhvofdI7wilZ0KUBsAaPj3MK52cRyD19VXKNNwt2bLDV6gcWQ8+QEMusxfp +1Dc9N9DSs+w3lGsFfpoeQ53/fXcVNJm6Bv89bH9anLGYdCdRGvZsvw+xRuglykqb +xLtL2lB6wzlRFREJoWTzCVsdpIZ8znPmk1cB0wDlbMeu6sddHmv+6fpyuvQfQmdj +D8WLRTuyxax94TmBlhJCFYxmO/y4Ivlx5C60GIRTkHpBYL/M0RjrbIszXEqcogzU +bdwjLIhdEnpJ5vy0uXwhltce8BDpenmHE7y1kHvPBiUG3vB7AIXqhohFsJU3AYUj +d1TvFKS2AsizUTLuq0Ydbnz3AxMfmnZe8qYkNu2zRygL2xTa58f/MwsHKakk3OmS +9JFZLrkkVWZKXoARctuahYtWBAsykaWVNnB6zGcdX1MGVccl930Z6QWHyydtZpQc +ivNdEGdGv9B0K7/ngNdVgD5Wd29AMMFnS8+55mLfRZDCjUmshSySaf6Ein4HD9Hr +vk6dJvBPjnI5UjeUPjmH+wcZKIjLHW/aV/6/zoxzBh61rWFlr/daec+CFZE/+epr +LRRYSmv8oY47fF4duDDhoexcvP/CH+A2Hr40OfciL4vKy3nuUDCNa59xO9JWv4NL +n3MQypC9bcaVPkXa7TK3ECq1Jgv8gwfdh5/ovG5OdZA4uIcO+aqcskt/PD252c63 +0Znww3RXXf46KT4GdKO5A377ixkUMkznnCMvottmkPxjnhQjAsQg3bJeQk8EoX8f +Pq0If4i7SRBSDtb2OH1pPmk0RVPtxlRDTVj3vS3Lci4xADFgC09n9nIvPO/55aau +O6StbJtLmpubS5giuDH3uftwuyRiLqm3gtbSKPdoTk+dJhHXbbpBknL4XYTPxSsR +IIaRds6w30vf7/IscyunMcquJlsO929SSa93UevKEIZbqbV9oGIqwkiUMdVZK09g +rW0F//Ts4a5nYdEQth/fq3JnwqeHvvUfKdasK4TtrTnUBX7qZk/K3Y1fZwjKdd/8 +t9t1z7Kb2d9hWwtY7xP8liDluVFTsq8NM54ZC2218X5ViWz1yFmF2LXvRixsmYJv +Tz8lUUnC2B/Etm1kkU4zrYK0/L77EikKVl+B7BXfEqx6ow41j7e1YZYaqmZ9mph+ +UieSdzqVYxhPwT25DrkU3r74iS28gKsbFhUaNklaFOO5iDWsKgBXT+wdZqlYQ6Fo +oPe66025iJMwK8t+d53jEduHezHO2sTMAuf2hpdaZo7+rP/hRTReAR6CmI7nkWhP +z5Kno9S+XhiSP+WTSpsoA4ubx0T94mL8NOVvSZA76TZ3ObVAP5VI/bwv6Grighor +Kpsjt7dhSJRv+RHv95sAWBeW1Fgv8XOPSAZOmpJV2qc3x3Qmj0MXIR+7+3GlUr8+ +Dit3CE1hwtxgOW0tc8kuBTfQD+wNSa9r0eUyFscEBBljpEVbLjgjVdNv4Hc+fsbT +g1JzZuUIDQZoEO2xLjxD+I7vLZKQa0J1JeZ7O+NqmSxsvSnwCWtJEWNMMxYNfwsP +rdj1zPLqn3rzSBqhroNbaDGn86BTwIqfhr+AKbvevxS6bI8IbyKm9u3BFr9cuawx +Sp1QM3NtqNStV67qR4A6U/ZyPUJdO1bxo8F3oRmJqOt7Jc93rFgkhBJ2+eMtrA75 +Om5tB9LBVSl5U5yLP0COO1QE5pqk5yuhJLT9Dyss8bWDRbSWKj83e4YXhPnq71Bm +001czylLVNUlDc69Tf7FXjtIxh2yjvOT3zeLBPXOjU0it+gAma4vgrh8/mMXnNiq +OLsVow8aKqm+Ofd6m13K5riDFgXgNI9lbvPKUSWlEqDMEqXk1oAqD4Nb5NTGSFpQ +Q4G+cHAxJCu7vcXBaZnP8uMP5IAkdg5jIPvvMRwg/aqkl/KbL98oYZ5+1xrOMuKA +LT1uCJ4MMB0lWsa1He4jPe8LneSupw7vAXlbo2VzcOI6oCSY5hV+cGQRY+LjW81q +Cu5nLq8bwgnZMSlPmwr0YrKmvh8YKyGOrmTadxykC5IC+XbrLDsw2Jd9mLIjUQ/V +4ibjeb+e0QGob22WOplCLnHGW/SnYei8KG1dxs/ahS+8vQdrI880ZJx2QJnrz0Ej +ux6tKv4mvUkqYA5hlTFeT3PTr54yA+YLcCLMfBDx4ykPQnYUBj7ONHuNSUYt1CJy +faZ7cWAbhgH+wlTFdVBVeW5D4FRbM8dMTPXyfC5ygwTJOiDu3vQKyyDkmiX7sEaC +P1JN2V55uacyR8ZAG5+Mlc4ZMx83kAIZZXTCdqa1EX8yda31FI2rDHmvW/82bmjL +pvI4Nnn9+zzJtDVCJ0B2VAZ3Edov5GzPikm3un4+mvyhUZpH4sbT0+VhPCsr1+zn +bDJyNw4AswxaaJKh2+7wBiU6h+9TP/lI8SAJHtZL7zHBH8tD10ptksLRWDs9vYqp +/3T86S2vxJL5DvLFJSAZrYOE3InS+keGmTMCdAl9I8zIworC/8uQp0N8ESebEVjA +aHotBk59lj/OW4JZ3tQkcdQWkpnUfW/x9xE2wthacHlRzYDDsFByjEqkQr0MU8VF +EGij9RCC97zyFrhv0xJm1C6wX0pcuEcuPTNBf38WyBTIfmVHHz/I5YKk5cdWG7Hq +fmccV5GKrs2BseR683HM+/u50sq0km9UrqjgFR1DjfDoRKp0guP9PqkJAnwG2nv1 +hmNtXumzkF0otP5LDKLJ84MGP8Wnb006iEdD48Lra+clRAIIuLX4A0wRQjViDp7n +OByI6ZcQd4DTMHnFPRvMkNMLYn13LghD6P9TTjQZ0KCOCwmc2TMCIhJlvzOYX6Cc +wJZYLO1ltgfnHEuh8ijv0u3d/BUpsknYKBSJGUyMEZ9iUtbFPVfXBGSTi3gcWHtl +IrM7wjswJwHWSvZKWUs+YWWJTwj0apG6ViGllwOAqR9C48uLKgFWPbMoTpolnp69 +eiij5ZHxB0i7SI80D+r65b+fqaFzVIJXVEI0zu/mIilbYBnGkhLI/Naw1m2e1qVJ +mi1JBjXLAT3pEJDh8b3Lpgw= + diff --git a/test/corpora/protected-headers/smime-sign+enc.eml b/test/corpora/protected-headers/smime-sign+enc.eml new file mode 100644 index 00000000..aa09d19f --- /dev/null +++ b/test/corpora/protected-headers/smime-sign+enc.eml @@ -0,0 +1,95 @@ +Received: from localhost (localhost [127.0.0.1]); Wed, 27 Nov 2019 + 01:15:28 -0700 (UTC-07:00) +MIME-Version: 1.0 +Content-Transfer-Encoding: base64 +Content-Type: application/pkcs7-mime; name="smime.p7m"; + smime-type="enveloped-data" +From: Alice Lovelace +To: Bob Babbage +Date: Wed, 27 Nov 2019 01:15:00 -0700 +Message-ID: +Subject: ... + +MIIPVQYJKoZIhvcNAQcDoIIPRjCCD0ICAQAxggLCMIIBXQIBADBFMC0xKzApBgNV +BAMTIlNhbXBsZSBMQU1QUyBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkCFCJT7jBtAgsf +As31ycE+Ot95phvCMA0GCSqGSIb3DQEBAQUABIIBAKswTlBs+STeesZIYAf7Gqsj +Za0rdUeDTSxt8RCa010EHb2lqKzHRwwPJkClLm6Glb09nYnQiFrEl6jbWTG3hMRD +OSt9kyqeg+MxXr2g4LoXAT+8hg/qBoF//tX+bzxhx0gx8wjxBc3bvp4esCJro7Aq +tx56BtVsIO6TA0NT0CaOcnMhIo09raR6JQX+DoPynKeXihny6TFDP7eopCgorCfR +o59O3ZMvaui6Q9KixZy3Yae8fa0ZdJu3FahIZTPdBHzbmirLxcYgp+cbTpW+Yno2 +X5GJ8eq8Y0qcc/8r6Xd3REarUxO2YbO2D6cgDj+aNnnsoG1/9psaYl8W1MSc2/Qw +ggFdAgEAMEUwLTErMCkGA1UEAxMiU2FtcGxlIExBTVBTIENlcnRpZmljYXRlIEF1 +dGhvcml0eQIUZ4K0WXNSS8H0cUcZavD9EYqqTAswDQYJKoZIhvcNAQEBBQAEggEA +RHhTarDqNLzXSaBokp2L3EwDv11KiGtMSMUQuPelNoC2nNYU1yzAF4jd+1UUo4Uu +quiHg5Hn44a9MejrVmQRLd5IEJiZGD8m5JguuOjn0ooyA6EEWUpMn6hOAKlaCiXd +kwTivKfhQFJe9Eb6TKqtvT2IEu3kXFfJKi+VyQw49+RXBmajDKJoHtumMJs8k4Ll +kJah+wD+snwHg2LCiJeSVHmpf4RvSiIJSvk206IeTxN3JecNbBpKLtIoy/CjWEZv +G3Pj/zkBbb+XhHbXo+Zk/e3aLToVG/cldx6Ti8zArOYNAzgt1G7dmJ3mnNPitEwN +O4qIozhT2Qn8P95AEV5PsDCCDHUGCSqGSIb3DQEHATAUBggqhkiG9w0DBwQIUzdf +vwulBs+AggxQMK121v6lO7W1r96RW0rsOHzsIvGyfyRTT1UuZRxVL09BQZstI5ss +5Zv8BogoKA0mLaNBKM755joUbzF5f/jMYhkW3q0Het9/HRH0mOnCSnoT4i2yzNdi +0tj8ixPT4sgPe9FOTkke9CzoJ967kj9D8u7Ik2goojttt3ViJkv3a1qrWDMiJRIJ +gOTTA6ZaQep5L92vtCobhD+i7iaktEpmbYucXs8jjMmwyxCFxHXGD/fwDk3UDgeu +8a5f66YepZdbLKB61A3rBwJMvQubuXEIEb04tG0Fgwx3Ao2NshN+XRk/y+uhQKdC +5ZduTxk5sokA+H4nzVv0IUkAAI+8FwY5ZWFGlncKUM/wvrGHQq3R/utChFauOHxD +7vZQLM91TcQzVWdHfJGPtp+ekjRlu9UqatQgc1ogObw3PGYlJc90Gl7AZHAsYncU +jsMbdsweuFuYNHJ8lR5VMo6L4bCNMy+tQBOfYTF1el+i9S3r3SWdBP+uLiKgDQ52 +/o4shxoi+YOf9k8wRR0iDKqwzcJuABplpgA9qjsQNqBKF5t5p3l3ihH1mfh8FaPL +ab0aDC7uunY5g44qXcG9YS+j5wUFuxgYyGkVcJq3xIit9YbEy8uPxJFz4g0vNC+r +uUSsztbLyHkhv7vnCTAlmjgG9eDpW/tEC/85pLOV1HUooD05eRfkjU+1XsccX8DG +iCax2C6W3cc1SC/d3a1+27OcgvPdDcb7zuL3v6qgqbN+7GDrcQHQRFMd2vd6+xGk +NWZQMBZVHmdCcKGl9YaH0RgkGH5beTRKEV1wBafuVOwTEwl/FuZzD4oHrOaP3GLO +cLxi44her/hNxtxDc2Lw0VQcxD8A55OkCt9+u9M5/YPj41FWyH6kdh86p958gzF5 +EpwCnQDe+s7OrwFVV00DEJhqtEcxRCSSW8dS4hVEhVxQJ56liJP+VZ+LTUJBelt4 +mfSpSqxeJnmyY0nmhEbZKVbK95a1WYMJCEpk2n1g/bQGqJKRryGwbEF9WqqHuvPo +Bv/BfinoUL3Kd3g+hgSCR4mCg5EhEsCx21jEqEggzb2XMcA+knGUYxSWj322pZfW +LDh50gkL3GQSmm9fOvjdK40GwZv8HUdLXuAQ/J19PafMaDkd4jzRi37VBqdDgLY3 +u6K+oFKhG4oqQYa/er+ZGAqqldTmu8HGCsjm6kGZvSAocJg0UnLPBNI0/iB0BYGf +KJk302jy8kfAXGSiWrYDNbTuDzFMD0zsbHbM07AOOROGwKv5TxAF1EHHTxGb3IKI +jRkVBL7QdRtDH03zlxv0lnFwiuCrzLrQdUuEG/0wt8RaNr+p8hAo0YEGbB9jmbax +CSLLWeNbMOo8eIi3Mft4qmDXp3TEuHHru8kbvA36vQ8+dunSf2BcecyM6UAYBqaw +SCcxQmEcyMuyjSLVerVfMl5lwlmM+qabxHq0hpJHnCR3Vl2qX3CiRWpVlNaBVyTf +793bAm7DU7G+Tzt5gdgE4s41aZt8fFXyclhH1QLPNSnctxJjuW1gJJ0h51iCQJp2 +TgzDw35oqvBxbN3yqCFjScsQXPXYErGWkLrAkUurff4x/ZAizFkmjjdpyaIK9JBw +QRyrYYQ8pJhXJe9BrP3OS6evFlsWZW1MaoQcOUMWsuVucE0e4AQRGlPixDjJWW7L +I6AQ3KUW6ggzDJksaYHDiuEoBa7vcYoTar+/AhNjYMjkQX/3kptQryqy+xke0t8O +EPQER0Wur2IpvM6YsvI/SoeFwxMb4Zm5AFvvibiCCmmoJc4A9E1tZ/sMstHyZ5iu +tJqu1M5B0DIoFdB5pzbZYCkgN2n7EY23JS7E/ozOrzYuOIVUJVtB5awqmuSLmI+N +R91g4FMEfLYC1HYKYlaknX2zmrx8+Z8MEJNM2K0q8wPBnm86OpGeJmlZhFwT2x0R +eJpKcfLGroXYh2Gb6BxwIfKjOOTXCoIFP02JbTJ7clc/2ei0BN6JxywPkH4renaP +SkuNBgbexfZGBhMTlR+CtKLEUmw5bxBTDwjjcvzWDPhy/VurLQxhOqYnbhZW21SV +4qMrJ4uGXEhylnP0FD+HR4mB2epYcW3dFj4cGN3B2Y5NnOTw0Z7fi4S0BPdvYjP9 +LL5WZ6p90mII9wcunGCRnLUUYumRnIbhVHIBTTIRI5PUSVFfEuotrDZ9oZcwYkO7 +fQX21gJCzvJyp8ft01HX4Kc4mN/FMPgGcmq70N335yQ4mQ/eSvTNn7E+35ZGn9f8 +PI7QPJRhdUkBZCnwyv+OwK2VzySxnqNfPaZk168foGRd9eFCw80L4U+SuLDQH6ZT +o++VKk4Ce2jx1khoig16wic0dVFwt4bmybNz4u/qdobYr5fs7dKPHHO02SBvAl60 +16foheiBtV2VA8mEBA1BhcNmKYegu+RGhmGfNDuZB8XdbPQ6M+N+ilEj/6rr+wgD +gcmEyAGNwJkmWpbyrm9M4lDtzemv5N5V32ppGizEt6c0xlkiULllwGdWey3+YRez +7b+Kl/uIpDuRbp5Tf43dyPsy/cx4DNm5kAB4CcyyVlXPaqXm0llEPYBmaMW3O+D2 +5v4Wj1qwIRO5qgI8FyVnX6sm/oucfg5l172edaCG8f42gIMNfQBgWVMsSG7Nt00x +dJo/OGtACwnY47ohMFG0BejWueAksdnqVWCIto989iBHgegNx5jUCycB/YOm0xh0 +pfeNjA9PwZMUpjlqrjDFIan/UFYAZH5ISSV7G30oRKJ3TTEshShXP2K3cn7Fa9W+ +H/jyTEQGfCiTq7Xx5FrOIJBmKjylkF7oGlIBxJgKKRm0iD/sGNTaSJ6Pl8/K6dEz +zsMwEFTawnWVq32Xn3d6/+FADZ9lGhC5WwVgaQHRb/9Ejt1mBdptmXjEj5w0YOib +xFer54LrQgvBWEYRqDneh3bI53BudbTl7YitqULVGETe+k1T0NbcyElrr2Y/NKHk +rPMarAfByookkJrDtVh3VrAm2ows7OwvKGyoNybjlyczjt7xosatZ1xkgb9mtR5i +E2l9ajSR4SzQjHoboRyOCwl5ZgLV/+yp3jTkNcUkFDRtkVbGfascBIMe0ifUGfvP +mJ9AQHZxdfm99KlQjCZzR8CBUvR+zsT43jr91CQKSSEvPMl6vVRV2thiWw3VGgP+ +c8i5zj6+zCnlEdSWiIeFwOJ9/ewKSdU9pGrA0OQtXbYQlDCKuGK1Vgy6jJCeglDH +T6gVNy5ip593wWWfOVxVEWUygi6JCdS27b5+P/wlNjTrzpZ4yWDCpyogyrT1gf1/ +GgvdGuWWinKSLOyh1fJ1p9WoDWcqH98QhJXLV+X3OC+tmMofytmHgXN8jjVsWSRa +VWrFUarMs2hZDWf6e6ncwvMC8QliiszrKXQNckxvBuh5hug9WKurVj4CIWnoqXFh +OqlO+VbqZSj+TT5pCN//370vsIZIn5UbrpDmUP0rUvdTGz9iWQRUl6R2g2h286s6 +pAGHv9luXCoPJ5uPTwcbBSl/js6J+K5McyqRl4fucacfVFnMuDpET/tT1eAROP3F +DOBKqV5YOO0rWMexzMLJUEQ/eGSwfp7wv8on7jeGxAexMqyWCrhRk9G2ZwiT4L7Q +rX4NIDj6oujCCkeFUATs0pGKwEFGmpbEUfDOsioWoVYJZPsO9kAGq6bhbKACOkeZ +v95ha/3CleYXGUUNtzLsCx+c9Zp/Wl+0PcT3ZSWhmRbXiIvz+ntHVe47PHxbvH6a +ZG7YGc/9u3jTvJJyYtQO54uGET/eFWSxCUo5/VfsheOuLdXN7JnVi6ooF+c7WUZd +61FwfDwNf8z0GWs3EotozrWyBgKS5VFP99vZM64nSqu9v5PSzmb0AY/Zc5KhVXVY +zQqmO3keXq92Fejtgyd/O9ITZf5GkMQVU7+IT52JxFRQplkbTHJj4HRGtGHtIyPW +Rmf9qSZz8QgVyAUKK1k+kLBJTHN3CWIB6S9hO42HWEFvLVl8wPWW5aLYTsVMGnMU +aZ35M35odjrvY9B0INMpL53Hm7qH1w/h9QCv+xsFmanYsoylwbuKW2TcSnWB74C7 +Wy0NmCkaM+JweOgygffWicLGJ3jKWccykTUZtodz1ectNHh24puZICnvfzwjte+n +eSQqJfHMsra6V8BcshpwmvPylHnkU+2KyhQ8430OR/qaXAYJ7EWRBEFe4EIpxzfL +zQF0LwbhpAstpcjOlJfEHmQiWx8ASzE1LMSfZo148sXYEWsJL7t5tWs= + From a8bf94af44b21449d8f28478013400c096b0241d Mon Sep 17 00:00:00 2001 From: Daniel Kahn Gillmor Date: Tue, 28 Apr 2020 14:57:18 -0400 Subject: [PATCH 140/427] tests/smime: Verify cryptographic message status When consuming a signed+encrypted S/MIME message generated by emacs, we expect to see the same cryptographic properties for the message as a whole. This is not done correctly yet, so the test is marked as known broken. Signed-off-by: Daniel Kahn Gillmor --- test/T355-smime.sh | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/test/T355-smime.sh b/test/T355-smime.sh index e92a7b16..f3956a34 100755 --- a/test/T355-smime.sh +++ b/test/T355-smime.sh @@ -88,4 +88,13 @@ This is a test encrypted message. EOF test_expect_equal_file EXPECTED OUTPUT +test_begin_subtest "Cryptographic message status (encrypted+signed)" +test_subtest_known_broken +output=$(notmuch show --format=json --decrypt=true subject:"test encrypted message 001") +test_json_nodes <<<"$output" \ + 'crypto_encrypted:[0][0][0]["crypto"]["decrypted"]["status"]="full"' \ + 'crypto_sigok:[0][0][0]["crypto"]["signed"]["status"][0]["status"]="good"' \ + 'crypto_fpr:[0][0][0]["crypto"]["signed"]["status"][0]["fingerprint"]="616F46CD73834C63847756AF0DFB64A6E0972A47"' \ + 'crypto_uid:[0][0][0]["crypto"]["signed"]["status"][0]["userid"]="CN=Notmuch Test Suite"' + test_done From 92149485cb5cd4af3d2c6c4856a2423da92dc727 Mon Sep 17 00:00:00 2001 From: Daniel Kahn Gillmor Date: Tue, 28 Apr 2020 14:57:19 -0400 Subject: [PATCH 141/427] tests/smime: Test indexing cleartext of envelopedData These tests describe some simple behavior we would expect to work if we were to correctly index the cleartext of encrypted S/MIME messages (PKCS#7 envelopedData). Of course, they don't currently pass, so we mark them known-broken. Signed-off-by: Daniel Kahn Gillmor --- test/T355-smime.sh | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/test/T355-smime.sh b/test/T355-smime.sh index f3956a34..14e4531d 100755 --- a/test/T355-smime.sh +++ b/test/T355-smime.sh @@ -97,4 +97,26 @@ test_json_nodes <<<"$output" \ 'crypto_fpr:[0][0][0]["crypto"]["signed"]["status"][0]["fingerprint"]="616F46CD73834C63847756AF0DFB64A6E0972A47"' \ 'crypto_uid:[0][0][0]["crypto"]["signed"]["status"][0]["userid"]="CN=Notmuch Test Suite"' +test_begin_subtest "encrypted+signed message is known to be encrypted, but signature is unknown" +test_subtest_known_broken +output=$(notmuch search subject:"test encrypted message 001") +test_expect_equal "$output" "thread:0000000000000002 2000-01-01 [1/1] Notmuch Test Suite; test encrypted message 001 (encrypted inbox)" + +test_begin_subtest "Encrypted body is not indexed" +output=$(notmuch search 'this is a test encrypted message') +test_expect_equal "$output" "" + +test_begin_subtest "Reindex cleartext" +test_expect_success "notmuch reindex --decrypt=true subject:'test encrypted message 001'" + +test_begin_subtest "signature is now known" +test_subtest_known_broken +output=$(notmuch search subject:"test encrypted message 001") +test_expect_equal "$output" "thread:0000000000000002 2000-01-01 [1/1] Notmuch Test Suite; test encrypted message 001 (encrypted inbox signed)" + +test_begin_subtest "Encrypted body is indexed" +test_subtest_known_broken +output=$(notmuch search 'this is a test encrypted message') +test_expect_equal "$output" "thread:0000000000000002 2000-01-01 [1/1] Notmuch Test Suite; test encrypted message 001 (encrypted inbox signed)" + test_done From 488e91f42b95c116b387212c90ea47c43c716f5b Mon Sep 17 00:00:00 2001 From: Daniel Kahn Gillmor Date: Tue, 28 Apr 2020 14:57:20 -0400 Subject: [PATCH 142/427] test-lib.sh: add test_valid_json This test does exactly what it says on the tin. It expects JSON data to be parseable by Python, at least. Signed-off-by: Daniel Kahn Gillmor --- test/test-lib.sh | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/test/test-lib.sh b/test/test-lib.sh index cb5bb894..43fffa09 100644 --- a/test/test-lib.sh +++ b/test/test-lib.sh @@ -524,6 +524,12 @@ test_expect_equal_json () { test_expect_equal "$output" "$expected" "$@" } +# Ensure that the argument is valid JSON data. +test_valid_json () { + PYTHONIOENCODING=utf-8 $NOTMUCH_PYTHON -c "import sys, json; json.load(sys.stdin)" <<<"$1" + test_expect_equal "$?" 0 +} + # Sort the top-level list of JSON data from stdin. test_sort_json () { PYTHONIOENCODING=utf-8 $NOTMUCH_PYTHON -c \ From b1a04bddc27c80411cf1ca148b3c33720bc8e785 Mon Sep 17 00:00:00 2001 From: Daniel Kahn Gillmor Date: Tue, 28 Apr 2020 14:57:21 -0400 Subject: [PATCH 143/427] tests/smime: add tests for S/MIME SignedData Add a simple S/MIME SignedData message, taken from an upcoming draft of https://datatracker.ietf.org/doc/draft-autocrypt-lamps-protected-headers/ RFC 8551 describes a SignedData, a one-part clearsigned object that is more resistant to common patterns of MTA message munging than multipart/signed (but has the downside that it is only readable by clients that implement S/MIME). To make sure sure notmuch can handle this kind of object, we want to know a few things: Already working: - Is the content of the SignedData object indexed? It actually is right now because of dumb luck -- i think we're indexing the raw CMS object and it happens to contain the cleartext of the message in a way that we can consume it before passing it on to Xapian. - Are we accidentally indexing the embedded PKCS#7 certificates? We don't want to, and for some reason I don't understand, our indexing is actually skipping the embedded certificates already. That's good! Still need fixing: - do we know the MIME type of the embedded part? - do we know that the message is signed? - can notmuch-show read its content? - can notmuch-show indicate the signature validity? - can notmuch-reply properly quote and attribute content? Signed-off-by: Daniel Kahn Gillmor --- test/T355-smime.sh | 77 +++++++++++++++++++++ test/corpora/pkcs7/smime-onepart-signed.eml | 51 ++++++++++++++ 2 files changed, 128 insertions(+) create mode 100644 test/corpora/pkcs7/smime-onepart-signed.eml diff --git a/test/T355-smime.sh b/test/T355-smime.sh index 14e4531d..117fa2b9 100755 --- a/test/T355-smime.sh +++ b/test/T355-smime.sh @@ -119,4 +119,81 @@ test_subtest_known_broken output=$(notmuch search 'this is a test encrypted message') test_expect_equal "$output" "thread:0000000000000002 2000-01-01 [1/1] Notmuch Test Suite; test encrypted message 001 (encrypted inbox signed)" +add_email_corpus pkcs7 + +test_begin_subtest "index PKCS#7 SignedData message" +output=$(notmuch search --output=messages Thanks) +expected=id:smime-onepart-signed@protected-headers.example +test_expect_equal "$expected" "$output" + +test_begin_subtest "do not index embedded certificates from PKCS#7 SignedData" +output=$(notmuch search --output=messages 'LAMPS Certificate') +expected='' +test_expect_equal "$expected" "$output" + +test_begin_subtest "know the MIME type of the embedded part in PKCS#7 SignedData" +test_subtest_known_broken +output=$(notmuch search --output=messages 'mimetype:text/plain') +expected=id:smime-onepart-signed@protected-headers.example +test_expect_equal "$expected" "$output" + +test_begin_subtest "PKCS#7 SignedData message is tagged 'signed'" +test_subtest_known_broken +output=$(notmuch dump id:smime-onepart-signed@protected-headers.example) +expected='#notmuch-dump batch-tag:3 config,properties,tags ++inbox +signed +unread -- id:smime-onepart-signed@protected-headers.example' +test_expect_equal "$expected" "$output" + +test_begin_subtest "show contents of PKCS#7 SignedData message" +test_subtest_known_broken +output=$(notmuch show --format=raw --part=2 id:smime-onepart-signed@protected-headers.example) +whitespace=' ' +expected="Bob, we need to cancel this contract. + +Please start the necessary processes to make that happen today. + +Thanks, Alice +--${whitespace} +Alice Lovelace +President +OpenPGP Example Corp" +test_expect_equal "$expected" "$output" + +test_begin_subtest "reply to PKCS#7 SignedData message with proper quoting and attribution" +test_subtest_known_broken +output=$(notmuch reply id:smime-onepart-signed@protected-headers.example) +expected="From: Notmuch Test Suite +Subject: Re: The FooCorp contract +To: Alice Lovelace , Bob Babbage +In-Reply-To: +References: + +On Tue, 26 Nov 2019 20:11:29 -0400, Alice Lovelace wrote: +> Bob, we need to cancel this contract. +>${whitespace} +> Please start the necessary processes to make that happen today. +>${whitespace} +> Thanks, Alice +> --${whitespace} +> Alice Lovelace +> President +> OpenPGP Example Corp" +test_expect_equal "$expected" "$output" + +test_begin_subtest "show PKCS#7 SignedData outputs valid JSON" +output=$(notmuch show --format=json id:smime-onepart-signed@protected-headers.example) +test_valid_json "$output" + +test_begin_subtest "Verify signature on PKCS#7 SignedData message" +test_subtest_known_broken +output=$(notmuch show --format=json id:smime-onepart-signed@protected-headers.example) +test_json_nodes <<<"$output" \ + 'crypto:[0][0][0]["crypto"]["signed"]["status"][0]={ + "created" : 1574813489, + "expires" : 2611032858, + "fingerprint" : "702BA4B157F1E2B7D16B0C6A5FFC8A7DE2057DEB", + "userid" : "CN=Alice Lovelace", + "status" : "good" + }' + test_done diff --git a/test/corpora/pkcs7/smime-onepart-signed.eml b/test/corpora/pkcs7/smime-onepart-signed.eml new file mode 100644 index 00000000..070303b7 --- /dev/null +++ b/test/corpora/pkcs7/smime-onepart-signed.eml @@ -0,0 +1,51 @@ +Received: from localhost (localhost [127.0.0.1]); Tue, 26 Nov 2019 + 20:11:46 -0400 (UTC-04:00) +Content-Transfer-Encoding: base64 +Content-Type: application/pkcs7-mime; name="smime.p7m"; + smime-type="signed-data" +MIME-Version: 1.0 +From: Alice Lovelace +To: Bob Babbage +Date: Tue, 26 Nov 2019 20:11:29 -0400 +Subject: The FooCorp contract +Message-ID: + +MIIHRQYJKoZIhvcNAQcCoIIHNjCCBzICAQExDTALBglghkgBZQMEAgEwggHJBgkq +hkiG9w0BBwGgggG6BIIBtkNvbnRlbnQtVHlwZTogdGV4dC9wbGFpbjsgY2hhcnNl +dD0idXMtYXNjaWkiDQpGcm9tOiBBbGljZSBMb3ZlbGFjZSA8YWxpY2VAc21pbWUu +ZXhhbXBsZT4NClRvOiBCb2IgQmFiYmFnZSA8Ym9iQHNtaW1lLmV4YW1wbGU+DQpE +YXRlOiBUdWUsIDI2IE5vdiAyMDE5IDIwOjExOjI5IC0wNDAwDQpTdWJqZWN0OiBU +aGUgRm9vQ29ycCBjb250cmFjdA0KTWVzc2FnZS1JRDogPHNtaW1lLW9uZXBhcnQt +c2lnbmVkQHByb3RlY3RlZC1oZWFkZXJzLmV4YW1wbGU+DQoNCkJvYiwgd2UgbmVl +ZCB0byBjYW5jZWwgdGhpcyBjb250cmFjdC4NCg0KUGxlYXNlIHN0YXJ0IHRoZSBu +ZWNlc3NhcnkgcHJvY2Vzc2VzIHRvIG1ha2UgdGhhdCBoYXBwZW4gdG9kYXkuDQoN +ClRoYW5rcywgQWxpY2UNCi0tIA0KQWxpY2UgTG92ZWxhY2UNClByZXNpZGVudA0K +T3BlblBHUCBFeGFtcGxlIENvcnANCqCCA3IwggNuMIICVqADAgECAhRngrRZc1JL +wfRxRxlq8P0RiqpMCzANBgkqhkiG9w0BAQ0FADAtMSswKQYDVQQDEyJTYW1wbGUg +TEFNUFMgQ2VydGlmaWNhdGUgQXV0aG9yaXR5MCAXDTE5MTEyMDA2NTQxOFoYDzIw +NTIwOTI3MDY1NDE4WjAZMRcwFQYDVQQDEw5BbGljZSBMb3ZlbGFjZTCCASIwDQYJ +KoZIhvcNAQEBBQADggEPADCCAQoCggEBAMPurfll0bYkDPMkY1kNn2xXsAqHSGVF ++gWNNk3mbhF6BABhLJqDjei5aLXFE3Rq9/RRNivCMrTipF1XsbMIAKgQqr/GI1Q6 +yN8lfNsK5uU3d9kw5cOyEooGpOGUrvlKMD0LPGDt6MaiJj+KJ2TR73Wd4rfRIIJo +FMmV9HZkOs+Tvcg8x6SzGhNq18X2HD10MD78eLXKm039obRD+z2JwWvGvrLbNBey +O5A+aMxmCPXRoP1xrNZWBFgKB+WGYDRXW5CXXChthTwMBXFWf4aBpurKMZAyjK2E +grQafn6h/DFddQz/NtT6Dr7UhJ2hfFFEW2rYbNsiqQAdllCb4FucWuECAwEAAaOB +lzCBlDAMBgNVHRMBAf8EAjAAMB4GA1UdEQQXMBWBE2FsaWNlQHNtaW1lLmV4YW1w +bGUwEwYDVR0lBAwwCgYIKwYBBQUHAwQwDwYDVR0PAQH/BAUDAwegADAdBgNVHQ4E +FgQUrC5UWqT9VRivLuhmRDjRJdHXAHkwHwYDVR0jBBgwFoAUt1JNc8CIPbLDeloM +85T394Cid9swDQYJKoZIhvcNAQENBQADggEBAHvqjhjPvKtVIVyleoutwa10jir3 +dooJcQIILM1AunjJ6yHpuuppkc0m3BhwnlOptTKb2EnvSIkTiMY037IBlHWW217Q +cUpggEozgQm6Yb77aGptRovPi2XToEdpA8K//02I1jur1H1z8HqzVjMeHCqRaG3Z +r4C2AngGSkb6D4yZkxBX8CjtHAsUon06UxYsGYRcVykgk3Qek9qxPScSX8yai1K7 +7xGcKUCLfIV/JMpv7ysPtXG7Jd62oNnp1T/3+KoP9JlLs5AiPLC13fjeYALPcHVG +UXEwdIDp1AB/Zu0a6apHQqICncqRhEB4+hompiQHtlp3TqeAWXQbQUc437sxggHZ +MIIB1QIBATBFMC0xKzApBgNVBAMTIlNhbXBsZSBMQU1QUyBDZXJ0aWZpY2F0ZSBB +dXRob3JpdHkCFGeCtFlzUkvB9HFHGWrw/RGKqkwLMAsGCWCGSAFlAwQCAaBpMBgG +CSqGSIb3DQEJAzELBgkqhkiG9w0BBwEwHAYJKoZIhvcNAQkFMQ8XDTE5MTEyNzAw +MTEyOVowLwYJKoZIhvcNAQkEMSIEILsI9kL3zfZiVOEDjAUWrbjHjGMLoGUwEqYH +pOA9XZ+QMA0GCSqGSIb3DQEBAQUABIIBAGDat8UYN9MShlKEw3hYVVUk6HKO6Xjp +rdgCBKpoyoWJy0VJis0xHxaT2gn/+TPu8a5l6RslgeALjMyflzyzAmrqnknQQG8K +bvbt/MwpU/TxnmxT+2oP9TVmAx/IQOq4pQ35uK7peSPck2CcTvZjHTeVBWcsLVEk +hELoSD8XFRBo34qdinBzW0/sMlyK1XnlN7khKry1g7uaXcurVqptRA1rWOvCOt72 +aElKG/Q7OoVgHxbUpdzV3Hqe9/UeTRDUqCs++on2pLlA0TA0Pq8RQ0hDHD/p0t41 +1RAT1/RbnGQiVfRilMan+VGT4shokb1RoANy/1rOO9ZKlyWToYdRl9E= From b415ec06c309974247d202c21a0f9f1b0d828f5d Mon Sep 17 00:00:00 2001 From: Daniel Kahn Gillmor Date: Tue, 28 Apr 2020 14:57:22 -0400 Subject: [PATCH 144/427] test/protected-headers: Add tests for S/MIME protected headers Recognize the protected subject for S/MIME example protected header messages. Signed-off-by: Daniel Kahn Gillmor --- test/T356-protected-headers.sh | 38 +++++++++++++++++++++++++++++++--- 1 file changed, 35 insertions(+), 3 deletions(-) diff --git a/test/T356-protected-headers.sh b/test/T356-protected-headers.sh index 925805df..b7a83715 100755 --- a/test/T356-protected-headers.sh +++ b/test/T356-protected-headers.sh @@ -1,14 +1,14 @@ #!/usr/bin/env bash -# TODO: -# * check S/MIME as well as PGP/MIME - test_description='Message decryption with protected headers' . $(dirname "$0")/test-lib.sh || exit 1 ################################################## +test_require_external_prereq gpgsm + add_gnupg_home +add_gpgsm_home add_email_corpus protected-headers @@ -155,6 +155,38 @@ test_begin_subtest "identify message that had a legacy display part skipped duri output=$(notmuch search --output=messages property:index.repaired=skip-protected-headers-legacy-display) test_expect_equal "$output" id:protected-with-legacy-display@crypto.notmuchmail.org +for variant in multipart-signed onepart-signed; do + test_begin_subtest "verify signed PKCS#7 subject ($variant)" + test_subtest_known_broken + output=$(notmuch show --verify --format=json "id:smime-${variant}@protected-headers.example") + test_json_nodes <<<"$output" \ + 'signed_subject:[0][0][0]["crypto"]["signed"]["headers"]=["Subject"]' \ + 'sig_good:[0][0][0]["crypto"]["signed"]["status"][0]["status"]="good"' \ + 'sig_fpr:[0][0][0]["crypto"]["signed"]["status"][0]["fingerprint"]="702BA4B157F1E2B7D16B0C6A5FFC8A7DE2057DEB"' \ + 'sig_uid:[0][0][0]["crypto"]["signed"]["status"][0]["userid"]="CN=Alice Lovelace"' \ + 'not_encrypted:[0][0][0]["crypto"]!"decrypted"' +done + +for variant in sign+enc sign+enc+legacy-disp; do + test_begin_subtest "confirm signed and encrypted PKCS#7 subject ($variant)" + test_subtest_known_broken + output=$(notmuch show --decrypt=true --format=json "id:smime-${variant}@protected-headers.example") + test_json_nodes <<<"$output" \ + 'signed_subject:[0][0][0]["crypto"]["signed"]["headers"]=["Subject"]' \ + 'sig_good:[0][0][0]["crypto"]["signed"]["status"][0]["status"]="good"' \ + 'sig_fpr:[0][0][0]["crypto"]["signed"]["status"][0]["fingerprint"]="702BA4B157F1E2B7D16B0C6A5FFC8A7DE2057DEB"' \ + 'sig_uid:[0][0][0]["crypto"]["signed"]["status"][0]["userid"]="CN=Alice Lovelace"' \ + 'encrypted:[0][0][0]["crypto"]["decrypted"]={"status":"full","header-mask":{"Subject":"..."}}' +done + +test_begin_subtest "confirm encryption-protected PKCS#7 subject (enc+legacy-disp)" +test_subtest_known_broken +output=$(notmuch show --decrypt=true --format=json "id:smime-enc+legacy-disp@protected-headers.example") +test_json_nodes <<<"$output" \ + 'encrypted:[0][0][0]["crypto"]["decrypted"]={"status":"full","header-mask":{"Subject":"..."}}' \ + 'no_sig:[0][0][0]["crypto"]!"signed"' + + # TODO: test that a part that looks like a legacy-display in # multipart/signed, but not encrypted, is indexed and not stripped. From 9055dfdae41ba762b12434fe678fc524ea3ed618 Mon Sep 17 00:00:00 2001 From: Daniel Kahn Gillmor Date: Thu, 19 Mar 2020 00:15:08 -0400 Subject: [PATCH 145/427] tests: disable CRL checks from gpgsm GPGME has a strange failure mode when it is in offline mode, and/or when certificates don't have any CRLs: in particular, it refuses to accept the validity of any certificate other than a "root" cert. This can be worked around by setting the `disable-crl-checks` configuration variable for gpgsm. I've reported this to the GPGME upstream at https://dev.gnupg.org/T4883, but I have no idea how it will be resolved. In the meantime, we'll just work around it. Note that this fixes the test for verification of id:smime-multipart-signed@protected-headers.example, because multipart/signed messages are already handled correctly (one-part PKCS#7 messages will get fixed later). Signed-off-by: Daniel Kahn Gillmor --- test/T356-protected-headers.sh | 2 +- test/test-lib.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/test/T356-protected-headers.sh b/test/T356-protected-headers.sh index b7a83715..520cb71c 100755 --- a/test/T356-protected-headers.sh +++ b/test/T356-protected-headers.sh @@ -157,7 +157,7 @@ test_expect_equal "$output" id:protected-with-legacy-display@crypto.notmuchmail. for variant in multipart-signed onepart-signed; do test_begin_subtest "verify signed PKCS#7 subject ($variant)" - test_subtest_known_broken + [ "$variant" = multipart-signed ] || test_subtest_known_broken output=$(notmuch show --verify --format=json "id:smime-${variant}@protected-headers.example") test_json_nodes <<<"$output" \ 'signed_subject:[0][0][0]["crypto"]["signed"]["headers"]=["Subject"]' \ diff --git a/test/test-lib.sh b/test/test-lib.sh index 43fffa09..5c8eab7c 100644 --- a/test/test-lib.sh +++ b/test/test-lib.sh @@ -144,7 +144,7 @@ add_gpgsm_home () echo "$fpr S relax" >> "$GNUPGHOME/trustlist.txt" gpgsm --quiet --batch --no-tty --no-common-certs-import --disable-dirmngr --import < $NOTMUCH_SRCDIR/test/smime/ca.crt echo "4D:E0:FF:63:C0:E9:EC:01:29:11:C8:7A:EE:DA:3A:9A:7F:6E:C1:0D S" >> "$GNUPGHOME/trustlist.txt" - echo include-certs::1 | gpgconf --output /dev/null --change-options gpgsm + printf '%s::1\n' include-certs disable-crl-checks | gpgconf --output /dev/null --change-options gpgsm gpgsm --batch --no-tty --no-common-certs-import --pinentry-mode=loopback --passphrase-fd 3 \ --disable-dirmngr --import "$NOTMUCH_SRCDIR/test/smime/bob.p12" >>"$GNUPGHOME"/import.log 2>&1 3<<<'' test_debug "cat $GNUPGHOME/import.log" From accfee5c6e1f52979adf153126945dd7f49d1b4d Mon Sep 17 00:00:00 2001 From: David Bremner Date: Mon, 4 May 2020 10:49:43 -0300 Subject: [PATCH 146/427] test: known broken test for reindex tag preservation In id:1588595993-ner-8.651@TPL520 Franz Fellner reported that tags starting with 'attachment' are removed by 'notmuch reindex'. This is probably related to the use of STRNCMP_LITERAL in _notmuch_message_remove_indexed_terms. --- test/T700-reindex.sh | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/test/T700-reindex.sh b/test/T700-reindex.sh index 9e795896..7b7e52de 100755 --- a/test/T700-reindex.sh +++ b/test/T700-reindex.sh @@ -33,6 +33,15 @@ notmuch reindex '*' notmuch dump > OUTPUT test_expect_equal_file initial-dump OUTPUT +test_begin_subtest 'reindex preserves tags with special prefixes' +test_subtest_known_broken +notmuch tag +attachment2 +encrypted2 +signed2 '*' +notmuch dump > EXPECTED +notmuch reindex '*' +notmuch dump > OUTPUT +notmuch tag -attachment2 -encrypted2 -signed2 '*' +test_expect_equal_file EXPECTED OUTPUT + test_begin_subtest 'reindex moves a message between threads' notmuch search --output=threads id:87iqd9rn3l.fsf@vertex.dottedmag > EXPECTED # re-parent From 45cfeb2e557bb4eeb2a9821fa44207eeb6cd335c Mon Sep 17 00:00:00 2001 From: David Bremner Date: Mon, 4 May 2020 10:55:43 -0300 Subject: [PATCH 147/427] lib: replace STRNCMP_LITERAL in __message_remove_indexed_terms strncmp looks for a prefix that matches, which is very much not what we want here. This fixes the bug reported by Franz Fellner in id:1588595993-ner-8.651@TPL520 --- lib/message.cc | 6 +++--- test/T700-reindex.sh | 1 - 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/lib/message.cc b/lib/message.cc index 5c9b58b2..0fa0eb3a 100644 --- a/lib/message.cc +++ b/lib/message.cc @@ -751,9 +751,9 @@ _notmuch_message_remove_indexed_terms (notmuch_message_t *message) const char *tag = notmuch_tags_get (tags); - if (STRNCMP_LITERAL (tag, "encrypted") != 0 && - STRNCMP_LITERAL (tag, "signed") != 0 && - STRNCMP_LITERAL (tag, "attachment") != 0) { + if (strcmp (tag, "encrypted") != 0 && + strcmp (tag, "signed") != 0 && + strcmp (tag, "attachment") != 0) { std::string term = tag_prefix + tag; message->doc.add_term (term); } diff --git a/test/T700-reindex.sh b/test/T700-reindex.sh index 7b7e52de..3d7c930d 100755 --- a/test/T700-reindex.sh +++ b/test/T700-reindex.sh @@ -34,7 +34,6 @@ notmuch dump > OUTPUT test_expect_equal_file initial-dump OUTPUT test_begin_subtest 'reindex preserves tags with special prefixes' -test_subtest_known_broken notmuch tag +attachment2 +encrypted2 +signed2 '*' notmuch dump > EXPECTED notmuch reindex '*' From 02a2bf1b256f4eeb8c5e08129a2f0976da2569ce Mon Sep 17 00:00:00 2001 From: Daniel Kahn Gillmor Date: Thu, 7 May 2020 15:26:45 -0400 Subject: [PATCH 148/427] notmuch(1): clarify documentation about --option/value separators id:CA+Tk8fzRiqxWpd=r8=DRvEewNZXUZgD7MKyRLB1A=R-LxxGEZw@mail.gmail.com started a thread of discussion that showed that the cli's current idiosyncrasies around dealing with boolean options were not understandable. This attempts to improve the documentation at least (actual changes to the API might be better, but have not reached consensus). Note that no one in the discussion thread identified any other (non-boolean) command-line options that cannot use space as a separator. If such an option is identified (or introduced in the future), it should be added explicitly to this part of the manual. Signed-off-by: Daniel Kahn Gillmor --- doc/man1/notmuch.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/doc/man1/notmuch.rst b/doc/man1/notmuch.rst index d2cd8da5..fecfd08a 100644 --- a/doc/man1/notmuch.rst +++ b/doc/man1/notmuch.rst @@ -128,9 +128,9 @@ OPTION SYNTAX ------------- All options accepting an argument can be used with '=' or ':' as a -separator. For the cases where it's not ambiguous (in particular -excluding boolean options), a space can also be used. The following -are all equivalent: +separator. Except for boolean options (which would be ambiguous), a +space can also be used as a separator. The following are all +equivalent: :: From 627460d7bbbb6b95a07084c2b6fc7f647a5547e1 Mon Sep 17 00:00:00 2001 From: Daniel Kahn Gillmor Date: Fri, 8 May 2020 19:24:38 -0400 Subject: [PATCH 149/427] test-lib: mark function variables as local Several functions in test/test-lib.sh used variable names that are also used outside of those functions (e.g. $output and $expected are used in many of the test scripts), but they are not expected to communicate via those variables. We mark those variables "local" within test-lib.sh so that they do not get clobbered when used outside test-lib. We also move the local variable declarations to beginning of each function, to avoid weird gotchas with local variable declarations as described in https://tldp.org/LDP/abs/html/localvar.html. Signed-off-by: Daniel Kahn Gillmor --- test/test-lib.sh | 31 ++++++++++++++++++++++--------- 1 file changed, 22 insertions(+), 9 deletions(-) diff --git a/test/test-lib.sh b/test/test-lib.sh index 5c8eab7c..58972339 100644 --- a/test/test-lib.sh +++ b/test/test-lib.sh @@ -109,7 +109,6 @@ unset ALTERNATE_EDITOR add_gnupg_home () { - local output [ -e "${GNUPGHOME}/gpg.conf" ] && return _gnupg_exit () { gpgconf --kill all 2>/dev/null || true; } at_exit_function _gnupg_exit @@ -345,13 +344,14 @@ trap 'trap_signal' HUP INT TERM # to the message and encrypting/signing. emacs_deliver_message () { - local subject="$1" - local body="$2" + local subject body smtp_dummy_pid smtp_dummy_port + subject="$1" + body="$2" shift 2 # before we can send a message, we have to prepare the FCC maildir mkdir -p "$MAIL_DIR"/sent/{cur,new,tmp} # eval'ing smtp-dummy --background will set smtp_dummy_pid and -_port - local smtp_dummy_pid= smtp_dummy_port= + smtp_dummy_pid= smtp_dummy_port= eval `$TEST_DIRECTORY/smtp-dummy --background sent_message` test -n "$smtp_dummy_pid" || return 1 test -n "$smtp_dummy_port" || return 1 @@ -391,13 +391,14 @@ emacs_deliver_message () # new" after message delivery emacs_fcc_message () { - local nmn_args='' + local nmn_args subject body + nmn_args='' while [[ "$1" =~ ^-- ]]; do nmn_args="$nmn_args $1" shift done - local subject="$1" - local body="$2" + subject="$1" + body="$2" shift 2 # before we can send a message, we have to prepare the FCC maildir mkdir -p "$MAIL_DIR"/sent/{cur,new,tmp} @@ -427,6 +428,7 @@ emacs_fcc_message () # number of messages. add_email_corpus () { + local corpus corpus=${1:-default} rm -rf ${MAIL_DIR} @@ -457,6 +459,7 @@ test_begin_subtest () # name. test_expect_equal () { + local output expected testname exec 1>&6 2>&7 # Restore stdout and stderr if [ -z "$inside_subtest" ]; then error "bug in the test script: test_expect_equal without test_begin_subtest" @@ -483,6 +486,7 @@ test_expect_equal () # Like test_expect_equal, but takes two filenames. test_expect_equal_file () { + local file1 file2 testname basename1 basename2 exec 1>&6 2>&7 # Restore stdout and stderr if [ -z "$inside_subtest" ]; then error "bug in the test script: test_expect_equal_file without test_begin_subtest" @@ -512,10 +516,11 @@ test_expect_equal_file () # canonicalized before diff'ing. If an argument cannot be parsed, it # is used unchanged so that there's something to diff against. test_expect_equal_json () { + local script output expected # The test suite forces LC_ALL=C, but this causes Python 3 to # decode stdin as ASCII. We need to read JSON in UTF-8, so # override Python's stdio encoding defaults. - local script='import json, sys; json.dump(json.load(sys.stdin), sys.stdout, sort_keys=True, indent=4)' + script='import json, sys; json.dump(json.load(sys.stdin), sys.stdout, sort_keys=True, indent=4)' output=$(echo "$1" | PYTHONIOENCODING=utf-8 $NOTMUCH_PYTHON -c "$script" \ || echo "$1") expected=$(echo "$2" | PYTHONIOENCODING=utf-8 $NOTMUCH_PYTHON -c "$script" \ @@ -540,6 +545,7 @@ test_sort_json () { # read the source of test/json_check_nodes.py (or the output when # invoking it without arguments) for an explanation of the syntax. test_json_nodes () { + local output exec 1>&6 2>&7 # Restore stdout and stderr if [ -z "$inside_subtest" ]; then error "bug in the test script: test_json_eval without test_begin_subtest" @@ -561,6 +567,7 @@ test_json_nodes () { } test_emacs_expect_t () { + local result test "$#" = 1 || error "bug in the test script: not 1 parameter to test_emacs_expect_t" if [ -z "$inside_subtest" ]; then @@ -653,7 +660,8 @@ notmuch_json_show_sanitize () notmuch_emacs_error_sanitize () { - local command=$1 + local command + command=$1 shift for file in "$@"; do echo "=== $file ===" @@ -717,6 +725,7 @@ declare -A test_subtest_missing_external_prereq_ # declare prerequisite for the given external binary test_declare_external_prereq () { + local binary binary="$1" test "$#" = 2 && name=$2 || name="$binary(1)" @@ -734,6 +743,7 @@ $binary () { # called indirectly (e.g. from emacs). # Returns success if dependency is available, failure otherwise. test_require_external_prereq () { + local binary binary="$1" if [[ ${test_missing_external_prereq_["${binary}"]} == t ]]; then # dependency is missing, call the replacement function to note it @@ -1075,6 +1085,7 @@ test_ruby() { } test_C () { + local exec_file test_file exec_file="test${test_count}" test_file="${exec_file}.c" cat > ${test_file} @@ -1086,6 +1097,7 @@ test_C () { } make_shim () { + local base_name test_file shim_file base_name="$1" test_file="${base_name}.c" shim_file="${base_name}.so" @@ -1094,6 +1106,7 @@ make_shim () { } notmuch_with_shim () { + local base_name shim_file base_name="$1" shift shim_file="${base_name}.so" From b14d9ae204dedab0b63ab54fc6d59ffe7090ba88 Mon Sep 17 00:00:00 2001 From: Daniel Kahn Gillmor Date: Thu, 21 May 2020 20:42:41 -0400 Subject: [PATCH 150/427] smime: tests of X.509 certificate validity are known-broken on GMime < 3.2.7 When checking cryptographic signatures, Notmuch relies on GMime to tell it whether the certificate that signs a message has a valid User ID or not. If the User ID is not valid, then notmuch does not report the signer's User ID to the user. This means that the consumer of notmuch's cryptographic summary of a message (or of its protected headers) can be confident in relaying the reported identity to the user. However, some versions of GMime before 3.2.7 cannot report Certificate validity for X.509 certificates. This is resolved upstream in GMime at https://github.com/jstedfast/gmime/pull/90. We adapt to this by marking tests of reported User IDs for S/MIME-signed messages as known-broken if GMime is older than 3.2.7 and has not been patched. If GMime >= 3.2.7 and certificate validity still doesn't work for X.509 certs, then there has likely been a regression in GMime and we should fail early, during ./configure. To break out these specific User ID checks from other checks, i had to split some tests into two parts, and reuse $output across the two subtests. Signed-off-by: Daniel Kahn Gillmor --- configure | 83 +++++++++++++++++++++++++++++++++- test/T355-smime.sh | 17 ++++--- test/T356-protected-headers.sh | 13 +++++- 3 files changed, 102 insertions(+), 11 deletions(-) diff --git a/configure b/configure index 0cfdaa6f..37368bda 100755 --- a/configure +++ b/configure @@ -494,7 +494,7 @@ int main () { if (error) return !! fprintf (stderr, "failed to instantiate parser with test/corpora/crypto/basic-encrypted.eml\n"); body = GMIME_MULTIPART_ENCRYPTED(g_mime_message_get_mime_part (g_mime_parser_construct_message (parser, NULL))); - if (body == NULL) return !! fprintf (stderr, "did not find a multipart encrypted message\n"); + if (body == NULL) return !! fprintf (stderr, "did not find a multipart encrypted message\n"); output = g_mime_multipart_encrypted_decrypt (body, GMIME_DECRYPT_EXPORT_SESSION_KEY, NULL, &decrypt_result, &error); if (error || output == NULL) return !! fprintf (stderr, "decryption failed\n"); @@ -536,6 +536,82 @@ EOF if [ -n "$TEMP_GPG" -a -d "$TEMP_GPG" ]; then rm -rf "$TEMP_GPG" fi + + # see https://github.com/jstedfast/gmime/pull/90 + # should be fixed in GMime in 3.2.7, but some distros might patch + printf "Checking for GMime X.509 certificate validity... " + + cat > _check_x509_validity.c < +#include + +int main () { + GError *error = NULL; + GMimeParser *parser = NULL; + GMimeApplicationPkcs7Mime *body = NULL; + GMimeSignatureList *sig_list = NULL; + GMimeSignature *sig = NULL; + GMimeCertificate *cert = NULL; + GMimeObject *output = NULL; + GMimeValidity validity = GMIME_VALIDITY_UNKNOWN; + int len; + + g_mime_init (); + parser = g_mime_parser_new (); + g_mime_parser_init_with_stream (parser, g_mime_stream_file_open("$srcdir/test/corpora/pkcs7/smime-onepart-signed.eml", "r", &error)); + if (error) return !! fprintf (stderr, "failed to instantiate parser with test/corpora/pkcs7/smime-onepart-signed.eml\n"); + + body = GMIME_APPLICATION_PKCS7_MIME(g_mime_message_get_mime_part (g_mime_parser_construct_message (parser, NULL))); + if (body == NULL) return !! fprintf (stderr, "did not find a application/pkcs7 message\n"); + + sig_list = g_mime_application_pkcs7_mime_verify (body, GMIME_VERIFY_NONE, &output, &error); + if (error || output == NULL) return !! fprintf (stderr, "verify failed\n"); + + if (sig_list == NULL) return !! fprintf (stderr, "no GMimeSignatureList found\n"); + len = g_mime_signature_list_length (sig_list); + if (len != 1) return !! fprintf (stderr, "expected 1 signature, got %d\n", len); + sig = g_mime_signature_list_get_signature (sig_list, 0); + if (sig == NULL) return !! fprintf (stderr, "no GMimeSignature found at position 0\n"); + cert = g_mime_signature_get_certificate (sig); + if (cert == NULL) return !! fprintf (stderr, "no GMimeCertificate found\n"); + validity = g_mime_certificate_get_id_validity (cert); + if (validity != GMIME_VALIDITY_FULL) return !! fprintf (stderr, "Got validity %d, expected %d\n", validity, GMIME_VALIDITY_FULL); + + return 0; +} +EOF + if ! TEMP_GPG=$(mktemp -d "${TMPDIR:-/tmp}/notmuch.XXXXXX"); then + printf 'No.\nCould not make tempdir for testing X.509 certificate validity support.\n' + errors=$((errors + 1)) + elif ${CC} ${CFLAGS} ${gmime_cflags} _check_x509_validity.c ${gmime_ldflags} -o _check_x509_validity \ + && echo disable-crl-checks > "$TEMP_GPG/gpgsm.conf" \ + && echo "4D:E0:FF:63:C0:E9:EC:01:29:11:C8:7A:EE:DA:3A:9A:7F:6E:C1:0D S" >> "$TEMP_GPG/trustlist.txt" \ + && GNUPGHOME=${TEMP_GPG} gpgsm --batch --quiet --import < "$srcdir"/test/smime/ca.crt + then + if GNUPGHOME=${TEMP_GPG} ./_check_x509_validity; then + gmime_x509_cert_validity=1 + printf "Yes.\n" + else + gmime_x509_cert_validity=0 + printf "No.\n" + if pkg-config --exists "gmime-3.0 >= 3.2.7"; then + cat < Makefile.config < Date: Tue, 12 May 2020 17:17:09 -0400 Subject: [PATCH 151/427] configure: report GMime minimum version in ./configure output We already report the minimum version for Glib, zlib, and Xapian development libraries. For consistency, report it for GMime as well. Signed-off-by: Daniel Kahn Gillmor --- configure | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure b/configure index 37368bda..9f910b12 100755 --- a/configure +++ b/configure @@ -468,7 +468,7 @@ fi GMIME_MINVER=3.0.3 -printf "Checking for GMime development files... " +printf "Checking for GMime development files (>= $GMIME_MINVER)... " if pkg-config --exists "gmime-3.0 >= $GMIME_MINVER"; then printf "Yes.\n" have_gmime=1 From 38bd0df922aa1c9ac98154f6d3dc3e30255ad47e Mon Sep 17 00:00:00 2001 From: Daniel Kahn Gillmor Date: Tue, 12 May 2020 18:29:31 -0400 Subject: [PATCH 152/427] lib: index PKCS7 SignedData parts When we are indexing, we should treat SignedData parts the same way that we treat a multipart object, indexing the wrapped part as a distinct MIME object. Unfortunately, this means doing some sort of cryptographic verification whose results we throw away, because GMime doesn't offer us any way to unwrap without doing signature verification. I've opened https://github.com/jstedfast/gmime/issues/67 to request the capability from GMime but for now, we'll just accept the additional performance hit. As we do this indexing, we also apply the "signed" tag, by analogy with how we handle multipart/signed messages. These days, that kind of change should probably be done with a property instead, but that's a different set of changes. This one is just for consistency. Note that we are currently *only* handling signedData parts, which are basically clearsigned messages. PKCS#7 parts can also be envelopedData and authEnvelopedData (which are effectively encryption layers), and compressedData (which afaict isn't implemented anywhere, i've never encountered it). We're laying the groundwork for indexing these other S/MIME types here, but we're only dealing with signedData for now. Signed-off-by: Daniel Kahn Gillmor --- lib/index.cc | 57 ++++++++++++++++++++++++++++++++++++++++++++++ test/T355-smime.sh | 2 -- 2 files changed, 57 insertions(+), 2 deletions(-) diff --git a/lib/index.cc b/lib/index.cc index 158ba5cf..bbf13dc5 100644 --- a/lib/index.cc +++ b/lib/index.cc @@ -372,6 +372,12 @@ _index_encrypted_mime_part (notmuch_message_t *message, notmuch_indexopts_t *ind GMimeMultipartEncrypted *part, _notmuch_message_crypto_t *msg_crypto); +static void +_index_pkcs7_part (notmuch_message_t *message, + notmuch_indexopts_t *indexopts, + GMimeObject *part, + _notmuch_message_crypto_t *msg_crypto); + /* Callback to generate terms for each mime part of a message. */ static void _index_mime_part (notmuch_message_t *message, @@ -466,6 +472,11 @@ _index_mime_part (notmuch_message_t *message, goto DONE; } + if (GMIME_IS_APPLICATION_PKCS7_MIME (part)) { + _index_pkcs7_part (message, indexopts, part, msg_crypto); + goto DONE; + } + if (! (GMIME_IS_PART (part))) { _notmuch_database_log (notmuch_message_get_database (message), "Warning: Not indexing unknown mime part: %s.\n", @@ -608,6 +619,52 @@ _index_encrypted_mime_part (notmuch_message_t *message, } +static void +_index_pkcs7_part (notmuch_message_t *message, + notmuch_indexopts_t *indexopts, + GMimeObject *part, + _notmuch_message_crypto_t *msg_crypto) +{ + GMimeApplicationPkcs7Mime *pkcs7; + GMimeSecureMimeType p7type; + GMimeObject *mimeobj = NULL; + GMimeSignatureList *sigs = NULL; + GError *err = NULL; + notmuch_database_t *notmuch = NULL; + + pkcs7 = GMIME_APPLICATION_PKCS7_MIME (part); + p7type = g_mime_application_pkcs7_mime_get_smime_type (pkcs7); + notmuch = notmuch_message_get_database (message); + _index_content_type (message, part); + + if (p7type == GMIME_SECURE_MIME_TYPE_SIGNED_DATA) { + sigs = g_mime_application_pkcs7_mime_verify (pkcs7, GMIME_VERIFY_NONE, &mimeobj, &err); + if (sigs == NULL) { + _notmuch_database_log (notmuch, "Failed to verify PKCS#7 SignedData during indexing. (%d:%d) [%s]\n", + err->domain, err->code, err->message); + g_error_free (err); + goto DONE; + } + _notmuch_message_add_term (message, "tag", "signed"); + GMimeObject *toindex = mimeobj; + if (_notmuch_message_crypto_potential_payload (msg_crypto, mimeobj, part, 0) && + msg_crypto->decryption_status == NOTMUCH_MESSAGE_DECRYPTED_FULL) { + toindex = _notmuch_repair_crypto_payload_skip_legacy_display (mimeobj); + if (toindex != mimeobj) + notmuch_message_add_property (message, "index.repaired", "skip-protected-headers-legacy-display"); + } + _index_mime_part (message, indexopts, toindex, msg_crypto); + } else { + _notmuch_database_log (notmuch, "Cannot currently handle PKCS#7 smime-type '%s'\n", + g_mime_object_get_content_type_parameter (part, "smime-type")); + } + DONE: + if (mimeobj) + g_object_unref (mimeobj); + if (sigs) + g_object_unref (sigs); +} + static notmuch_status_t _notmuch_message_index_user_headers (notmuch_message_t *message, GMimeMessage *mime_message) { diff --git a/test/T355-smime.sh b/test/T355-smime.sh index f8e8e396..a7eecedf 100755 --- a/test/T355-smime.sh +++ b/test/T355-smime.sh @@ -132,13 +132,11 @@ expected='' test_expect_equal "$expected" "$output" test_begin_subtest "know the MIME type of the embedded part in PKCS#7 SignedData" -test_subtest_known_broken output=$(notmuch search --output=messages 'mimetype:text/plain') expected=id:smime-onepart-signed@protected-headers.example test_expect_equal "$expected" "$output" test_begin_subtest "PKCS#7 SignedData message is tagged 'signed'" -test_subtest_known_broken output=$(notmuch dump id:smime-onepart-signed@protected-headers.example) expected='#notmuch-dump batch-tag:3 config,properties,tags +inbox +signed +unread -- id:smime-onepart-signed@protected-headers.example' From ad60e5d4e8a6736af28f326803dbd38620e71ae8 Mon Sep 17 00:00:00 2001 From: Daniel Kahn Gillmor Date: Tue, 12 May 2020 18:29:32 -0400 Subject: [PATCH 153/427] smime: Identify encrypted S/MIME parts during indexing We don't handle them correctly yet, but we can at least mark them as being encrypted. Signed-off-by: Daniel Kahn Gillmor --- lib/index.cc | 4 ++++ test/T355-smime.sh | 1 - 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/index.cc b/lib/index.cc index bbf13dc5..f029b334 100644 --- a/lib/index.cc +++ b/lib/index.cc @@ -654,6 +654,10 @@ _index_pkcs7_part (notmuch_message_t *message, notmuch_message_add_property (message, "index.repaired", "skip-protected-headers-legacy-display"); } _index_mime_part (message, indexopts, toindex, msg_crypto); + } else if (p7type == GMIME_SECURE_MIME_TYPE_ENVELOPED_DATA) { + _notmuch_message_add_term (message, "tag", "encrypted"); + if (notmuch_indexopts_get_decrypt_policy (indexopts) != NOTMUCH_DECRYPT_FALSE) + _notmuch_database_log (notmuch, "Cannot decrypt PKCS#7 envelopedData (S/MIME encrypted messages)\n"); } else { _notmuch_database_log (notmuch, "Cannot currently handle PKCS#7 smime-type '%s'\n", g_mime_object_get_content_type_parameter (part, "smime-type")); diff --git a/test/T355-smime.sh b/test/T355-smime.sh index a7eecedf..7c28282a 100755 --- a/test/T355-smime.sh +++ b/test/T355-smime.sh @@ -98,7 +98,6 @@ test_json_nodes <<<"$output" \ 'crypto_uid:[0][0][0]["crypto"]["signed"]["status"][0]["userid"]="CN=Notmuch Test Suite"' test_begin_subtest "encrypted+signed message is known to be encrypted, but signature is unknown" -test_subtest_known_broken output=$(notmuch search subject:"test encrypted message 001") test_expect_equal "$output" "thread:0000000000000002 2000-01-01 [1/1] Notmuch Test Suite; test encrypted message 001 (encrypted inbox)" From 5f4aceee26ca6db3cdd09ff69220a63e07976a01 Mon Sep 17 00:00:00 2001 From: Daniel Kahn Gillmor Date: Tue, 12 May 2020 18:29:33 -0400 Subject: [PATCH 154/427] cli: include wrapped part of PKCS#7 SignedData in the MIME tree Unwrap a PKCS#7 SignedData part unconditionally when the cli is traversing the MIME tree, and return it as a "child" of what would otherwise be a leaf in the tree. Unfortunately, this also breaks the JSON output. We will fix that next. Signed-off-by: Daniel Kahn Gillmor --- mime-node.c | 23 +++++++++++++++++++++-- test/T355-smime.sh | 2 +- 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/mime-node.c b/mime-node.c index ff6805bf..b6431e3b 100644 --- a/mime-node.c +++ b/mime-node.c @@ -220,8 +220,17 @@ node_verify (mime_node_t *node, GMimeObject *part) notmuch_status_t status; node->verify_attempted = true; - node->sig_list = g_mime_multipart_signed_verify ( - GMIME_MULTIPART_SIGNED (part), GMIME_VERIFY_NONE, &err); + if (GMIME_IS_APPLICATION_PKCS7_MIME (part)) + node->sig_list = g_mime_application_pkcs7_mime_verify ( + GMIME_APPLICATION_PKCS7_MIME (part), GMIME_VERIFY_NONE, &node->unwrapped_child, &err); + else + node->sig_list = g_mime_multipart_signed_verify ( + GMIME_MULTIPART_SIGNED (part), GMIME_VERIFY_NONE, &err); + + if (node->unwrapped_child) { + node->nchildren = 1; + set_unwrapped_child_destructor (node); + } if (node->sig_list) set_signature_list_destructor (node); @@ -376,6 +385,12 @@ _mime_node_set_up_part (mime_node_t *node, GMimeObject *part, int numchild) } else { node_verify (node, part); } + } else if (GMIME_IS_APPLICATION_PKCS7_MIME (part) && + GMIME_SECURE_MIME_TYPE_SIGNED_DATA == g_mime_application_pkcs7_mime_get_smime_type (GMIME_APPLICATION_PKCS7_MIME (part))) { + /* If node->ctx->crypto->verify is false, it would be better + * to just unwrap (instead of verifying), but + * https://github.com/jstedfast/gmime/issues/67 */ + node_verify (node, part); } else { if (_notmuch_message_crypto_potential_payload (node->ctx->msg_crypto, part, node->parent ? node->parent->part : NULL, numchild) && node->ctx->msg_crypto->decryption_status == NOTMUCH_MESSAGE_DECRYPTED_FULL) { @@ -409,6 +424,10 @@ mime_node_child (mime_node_t *parent, int child) GMIME_MULTIPART (parent->part), child); } else if (GMIME_IS_MESSAGE (parent->part)) { sub = g_mime_message_get_mime_part (GMIME_MESSAGE (parent->part)); + } else if (GMIME_IS_APPLICATION_PKCS7_MIME (parent->part) && + parent->unwrapped_child && + child == 0) { + sub = parent->unwrapped_child; } else { /* This should have been caught by _mime_node_set_up_part */ INTERNAL_ERROR ("Unexpected GMimeObject type: %s", diff --git a/test/T355-smime.sh b/test/T355-smime.sh index 7c28282a..4de0fbef 100755 --- a/test/T355-smime.sh +++ b/test/T355-smime.sh @@ -142,7 +142,6 @@ expected='#notmuch-dump batch-tag:3 config,properties,tags test_expect_equal "$expected" "$output" test_begin_subtest "show contents of PKCS#7 SignedData message" -test_subtest_known_broken output=$(notmuch show --format=raw --part=2 id:smime-onepart-signed@protected-headers.example) whitespace=' ' expected="Bob, we need to cancel this contract. @@ -178,6 +177,7 @@ On Tue, 26 Nov 2019 20:11:29 -0400, Alice Lovelace wrote: test_expect_equal "$expected" "$output" test_begin_subtest "show PKCS#7 SignedData outputs valid JSON" +test_subtest_known_broken output=$(notmuch show --format=json id:smime-onepart-signed@protected-headers.example) test_valid_json "$output" From f12fb4d819956cb467b22183f0416fed44703d0f Mon Sep 17 00:00:00 2001 From: Daniel Kahn Gillmor Date: Tue, 12 May 2020 18:29:34 -0400 Subject: [PATCH 155/427] cli/show: If a leaf part has children, show them instead of omitting Until we did PKCS#7 unwrapping, no leaf MIME part could have a child. Now, we treat the unwrapped MIME part as the child of the PKCS#7 SignedData object. So in that case, we want to show it instead of deliberately omitting the content. This fixes the test of the protected subject in id:smime-onepart-signed@protected-headers.example. Signed-off-by: Daniel Kahn Gillmor --- notmuch-show.c | 11 ++++++++++- test/T355-smime.sh | 6 +++--- test/T356-protected-headers.sh | 3 +-- 3 files changed, 14 insertions(+), 6 deletions(-) diff --git a/notmuch-show.c b/notmuch-show.c index ab1cd144..36265043 100644 --- a/notmuch-show.c +++ b/notmuch-show.c @@ -759,7 +759,16 @@ format_part_sprinter (const void *ctx, sprinter_t *sp, mime_node_t *node, sp->string_len (sp, (char *) part_content->data, part_content->len); g_object_unref (stream_memory); } else { - format_omitted_part_meta_sprinter (sp, meta, GMIME_PART (node->part)); + /* if we have a child part despite being a standard + * (non-multipart) MIME part, that means there is + * something to unwrap, which we will present in + * content: */ + if (node->nchildren) { + sp->map_key (sp, "content"); + sp->begin_list (sp); + nclose = 1; + } else + format_omitted_part_meta_sprinter (sp, meta, GMIME_PART (node->part)); } } else if (GMIME_IS_MULTIPART (node->part)) { sp->map_key (sp, "content"); diff --git a/test/T355-smime.sh b/test/T355-smime.sh index 4de0fbef..03aada20 100755 --- a/test/T355-smime.sh +++ b/test/T355-smime.sh @@ -177,12 +177,10 @@ On Tue, 26 Nov 2019 20:11:29 -0400, Alice Lovelace wrote: test_expect_equal "$expected" "$output" test_begin_subtest "show PKCS#7 SignedData outputs valid JSON" -test_subtest_known_broken output=$(notmuch show --format=json id:smime-onepart-signed@protected-headers.example) test_valid_json "$output" test_begin_subtest "Verify signature on PKCS#7 SignedData message" -test_subtest_known_broken output=$(notmuch show --format=json id:smime-onepart-signed@protected-headers.example) test_json_nodes <<<"$output" \ @@ -192,7 +190,9 @@ test_json_nodes <<<"$output" \ 'status:[0][0][0]["crypto"]["signed"]["status"][0]["status"]="good"' test_begin_subtest "Verify signature on PKCS#7 SignedData message signer User ID" -test_subtest_known_broken +if [ $NOTMUCH_GMIME_X509_CERT_VALIDITY -ne 1 ]; then + test_subtest_known_broken +fi test_json_nodes <<<"$output" \ 'userid:[0][0][0]["crypto"]["signed"]["status"][0]["userid"]="CN=Alice Lovelace"' diff --git a/test/T356-protected-headers.sh b/test/T356-protected-headers.sh index 5fd27434..5beffaf0 100755 --- a/test/T356-protected-headers.sh +++ b/test/T356-protected-headers.sh @@ -157,7 +157,6 @@ test_expect_equal "$output" id:protected-with-legacy-display@crypto.notmuchmail. for variant in multipart-signed onepart-signed; do test_begin_subtest "verify signed PKCS#7 subject ($variant)" - [ "$variant" = multipart-signed ] || test_subtest_known_broken output=$(notmuch show --verify --format=json "id:smime-${variant}@protected-headers.example") test_json_nodes <<<"$output" \ 'signed_subject:[0][0][0]["crypto"]["signed"]["headers"]=["Subject"]' \ @@ -165,7 +164,7 @@ for variant in multipart-signed onepart-signed; do 'sig_fpr:[0][0][0]["crypto"]["signed"]["status"][0]["fingerprint"]="702BA4B157F1E2B7D16B0C6A5FFC8A7DE2057DEB"' \ 'not_encrypted:[0][0][0]["crypto"]!"decrypted"' test_begin_subtest "verify signed PKCS#7 subject ($variant) signer User ID" - if [ $NOTMUCH_GMIME_X509_CERT_VALIDITY -ne 1 ] || [ "$variant" != multipart-signed ]; then + if [ $NOTMUCH_GMIME_X509_CERT_VALIDITY -ne 1 ]; then test_subtest_known_broken fi test_json_nodes <<<"$output" \ From 1b9f4a9863003955e6a757a6eeb8b6926d60c896 Mon Sep 17 00:00:00 2001 From: Daniel Kahn Gillmor Date: Tue, 12 May 2020 18:29:35 -0400 Subject: [PATCH 156/427] cli/reply: Ignore PKCS#7 wrapper parts when replying When composing a reply, no one wants to see this line in the proposed message: Non-text part: application/pkcs7-mime So we hide it, the same way we hide PGP/MIME cruft. Signed-off-by: Daniel Kahn Gillmor --- notmuch-reply.c | 5 +++-- test/T355-smime.sh | 1 - 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/notmuch-reply.c b/notmuch-reply.c index 2c30f6f9..ceb4f39b 100644 --- a/notmuch-reply.c +++ b/notmuch-reply.c @@ -65,8 +65,9 @@ format_part_reply (GMimeStream *stream, mime_node_t *node) GMimeContentDisposition *disposition = g_mime_object_get_content_disposition (node->part); if (g_mime_content_type_is_type (content_type, "application", "pgp-encrypted") || - g_mime_content_type_is_type (content_type, "application", "pgp-signature")) { - /* Ignore PGP/MIME cruft parts */ + g_mime_content_type_is_type (content_type, "application", "pgp-signature") || + g_mime_content_type_is_type (content_type, "application", "pkcs7-mime")) { + /* Ignore PGP/MIME and S/MIME cruft parts */ } else if (g_mime_content_type_is_type (content_type, "text", "*") && ! g_mime_content_type_is_type (content_type, "text", "html")) { show_text_part_content (node->part, stream, NOTMUCH_SHOW_TEXT_PART_REPLY); diff --git a/test/T355-smime.sh b/test/T355-smime.sh index 03aada20..8d225bc1 100755 --- a/test/T355-smime.sh +++ b/test/T355-smime.sh @@ -156,7 +156,6 @@ OpenPGP Example Corp" test_expect_equal "$expected" "$output" test_begin_subtest "reply to PKCS#7 SignedData message with proper quoting and attribution" -test_subtest_known_broken output=$(notmuch reply id:smime-onepart-signed@protected-headers.example) expected="From: Notmuch Test Suite Subject: Re: The FooCorp contract From 2b108728c429408c5bf86f1852a205588821286e Mon Sep 17 00:00:00 2001 From: Daniel Kahn Gillmor Date: Tue, 12 May 2020 18:29:36 -0400 Subject: [PATCH 157/427] crypto: Make _notmuch_crypto_decrypt take a GMimeObject As we prepare to handle S/MIME-encrypted PKCS#7 EnvelopedData (which is not multipart), we don't want to be limited to passing only GMimeMultipartEncrypted MIME parts to _notmuch_crypto_decrypt. There is no functional change here, just a matter of adjusting how we pass arguments internally. Signed-off-by: Daniel Kahn Gillmor --- lib/index.cc | 8 ++++---- mime-node.c | 3 +-- util/crypto.c | 6 +++--- util/crypto.h | 2 +- 4 files changed, 9 insertions(+), 10 deletions(-) diff --git a/lib/index.cc b/lib/index.cc index f029b334..da9a3abe 100644 --- a/lib/index.cc +++ b/lib/index.cc @@ -369,7 +369,7 @@ _index_content_type (notmuch_message_t *message, GMimeObject *part) static void _index_encrypted_mime_part (notmuch_message_t *message, notmuch_indexopts_t *indexopts, - GMimeMultipartEncrypted *part, + GMimeObject *part, _notmuch_message_crypto_t *msg_crypto); static void @@ -439,7 +439,7 @@ _index_mime_part (notmuch_message_t *message, g_mime_multipart_get_part (multipart, i)); if (i == GMIME_MULTIPART_ENCRYPTED_CONTENT) { _index_encrypted_mime_part (message, indexopts, - GMIME_MULTIPART_ENCRYPTED (part), + part, msg_crypto); } else { if (i != GMIME_MULTIPART_ENCRYPTED_VERSION) { @@ -551,7 +551,7 @@ _index_mime_part (notmuch_message_t *message, static void _index_encrypted_mime_part (notmuch_message_t *message, notmuch_indexopts_t *indexopts, - GMimeMultipartEncrypted *encrypted_data, + GMimeObject *encrypted_data, _notmuch_message_crypto_t *msg_crypto) { notmuch_status_t status; @@ -603,7 +603,7 @@ _index_encrypted_mime_part (notmuch_message_t *message, g_object_unref (decrypt_result); } GMimeObject *toindex = clear; - if (_notmuch_message_crypto_potential_payload (msg_crypto, clear, GMIME_OBJECT (encrypted_data), GMIME_MULTIPART_ENCRYPTED_CONTENT) && + if (_notmuch_message_crypto_potential_payload (msg_crypto, clear, encrypted_data, GMIME_MULTIPART_ENCRYPTED_CONTENT) && msg_crypto->decryption_status == NOTMUCH_MESSAGE_DECRYPTED_FULL) { toindex = _notmuch_repair_crypto_payload_skip_legacy_display (clear); if (toindex != clear) diff --git a/mime-node.c b/mime-node.c index b6431e3b..c2ee858d 100644 --- a/mime-node.c +++ b/mime-node.c @@ -253,7 +253,6 @@ node_decrypt_and_verify (mime_node_t *node, GMimeObject *part) GError *err = NULL; GMimeDecryptResult *decrypt_result = NULL; notmuch_status_t status; - GMimeMultipartEncrypted *encrypteddata = GMIME_MULTIPART_ENCRYPTED (part); notmuch_message_t *message = NULL; if (! node->unwrapped_child) { @@ -266,7 +265,7 @@ node_decrypt_and_verify (mime_node_t *node, GMimeObject *part) node->unwrapped_child = _notmuch_crypto_decrypt (&node->decrypt_attempted, node->ctx->crypto->decrypt, message, - encrypteddata, &decrypt_result, &err); + part, &decrypt_result, &err); if (node->unwrapped_child) set_unwrapped_child_destructor (node); } diff --git a/util/crypto.c b/util/crypto.c index 0bb6f526..fbd5f011 100644 --- a/util/crypto.c +++ b/util/crypto.c @@ -34,7 +34,7 @@ GMimeObject * _notmuch_crypto_decrypt (bool *attempted, notmuch_decryption_policy_t decrypt, notmuch_message_t *message, - GMimeMultipartEncrypted *part, + GMimeObject *part, GMimeDecryptResult **decrypt_result, GError **err) { @@ -55,7 +55,7 @@ _notmuch_crypto_decrypt (bool *attempted, } if (attempted) *attempted = true; - ret = g_mime_multipart_encrypted_decrypt (part, + ret = g_mime_multipart_encrypted_decrypt (GMIME_MULTIPART_ENCRYPTED (part), GMIME_DECRYPT_NONE, notmuch_message_properties_value (list), decrypt_result, err); @@ -81,7 +81,7 @@ _notmuch_crypto_decrypt (bool *attempted, GMimeDecryptFlags flags = GMIME_DECRYPT_NONE; if (decrypt == NOTMUCH_DECRYPT_TRUE && decrypt_result) flags |= GMIME_DECRYPT_EXPORT_SESSION_KEY; - ret = g_mime_multipart_encrypted_decrypt (part, flags, NULL, + ret = g_mime_multipart_encrypted_decrypt (GMIME_MULTIPART_ENCRYPTED (part), flags, NULL, decrypt_result, err); return ret; } diff --git a/util/crypto.h b/util/crypto.h index f8bda0d1..4fa5599c 100644 --- a/util/crypto.h +++ b/util/crypto.h @@ -18,7 +18,7 @@ GMimeObject * _notmuch_crypto_decrypt (bool *attempted, notmuch_decryption_policy_t decrypt, notmuch_message_t *message, - GMimeMultipartEncrypted *part, + GMimeObject *part, GMimeDecryptResult **decrypt_result, GError **err); From 1a34f68a584c2731d33cd5d2a4ee4e6d7faf6a83 Mon Sep 17 00:00:00 2001 From: Daniel Kahn Gillmor Date: Tue, 12 May 2020 18:29:37 -0400 Subject: [PATCH 158/427] crypto: handle PKCS#7 envelopedData in _notmuch_crypto_decrypt In the two places where _notmuch_crypto_decrypt handles multipart/encrypted messages (PGP/MIME), we should also handle PKCS#7 envelopedData (S/MIME). This is insufficient for fully handling S/MIME encrypted data because _notmuch_crypto_decrypt isn't yet actually invoked for envelopedData parts, but that will happen in the following changes. Signed-off-by: Daniel Kahn Gillmor --- util/crypto.c | 32 ++++++++++++++++++++++++++------ 1 file changed, 26 insertions(+), 6 deletions(-) diff --git a/util/crypto.c b/util/crypto.c index fbd5f011..c09f467b 100644 --- a/util/crypto.c +++ b/util/crypto.c @@ -55,10 +55,21 @@ _notmuch_crypto_decrypt (bool *attempted, } if (attempted) *attempted = true; - ret = g_mime_multipart_encrypted_decrypt (GMIME_MULTIPART_ENCRYPTED (part), - GMIME_DECRYPT_NONE, - notmuch_message_properties_value (list), - decrypt_result, err); + if (GMIME_IS_MULTIPART_ENCRYPTED (part)) { + ret = g_mime_multipart_encrypted_decrypt (GMIME_MULTIPART_ENCRYPTED (part), + GMIME_DECRYPT_NONE, + notmuch_message_properties_value (list), + decrypt_result, err); + } else if (GMIME_IS_APPLICATION_PKCS7_MIME (part)) { + GMimeApplicationPkcs7Mime *pkcs7 = GMIME_APPLICATION_PKCS7_MIME (part); + GMimeSecureMimeType type = g_mime_application_pkcs7_mime_get_smime_type (pkcs7); + if (type == GMIME_SECURE_MIME_TYPE_ENVELOPED_DATA) { + ret = g_mime_application_pkcs7_mime_decrypt (pkcs7, + GMIME_DECRYPT_NONE, + notmuch_message_properties_value (list), + decrypt_result, err); + } + } if (ret) break; } @@ -81,8 +92,17 @@ _notmuch_crypto_decrypt (bool *attempted, GMimeDecryptFlags flags = GMIME_DECRYPT_NONE; if (decrypt == NOTMUCH_DECRYPT_TRUE && decrypt_result) flags |= GMIME_DECRYPT_EXPORT_SESSION_KEY; - ret = g_mime_multipart_encrypted_decrypt (GMIME_MULTIPART_ENCRYPTED (part), flags, NULL, - decrypt_result, err); + if (GMIME_IS_MULTIPART_ENCRYPTED (part)) { + ret = g_mime_multipart_encrypted_decrypt (GMIME_MULTIPART_ENCRYPTED (part), flags, NULL, + decrypt_result, err); + } else if (GMIME_IS_APPLICATION_PKCS7_MIME (part)) { + GMimeApplicationPkcs7Mime *pkcs7 = GMIME_APPLICATION_PKCS7_MIME (part); + GMimeSecureMimeType p7type = g_mime_application_pkcs7_mime_get_smime_type (pkcs7); + if (p7type == GMIME_SECURE_MIME_TYPE_ENVELOPED_DATA) { + ret = g_mime_application_pkcs7_mime_decrypt (pkcs7, flags, NULL, + decrypt_result, err); + } + } return ret; } From cb88b51fe55fcb01235747d4b94072fa6efd501c Mon Sep 17 00:00:00 2001 From: Daniel Kahn Gillmor Date: Tue, 12 May 2020 18:29:38 -0400 Subject: [PATCH 159/427] smime: Pass PKCS#7 envelopedData to node_decrypt_and_verify This change means we can support "notmuch show --decrypt=true" for S/MIME encrypted messages, resolving several outstanding broken tests, including all the remaining S/MIME protected header examples. We do not yet handle indexing the cleartext of S/MIME encrypted messages, though. Signed-off-by: Daniel Kahn Gillmor --- mime-node.c | 6 ++++++ test/T355-smime.sh | 2 -- test/T356-protected-headers.sh | 6 +++--- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/mime-node.c b/mime-node.c index c2ee858d..f552e03a 100644 --- a/mime-node.c +++ b/mime-node.c @@ -390,6 +390,12 @@ _mime_node_set_up_part (mime_node_t *node, GMimeObject *part, int numchild) * to just unwrap (instead of verifying), but * https://github.com/jstedfast/gmime/issues/67 */ node_verify (node, part); + } else if (GMIME_IS_APPLICATION_PKCS7_MIME (part) && + GMIME_SECURE_MIME_TYPE_ENVELOPED_DATA == g_mime_application_pkcs7_mime_get_smime_type (GMIME_APPLICATION_PKCS7_MIME (part)) && + (node->ctx->crypto->decrypt != NOTMUCH_DECRYPT_FALSE)) { + node_decrypt_and_verify (node, part); + if (node->unwrapped_child && node->nchildren == 0) + node->nchildren = 1; } else { if (_notmuch_message_crypto_potential_payload (node->ctx->msg_crypto, part, node->parent ? node->parent->part : NULL, numchild) && node->ctx->msg_crypto->decryption_status == NOTMUCH_MESSAGE_DECRYPTED_FULL) { diff --git a/test/T355-smime.sh b/test/T355-smime.sh index 8d225bc1..1f11725f 100755 --- a/test/T355-smime.sh +++ b/test/T355-smime.sh @@ -80,7 +80,6 @@ EOF test_expect_equal_file EXPECTED OUTPUT test_begin_subtest "Decryption (notmuch CLI)" -test_subtest_known_broken notmuch show --decrypt=true subject:"test encrypted message 001" |\ grep "^This is a" > OUTPUT cat < EXPECTED @@ -89,7 +88,6 @@ EOF test_expect_equal_file EXPECTED OUTPUT test_begin_subtest "Cryptographic message status (encrypted+signed)" -test_subtest_known_broken output=$(notmuch show --format=json --decrypt=true subject:"test encrypted message 001") test_json_nodes <<<"$output" \ 'crypto_encrypted:[0][0][0]["crypto"]["decrypted"]["status"]="full"' \ diff --git a/test/T356-protected-headers.sh b/test/T356-protected-headers.sh index 5beffaf0..074a2345 100755 --- a/test/T356-protected-headers.sh +++ b/test/T356-protected-headers.sh @@ -173,7 +173,6 @@ done for variant in sign+enc sign+enc+legacy-disp; do test_begin_subtest "confirm signed and encrypted PKCS#7 subject ($variant)" - test_subtest_known_broken output=$(notmuch show --decrypt=true --format=json "id:smime-${variant}@protected-headers.example") test_json_nodes <<<"$output" \ 'signed_subject:[0][0][0]["crypto"]["signed"]["headers"]=["Subject"]' \ @@ -181,14 +180,15 @@ for variant in sign+enc sign+enc+legacy-disp; do 'sig_fpr:[0][0][0]["crypto"]["signed"]["status"][0]["fingerprint"]="702BA4B157F1E2B7D16B0C6A5FFC8A7DE2057DEB"' \ 'encrypted:[0][0][0]["crypto"]["decrypted"]={"status":"full","header-mask":{"Subject":"..."}}' test_begin_subtest "confirm signed and encrypted PKCS#7 subject ($variant) signer User ID" - test_subtest_known_broken + if [ $NOTMUCH_GMIME_X509_CERT_VALIDITY -ne 1 ]; then + test_subtest_known_broken + fi test_json_nodes <<<"$output" \ 'sig_uid:[0][0][0]["crypto"]["signed"]["status"][0]["userid"]="CN=Alice Lovelace"' done test_begin_subtest "confirm encryption-protected PKCS#7 subject (enc+legacy-disp)" -test_subtest_known_broken output=$(notmuch show --decrypt=true --format=json "id:smime-enc+legacy-disp@protected-headers.example") test_json_nodes <<<"$output" \ 'encrypted:[0][0][0]["crypto"]["decrypted"]={"status":"full","header-mask":{"Subject":"..."}}' \ From 6cdf4b7e38000c6d0a14ff04ab71f7c7e60835ec Mon Sep 17 00:00:00 2001 From: Daniel Kahn Gillmor Date: Tue, 12 May 2020 18:29:39 -0400 Subject: [PATCH 160/427] smime: Index cleartext of envelopedData when requested Signed-off-by: Daniel Kahn Gillmor --- lib/index.cc | 5 +++-- test/T355-smime.sh | 2 -- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/lib/index.cc b/lib/index.cc index da9a3abe..826aa341 100644 --- a/lib/index.cc +++ b/lib/index.cc @@ -656,8 +656,9 @@ _index_pkcs7_part (notmuch_message_t *message, _index_mime_part (message, indexopts, toindex, msg_crypto); } else if (p7type == GMIME_SECURE_MIME_TYPE_ENVELOPED_DATA) { _notmuch_message_add_term (message, "tag", "encrypted"); - if (notmuch_indexopts_get_decrypt_policy (indexopts) != NOTMUCH_DECRYPT_FALSE) - _notmuch_database_log (notmuch, "Cannot decrypt PKCS#7 envelopedData (S/MIME encrypted messages)\n"); + _index_encrypted_mime_part (message, indexopts, + part, + msg_crypto); } else { _notmuch_database_log (notmuch, "Cannot currently handle PKCS#7 smime-type '%s'\n", g_mime_object_get_content_type_parameter (part, "smime-type")); diff --git a/test/T355-smime.sh b/test/T355-smime.sh index 1f11725f..170f8649 100755 --- a/test/T355-smime.sh +++ b/test/T355-smime.sh @@ -107,12 +107,10 @@ test_begin_subtest "Reindex cleartext" test_expect_success "notmuch reindex --decrypt=true subject:'test encrypted message 001'" test_begin_subtest "signature is now known" -test_subtest_known_broken output=$(notmuch search subject:"test encrypted message 001") test_expect_equal "$output" "thread:0000000000000002 2000-01-01 [1/1] Notmuch Test Suite; test encrypted message 001 (encrypted inbox signed)" test_begin_subtest "Encrypted body is indexed" -test_subtest_known_broken output=$(notmuch search 'this is a test encrypted message') test_expect_equal "$output" "thread:0000000000000002 2000-01-01 [1/1] Notmuch Test Suite; test encrypted message 001 (encrypted inbox signed)" From 291ef68eded5ff8f5f39b1aa17a83dd8e6abb342 Mon Sep 17 00:00:00 2001 From: Jonas Bernoulli Date: Sat, 25 Apr 2020 22:18:09 +0200 Subject: [PATCH 161/427] emacs: Use `dolist' instead of `mapcar' for side-effects As recommended by the byte-compiler. --- test/emacs-attachment-warnings.el | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/test/emacs-attachment-warnings.el b/test/emacs-attachment-warnings.el index a23692d7..8f4918ef 100644 --- a/test/emacs-attachment-warnings.el +++ b/test/emacs-attachment-warnings.el @@ -67,16 +67,15 @@ Return `t' if the message would be sent, otherwise `nil'" (defun notmuch-test-attachment-warning-1 () (let (output expected) - (mapcar (lambda (test) - (let* ((expect (car test)) - (body (cdr test)) - (result (attachment-check-test body))) - (push expect expected) - (push (if (eq result expect) - result - ;; In the case of a failure, include the test - ;; details to make it simpler to debug. - (format "%S <-- %S" result body)) - output))) - attachment-check-tests) + (dolist (test attachment-check-tests) + (let* ((expect (car test)) + (body (cdr test)) + (result (attachment-check-test body))) + (push expect expected) + (push (if (eq result expect) + result + ;; In the case of a failure, include the test + ;; details to make it simpler to debug. + (format "%S <-- %S" result body)) + output))) (notmuch-test-expect-equal output expected))) From c9e55a712e1ab4d5e84ba15d07d094865e72ffa1 Mon Sep 17 00:00:00 2001 From: Tomi Ollila Date: Fri, 24 Apr 2020 00:26:43 +0300 Subject: [PATCH 162/427] test/test-lib.sh: fix two out of tree test issues json_check_nodes.py exists in source tree, not in out of tree build tree. Added -B to the execution so source tree is not "polluted" by a .pyc file when json_check_nodes.py is executed. When creating run_emacs.sh make it load .elc files from out of tree build tree, not from source tree if such files existed. If existed, those may be outdated, or even created by some other emacs than the one that was used to build .elc files in out of tree build dir. --- test/test-lib.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/test-lib.sh b/test/test-lib.sh index 58972339..87fa6fe4 100644 --- a/test/test-lib.sh +++ b/test/test-lib.sh @@ -556,7 +556,7 @@ test_json_nodes () { if ! test_skip "$test_subtest_name" then - output=$(PYTHONIOENCODING=utf-8 $NOTMUCH_PYTHON "$TEST_DIRECTORY"/json_check_nodes.py "$@") + output=$(PYTHONIOENCODING=utf-8 $NOTMUCH_PYTHON -B "$NOTMUCH_SRCDIR"/test/json_check_nodes.py "$@") if [ "$?" = 0 ] then test_ok_ @@ -1021,7 +1021,7 @@ export NOTMUCH_CONFIG=$NOTMUCH_CONFIG # --load Force loading of notmuch.el and test-lib.el exec ${TEST_EMACS} --quick \ - --directory "$NOTMUCH_SRCDIR/emacs" --load notmuch.el \ + --directory "$NOTMUCH_BUILDDIR/emacs" --load notmuch.el \ --directory "$NOTMUCH_SRCDIR/test" --load test-lib.el \ "\$@" EOF From ef0ab496b3f1e3a85f08102e3aaf251f4af69fd3 Mon Sep 17 00:00:00 2001 From: Daniel Kahn Gillmor Date: Thu, 21 May 2020 21:03:59 -0400 Subject: [PATCH 163/427] python-cffi: enable out-of-tree builds This is a simple hack to enable out-of-tree builds, a concern raised by Tomi in id:m24kzjib9a.fsf@guru.guru-group.fi This change at least enables "make check" to complete without error, but I'm sure it could be improved. I am not expert enough in setuptools to know how. Signed-off-by: Daniel Kahn Gillmor Amended by db per id:87d06usa31.fsf@powell.devork.be --- configure | 8 ++++++++ test/T391-python-cffi.sh | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/configure b/configure index 9f910b12..32a836bb 100755 --- a/configure +++ b/configure @@ -70,6 +70,14 @@ if [ "$srcdir" != "." ]; then mkdir bindings/ruby cp -a "$srcdir"/bindings/ruby/*.[ch] bindings/ruby cp -a "$srcdir"/bindings/ruby/extconf.rb bindings/ruby + + # Use the same hack to replicate python-cffi source for + # out-of-tree builds (again, not ideal). + mkdir bindings/python-cffi + cp -a "$srcdir"/bindings/python-cffi/tests \ + "$srcdir"/bindings/python-cffi/notmuch2 \ + "$srcdir"/bindings/python-cffi/setup.py \ + bindings/python-cffi/ fi # Set several defaults (optionally specified by the user in diff --git a/test/T391-python-cffi.sh b/test/T391-python-cffi.sh index e4f80dc6..f961069b 100755 --- a/test/T391-python-cffi.sh +++ b/test/T391-python-cffi.sh @@ -8,7 +8,7 @@ fi test_begin_subtest "python cffi tests" -pytest_dir=$NOTMUCH_SRCDIR/bindings/python-cffi/build/stage +pytest_dir=$NOTMUCH_BUILDDIR/bindings/python-cffi/build/stage printf "[pytest]\nminversion = 3.0\naddopts = -ra\n" > $pytest_dir/pytest.ini test_expect_success "(cd $pytest_dir && ${NOTMUCH_PYTHON} -m pytest --log-file=$TMP_DIRECTORY/test.output)" test_done From b624b406ff004909e30903593e6b4f7dd889c9dc Mon Sep 17 00:00:00 2001 From: Daniel Kahn Gillmor Date: Tue, 26 May 2020 13:06:02 -0400 Subject: [PATCH 164/427] tests/ruby: Ensure that test works for out-of-tree builds --- test/test-lib.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test-lib.sh b/test/test-lib.sh index 87fa6fe4..7424881e 100644 --- a/test/test-lib.sh +++ b/test/test-lib.sh @@ -1081,7 +1081,7 @@ test_python() { } test_ruby() { - MAIL_DIR=$MAIL_DIR $NOTMUCH_RUBY -I $NOTMUCH_SRCDIR/bindings/ruby> OUTPUT + MAIL_DIR=$MAIL_DIR $NOTMUCH_RUBY -I "$NOTMUCH_BUILDDIR/bindings/ruby"> OUTPUT } test_C () { From 3e6e219384b75fbb0f067c59a0ae279bab5be7ff Mon Sep 17 00:00:00 2001 From: Sean Whitton Date: Sun, 31 May 2020 23:17:04 -0700 Subject: [PATCH 165/427] emacs: Respect `load-prefer-newer` when loading `notmuch-init-file' Before this change, `load-prefer-newer' was ignored. Set NOERROR and MUST-SUFFIX arguments of `load' to t, and NOSUFFIX argument to nil, to preserve the behaviour of the deleted `let' form. --- emacs/notmuch.el | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/emacs/notmuch.el b/emacs/notmuch.el index a980c7a2..5a24d8a7 100644 --- a/emacs/notmuch.el +++ b/emacs/notmuch.el @@ -1148,8 +1148,6 @@ beginning of the line." ;; After provide to avoid loops if notmuch was require'd via notmuch-init-file. (if init-file-user ; don't load init file if the -q option was used. - (let ((init-file (locate-file notmuch-init-file '("/") - (get-load-suffixes)))) - (if init-file (load init-file nil t t)))) + (load notmuch-init-file t t nil t)) ;;; notmuch.el ends here From 16d073ebe8fe062bb733165bdb10d7d2937616b5 Mon Sep 17 00:00:00 2001 From: Tomi Ollila Date: Mon, 1 Jun 2020 08:31:42 +0300 Subject: [PATCH 166/427] configure: check existence of python3 setuptools and dev package The notmuch2 CFFI-based Python interface is not buildable unless python3 dev package and python3 setuptools are installed. Check that these exist in configure (and disable notmuch2 bindings build if not) so that build of these bindings don't fail when make(1) is executed. --- configure | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/configure b/configure index 32a836bb..e4c738e5 100755 --- a/configure +++ b/configure @@ -711,12 +711,14 @@ if [ $have_python -eq 0 ]; then errors=$((errors + 1)) fi -have_python3=0 +have_python3_dev=0 if [ $have_python -eq 1 ]; then - printf "Checking for python3..." - if "$python" -c 'import sys; assert sys.version_info >= (3,0)' > /dev/null 2>&1; then + printf "Checking for python3 dev..." + if "$python" -c 'import os, sys, sysconfig; +assert sys.version_info >= (3,0) +assert os.path.isfile(sysconfig.get_config_h_filename())' >/dev/null 2>&1; then printf "Yes.\n" - have_python3=1 + have_python3_dev=1 else printf "No.\n" fi @@ -724,9 +726,9 @@ fi have_python3_cffi=0 have_python3_pytest=0 -if [ $have_python3 -eq 1 ]; then - printf "Checking for python3 cffi... " - if "$python" -c 'import cffi' >/dev/null 2>&1; then +if [ $have_python3_dev -eq 1 ]; then + printf "Checking for python3 cffi and setuptools... " + if "$python" -c 'import cffi; import setuptools' >/dev/null 2>&1; then printf "Yes.\n" have_python3_cffi=1 else From ee8dba1c3013a00c0d1185583ea084f8ce3699b7 Mon Sep 17 00:00:00 2001 From: David Bremner Date: Sun, 31 May 2020 13:15:03 -0300 Subject: [PATCH 167/427] doc: fix for out-of-tree builds of notmuch-emacs docs The sphinx-doc include directive does not have the ability to include files from the build tree, so we replace the include with reading the files in conf.py. The non-trivial downside of this is that the emacs docstrings are now defined for every rst source file. They are namespaced with docstring::, so hopefully there will not be any surprises. One thing that is noticable is a small (absolute) time penalty in running sphinx-doc. --- doc/Makefile.local | 2 +- doc/conf.py | 18 ++++++++++++++---- doc/notmuch-emacs.rst | 10 ---------- 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/doc/Makefile.local b/doc/Makefile.local index b4e0c955..769438ed 100644 --- a/doc/Makefile.local +++ b/doc/Makefile.local @@ -4,7 +4,7 @@ dir := doc # You can set these variables from the command line. SPHINXOPTS := -q -SPHINXBUILD = WITH_EMACS=${WITH_EMACS} sphinx-build +SPHINXBUILD = WITH_EMACS=${WITH_EMACS} RSTI_DIR=$(realpath emacs) sphinx-build DOCBUILDDIR := $(dir)/_build # Internal variables. diff --git a/doc/conf.py b/doc/conf.py index fc9738ff..ffb8ca1f 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -29,10 +29,20 @@ release = version # directories to ignore when looking for source files. exclude_patterns = ['_build'] -# If we don't have emacs (or the user configured --without-emacs), -# don't build the notmuch-emacs docs, as they need emacs to generate -# the docstring include files -if os.environ.get('WITH_EMACS') != '1': +if os.environ.get('WITH_EMACS') == '1': + # Hacky reimplementation of include to workaround limitations of + # sphinx-doc + lines = ['.. include:: /../emacs/rstdoc.rsti\n\n'] # in the source tree + rsti_dir = os.environ.get('RSTI_DIR') + # the other files are from the build tree + for file in ('notmuch.rsti', 'notmuch-lib.rsti', 'notmuch-show.rsti', 'notmuch-tag.rsti'): + lines.extend(open(rsti_dir+'/'+file)) + rst_epilog = ''.join(lines) + del lines +else: + # If we don't have emacs (or the user configured --without-emacs), + # don't build the notmuch-emacs docs, as they need emacs to generate + # the docstring include files exclude_patterns.append('notmuch-emacs.rst') # The name of the Pygments (syntax highlighting) style to use. diff --git a/doc/notmuch-emacs.rst b/doc/notmuch-emacs.rst index 1655e2f0..de47b726 100644 --- a/doc/notmuch-emacs.rst +++ b/doc/notmuch-emacs.rst @@ -377,13 +377,3 @@ suffix exist it will be read instead (just one of these, chosen in this order). Most often users create ``~/.emacs.d/notmuch-config.el`` and just work with it. If Emacs was invoked with the ``-q`` or ``--no-init-file`` options, ``notmuch-init-file`` is not read. - -.. include:: ../emacs/rstdoc.rsti - -.. include:: ../emacs/notmuch.rsti - -.. include:: ../emacs/notmuch-lib.rsti - -.. include:: ../emacs/notmuch-show.rsti - -.. include:: ../emacs/notmuch-tag.rsti From 01fe987eec29b13f69309fa09730b6f8fc796c4a Mon Sep 17 00:00:00 2001 From: David Bremner Date: Mon, 1 Jun 2020 20:58:52 -0300 Subject: [PATCH 168/427] bump date in documentation --- doc/conf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/conf.py b/doc/conf.py index ffb8ca1f..70987ac5 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -12,7 +12,7 @@ master_doc = 'index' # General information about the project. project = u'notmuch' -copyright = u'2009-2019, Carl Worth and many others' +copyright = u'2009-2020, Carl Worth and many others' location = os.path.dirname(__file__) From dfd092e95d2d2fce9fcf74e03f11eb01254f8b4f Mon Sep 17 00:00:00 2001 From: David Bremner Date: Mon, 1 Jun 2020 21:00:04 -0300 Subject: [PATCH 169/427] version: bump to 0.30~rc0 --- bindings/python/notmuch/version.py | 2 +- version | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/bindings/python/notmuch/version.py b/bindings/python/notmuch/version.py index e688b565..d0c9b219 100644 --- a/bindings/python/notmuch/version.py +++ b/bindings/python/notmuch/version.py @@ -1,3 +1,3 @@ # this file should be kept in sync with ../../../version -__VERSION__ = '0.29.3' +__VERSION__ = '0.30~rc0' SOVERSION = '5' diff --git a/version b/version index 5540b6e0..f792fd29 100644 --- a/version +++ b/version @@ -1 +1 @@ -0.29.3 +0.30~rc0 From e66e5db7686fa1d69024c7142c92e022acaa175e Mon Sep 17 00:00:00 2001 From: David Bremner Date: Mon, 1 Jun 2020 21:03:06 -0300 Subject: [PATCH 170/427] debian: add changelog for release candidate --- debian/changelog | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/debian/changelog b/debian/changelog index 4f7457cd..5a0c0210 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,9 @@ +notmuch (0.30~rc0-1) experimental; urgency=medium + + * New upstream release candidate + + -- David Bremner Mon, 01 Jun 2020 21:01:27 -0300 + notmuch (0.29.3-1) unstable; urgency=medium * New upstream bugfix release. From 5b85699d6cbdeae1b9f5ed8a08262dcf64ab032e Mon Sep 17 00:00:00 2001 From: David Bremner Date: Mon, 1 Jun 2020 21:09:28 -0300 Subject: [PATCH 171/427] NEWS: stub for 0.30 --- NEWS | 3 +++ 1 file changed, 3 insertions(+) diff --git a/NEWS b/NEWS index 66bb69f1..14db7f6f 100644 --- a/NEWS +++ b/NEWS @@ -1,3 +1,6 @@ +Notmuch 0.30 (2020-06-01) +========================= + Notmuch 0.29.3 (2019-11-27) =========================== From 617377755a26a4c5be01f65bb74cce080066c366 Mon Sep 17 00:00:00 2001 From: David Bremner Date: Mon, 1 Jun 2020 21:32:23 -0300 Subject: [PATCH 172/427] debian: add desktop-file-utils dependency This enables installation of notmuch-emacs-mua.desktop --- debian/control | 1 + 1 file changed, 1 insertion(+) diff --git a/debian/control b/debian/control index a1371fc8..8280be4f 100644 --- a/debian/control +++ b/debian/control @@ -14,6 +14,7 @@ Build-Depends: debhelper-compat (= 12), dh-elpa (>= 1.3), dh-python, + desktop-file-utils, dpkg-dev (>= 1.17.14), dtach (>= 0.8) , emacs-nox | emacs-gtk | emacs-lucid | emacs25-nox | emacs25 (>=25~) | emacs25-lucid (>=25~) | emacs24-nox | emacs24 (>=24~) | emacs24-lucid (>=24~), From 3ee29bde69bc42a41317ef3a1f496014658e002a Mon Sep 17 00:00:00 2001 From: David Bremner Date: Mon, 1 Jun 2020 21:38:02 -0300 Subject: [PATCH 173/427] debian: Add build dependency on doxygen --- debian/control | 1 + 1 file changed, 1 insertion(+) diff --git a/debian/control b/debian/control index 8280be4f..e2bb166f 100644 --- a/debian/control +++ b/debian/control @@ -15,6 +15,7 @@ Build-Depends: dh-elpa (>= 1.3), dh-python, desktop-file-utils, + doxygen, dpkg-dev (>= 1.17.14), dtach (>= 0.8) , emacs-nox | emacs-gtk | emacs-lucid | emacs25-nox | emacs25 (>=25~) | emacs25-lucid (>=25~) | emacs24-nox | emacs24 (>=24~) | emacs24-lucid (>=24~), From 19a83d898d45d97ba11b16222efde7d634b5045f Mon Sep 17 00:00:00 2001 From: David Bremner Date: Mon, 1 Jun 2020 23:16:09 -0300 Subject: [PATCH 174/427] debian: bump debian revision second, with binaries upload --- debian/changelog | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/debian/changelog b/debian/changelog index 5a0c0210..8fa2dcbf 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,4 +1,4 @@ -notmuch (0.30~rc0-1) experimental; urgency=medium +notmuch (0.30~rc0-2) experimental; urgency=medium * New upstream release candidate From 1277f3c1296dba189ab1c391e96a415528b77cc2 Mon Sep 17 00:00:00 2001 From: Daniel Kahn Gillmor Date: Mon, 1 Jun 2020 21:54:56 -0400 Subject: [PATCH 175/427] add NEWS for 0.30 Signed-off-by: Daniel Kahn Gillmor --- NEWS | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/NEWS b/NEWS index 14db7f6f..1921127f 100644 --- a/NEWS +++ b/NEWS @@ -1,6 +1,41 @@ Notmuch 0.30 (2020-06-01) ========================= +S/MIME +------ + +Handle S/MIME (PKCS#7) messages -- one-part signed messages, encrypted +messages, and multilayer messages. Treat them symmetrically to +OpenPGP messages. This includes handling protected headers +gracefully. + +If you're using Notmuch with S/MIME, you currently need to configure +gpgsm appropriately. + +Mixed-up MIME Repair +-------------------- + +Detect and automatically repair a common form of message mangling +created by Microsoft Exchange. (see index.repaired=mixedup in +notmuch-properties(7). + +Protected Headers +----------------- + +Avoid indexing the legacy-display part of an encrypted message that +has protected headers. (see +index.repaired=skip-protected-headers-legacy-display in +notmuch-properties(7). + +Python +------ + +Drop compatibility with python2, focus on python3. + +Introduce new CFFI-based python bindings in the python module named +"notmuch2". Officially deprecate (but still support) the older +"notmuch" module. + Notmuch 0.29.3 (2019-11-27) =========================== From 2c7b3d3a80d40c25e2991e2659c6a6006b50fb08 Mon Sep 17 00:00:00 2001 From: Tomi Ollila Date: Thu, 4 Jun 2020 22:38:35 +0300 Subject: [PATCH 176/427] NEWS: The minimum supported version of Xapian is now 1.4.0 --- NEWS | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/NEWS b/NEWS index 1921127f..a16689ca 100644 --- a/NEWS +++ b/NEWS @@ -36,6 +36,12 @@ Introduce new CFFI-based python bindings in the python module named "notmuch2". Officially deprecate (but still support) the older "notmuch" module. +Dependencies +------------ + +Support for Xapian 1.2 is removed. The minimum supported version of +Xapian is now 1.4.0. + Notmuch 0.29.3 (2019-11-27) =========================== From ba35784114dfcd72978fae195063a49120ffef38 Mon Sep 17 00:00:00 2001 From: David Bremner Date: Fri, 5 Jun 2020 06:58:31 -0300 Subject: [PATCH 177/427] tweaks to NEWS suggest by Tomi See id:m27dwp40sx.fsf@guru.guru-group.fi --- NEWS | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/NEWS b/NEWS index a16689ca..556172bb 100644 --- a/NEWS +++ b/NEWS @@ -5,8 +5,8 @@ S/MIME ------ Handle S/MIME (PKCS#7) messages -- one-part signed messages, encrypted -messages, and multilayer messages. Treat them symmetrically to -OpenPGP messages. This includes handling protected headers +messages, and multilayer messages. Treat them symmetrically to +OpenPGP messages. This includes handling protected headers gracefully. If you're using Notmuch with S/MIME, you currently need to configure @@ -16,21 +16,21 @@ Mixed-up MIME Repair -------------------- Detect and automatically repair a common form of message mangling -created by Microsoft Exchange. (see index.repaired=mixedup in -notmuch-properties(7). +created by Microsoft Exchange (see index.repaired=mixedup in +notmuch-properties(7)). Protected Headers ----------------- Avoid indexing the legacy-display part of an encrypted message that -has protected headers. (see +has protected headers (see index.repaired=skip-protected-headers-legacy-display in -notmuch-properties(7). +notmuch-properties(7)). Python ------ -Drop compatibility with python2, focus on python3. +Drop support for python2, focus on python3. Introduce new CFFI-based python bindings in the python module named "notmuch2". Officially deprecate (but still support) the older From 55619625eb08f046e4e02ebc43f4aefa8903c638 Mon Sep 17 00:00:00 2001 From: David Bremner Date: Wed, 3 Jun 2020 13:05:12 -0300 Subject: [PATCH 178/427] devel: script to calculate a list of authors. As an initial heuristic, report anyone with at least 15 lines of code in the current source tree. Test corpora are excluded, although probabably this doesn't change much about the list of authors produced. --- devel/author-scan.sh | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 devel/author-scan.sh diff --git a/devel/author-scan.sh b/devel/author-scan.sh new file mode 100644 index 00000000..2d9c4af8 --- /dev/null +++ b/devel/author-scan.sh @@ -0,0 +1,11 @@ +#!/bin/sh + +FILE_EXCLUDE='corpora' +AUTHOR_EXCLUDE='uncrustify' +# based on the FSF guideline, for want of a better idea. +THRESHOLD=15 + +git ls-files | grep -v -e "$FILE_EXCLUDE" | xargs -n 1 -d \\n \ + git blame -w --line-porcelain -- | \ + sed -n "/$AUTHOR_EXCLUDE/d; s/^[aA][uU][tT][hH][Oo][rR] //p" | \ + sort -fd | uniq -ic | awk "\$1 >= $THRESHOLD" | sort -nr From 5d92582b13f6a7eafe8f2f31c19c9b77b3d6c47b Mon Sep 17 00:00:00 2001 From: David Bremner Date: Thu, 4 Jun 2020 08:36:39 -0300 Subject: [PATCH 179/427] doc: update AUTHORS file I tried to be inclusive, and did not delete anyone from Carl's original file. I also reworded the acknowledgement of Google LLC. --- AUTHORS | 109 ++++++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 103 insertions(+), 6 deletions(-) diff --git a/AUTHORS b/AUTHORS index 6a05b441..f8989f6e 100644 --- a/AUTHORS +++ b/AUTHORS @@ -1,5 +1,6 @@ -Carl Worth is the primary author of Notmuch. -But there's really not much that he's done. There's been a lot of +Carl Worth was the original author of Notmuch. +David Bremner has maintained Notmuch since release 0.6 (2011). But +there's really not much that they've done. There's been a lot of standing on shoulders here: William Morgan deserves credit for providing the primary inspiration @@ -21,11 +22,107 @@ engine that does the really heavy lifting, as well as the various system libraries, compilers, and the kernel that make it all work (thanks GNU, thanks Linux). Thanks to everyone who has played a part! +The following list of people have at least 15 lines of code in the +Notmuch 0.30 release (calculated by devel/author-scan.sh). + + David Bremner + Carl Worth + Jani Nikula + Austin Clements + Daniel Kahn Gillmor + Mark Walters + Floris Bruynooghe + David Edmondson + Sebastian Spaeth + Ali Polatel + Tomi Ollila + Michal Sojka + Justus Winter + Sebastien Binet + W. Trevor King + Jameson Graef Rollins + Felipe Contreras + Pieter Praet + Peter Feigl + Dmitry Kurochkin + Peter Wang + Daniel Schoepe + Gregor Zattler + Keith Packard + Adam Wolfe Gordon + Stefano Zacchiroli + Vincent Breitmoser + laochailan + Ben Gamari + Aaron Ecay + Jesse Rosenthal + l-m-h@web.de + Thomas Jost + Dirk Hohndel + Blake Jones + Jonas Bernoulli + Damien Cassou + Vladimir Panteleev + Matt Armstrong + Örjan Ekeberg + Jan Janak + Patrick Totzke + Chunyang Xu + rhn + Ruben Pollan + Ioan-Adrian Ratiu + Ethan Glasser-Camp + Todd + Chris Wilson + William Casarin + Yuri Volchkov + Cédric Cabessa + Mark Anderson + Jed Brown + Maxime Coste + Ludovic LANGE + Sebastian Poeplau + Mikhail + Gaute Hope + Keith Amidon + martin f. krafft + Jeffrey C. Ollie + Bart Trojanowski + Jameson Rollins + Scott Henson + Vladimir Marek + Servilio Afre Puentes + Kevin McCarthy + Tomas Carnecky + Kevin J. McCarthy + Scott Robinson + Wael M. Nasreddine + Charles Celerier + Olly Betts + Istvan Marko + Florian Klink + Thibaut Horel + Joel Borggrén-Franck + Ingmar Vanhassel + Olivier Taïbi + Ian Main + Alexander Botero-Lowry + Luis Ressel + Sergei Shilovsky + Trevor Jim + Jinwoo Lee + Uli Scholler + Matthew Lear + Amadeusz Żołnowski + Here is an incomplete list of other people that have made contributions to Notmuch (whether by code, bug reporting/fixes, ideas, inspiration, testing or feedback): -Martin Krafft -Keith Packard -Jamey Sharp -Google LLC + Martin Krafft + Jamey Sharp + +The Notmuch project acknowledges the contributions of the following +organizations via their employees + + Google LLC From 30f8bc8cee1968c586652949fb1095a4f2ca6ca2 Mon Sep 17 00:00:00 2001 From: David Bremner Date: Thu, 4 Jun 2020 08:53:27 -0300 Subject: [PATCH 180/427] debian: update debian/copyright This is based on the updated AUTHORS file. --- debian/copyright | 117 ++++++++++++++++++++++++++++++++++++----------- 1 file changed, 90 insertions(+), 27 deletions(-) diff --git a/debian/copyright b/debian/copyright index a88ce1dc..3c8eeea1 100644 --- a/debian/copyright +++ b/debian/copyright @@ -4,33 +4,96 @@ Source: https://git.notmuchmail.org/git/notmuch Upstream-Contact: Notmuch Mailing List Files: * -Copyright: Copyright 2009 Carl Worth - Bart Trojanowski - Keith Packard - Alexander Botero-Lowry - Ingmar Vanhassel - Jed Brown - Jan Janak - Chris Wilson - Keith Amidon - Aneesh Kumar K.V - Mikhail Gusarov - Jeffrey C. Ollie - Jameson Graef Rollins - Stewart Smith - Adrian Perez - Kan-Ru Chen - James Rowe - Eric Anholt - Alec Berryman - Tassilo Horn - Stefan Schmidt - Rolland Santimano - Peter Wang - Lars Kellogg-Stedman - Holger Freyther - David Bremner - Alexander Botero-Lowry +Copyright: Copyright 2009-2020 + David Bremner + Carl Worth + Jani Nikula + Austin Clements + Daniel Kahn Gillmor + Mark Walters + Floris Bruynooghe + David Edmondson + Sebastian Spaeth + Ali Polatel + Tomi Ollila + Michal Sojka + Justus Winter + Sebastien Binet + W. Trevor King + Jameson Graef Rollins + Felipe Contreras + Pieter Praet + Peter Feigl + Dmitry Kurochkin + Peter Wang + Daniel Schoepe + Gregor Zattler + Keith Packard + Adam Wolfe Gordon + Stefano Zacchiroli + Vincent Breitmoser + laochailan + Ben Gamari + Aaron Ecay + Jesse Rosenthal + l-m-h@web.de + Thomas Jost + Dirk Hohndel + Blake Jones + Jonas Bernoulli + Damien Cassou + Vladimir Panteleev + Matt Armstrong + Örjan Ekeberg + Jan Janak + Patrick Totzke + Chunyang Xu + rhn + Ruben Pollan + Ioan-Adrian Ratiu + Ethan Glasser-Camp + Todd + Chris Wilson + William Casarin + Yuri Volchkov + Cédric Cabessa + Mark Anderson + Jed Brown + Maxime Coste + Ludovic LANGE + Sebastian Poeplau + Mikhail + Gaute Hope + Keith Amidon + martin f. krafft + Jeffrey C. Ollie + Bart Trojanowski + Jameson Rollins + Scott Henson + Vladimir Marek + Servilio Afre Puentes + Kevin McCarthy + Tomas Carnecky + Kevin J. McCarthy + Scott Robinson + Wael M. Nasreddine + Charles Celerier + Olly Betts + Istvan Marko + Florian Klink + Thibaut Horel + Joel Borggrén-Franck + Ingmar Vanhassel + Olivier Taïbi + Ian Main + Alexander Botero-Lowry + Luis Ressel + Sergei Shilovsky + Trevor Jim + Jinwoo Lee + Uli Scholler + Matthew Lear + Amadeusz Żołnowski License: GPL-3+ Files: debian/* From ca37d8950b8f7f0d2952af25482db9aa36ae9d9c Mon Sep 17 00:00:00 2001 From: Tomi Ollila Date: Sat, 6 Jun 2020 00:57:55 +0300 Subject: [PATCH 181/427] configure: require python 3.5 for CFFI-based notmuch bindings Also tell users what the consequences of a "No" answer is when python version is less than 3.5, cffi or setuptools is missing, or no pytest >= 3.0 is available. --- configure | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/configure b/configure index e4c738e5..f4b3c61a 100755 --- a/configure +++ b/configure @@ -713,14 +713,14 @@ fi have_python3_dev=0 if [ $have_python -eq 1 ]; then - printf "Checking for python3 dev..." + printf "Checking for python3 dev (>= 3.5)..." if "$python" -c 'import os, sys, sysconfig; -assert sys.version_info >= (3,0) +assert sys.version_info >= (3,5) assert os.path.isfile(sysconfig.get_config_h_filename())' >/dev/null 2>&1; then printf "Yes.\n" have_python3_dev=1 else - printf "No.\n" + printf "No (will not install CFFI-based python bindings).\n" fi fi @@ -732,7 +732,7 @@ if [ $have_python3_dev -eq 1 ]; then printf "Yes.\n" have_python3_cffi=1 else - printf "No.\n" + printf "No (will not install CFFI-based python bindings).\n" fi printf "Checking for python3 pytest (>= 3.0)... " @@ -742,7 +742,7 @@ if [ $have_python3_dev -eq 1 ]; then printf "Yes.\n" have_python3_pytest=1 else - printf "No.\n" + printf "No (will not test CFFI-based python bindings).\n" fi rm -f $conf fi From 507d2f07a6c74eea86f87b637d0b67a0ddc1fc81 Mon Sep 17 00:00:00 2001 From: Tomi Ollila Date: Tue, 2 Jun 2020 23:21:59 +0300 Subject: [PATCH 182/427] doc: field processor support now always included, adjust manual pages The features that require field processor support, are now just documented w/o mentioning **Xapian Field Processors**' is needed for those. Replaced "compact" and "field_processor" with "retry_lock" in build_with config option, as it is currently the only one that is optionally excluded. The former 2 are now documented as features always included. Dropped one 'we' "passive" in notmuch-search-terms.rst. It was the only one, and inconsistent with rest of the documentation in that file. Dropped message about conditional open-ended ranges support, as those are now always supported. --- doc/man1/notmuch-config.rst | 5 +-- doc/man7/notmuch-search-terms.rst | 54 +++++++++---------------------- 2 files changed, 18 insertions(+), 41 deletions(-) diff --git a/doc/man1/notmuch-config.rst b/doc/man1/notmuch-config.rst index 323a8b65..769f336a 100644 --- a/doc/man1/notmuch-config.rst +++ b/doc/man1/notmuch-config.rst @@ -198,8 +198,9 @@ The available configuration items are described below. **built_with.** Compile time feature . Current possibilities include - "compact" (see **notmuch-compact(1)**) and "field_processor" (see - **notmuch-search-terms(7)**). + "retry_lock" (configure option, included by default). + (since notmuch 0.30, "compact" and "field_processor" are + always included.) **query.** **[STORED IN DATABASE]** Expansion for named query called . See diff --git a/doc/man7/notmuch-search-terms.rst b/doc/man7/notmuch-search-terms.rst index 1dd2dc58..28fca737 100644 --- a/doc/man7/notmuch-search-terms.rst +++ b/doc/man7/notmuch-search-terms.rst @@ -37,9 +37,8 @@ In addition to free text, the following prefixes can be used to force terms to match against specific portions of an email, (where indicate user-supplied values). -If notmuch is built with **Xapian Field Processors** (see below) some -of the prefixes with forms can be also used to restrict the -results to those whose value matches a regular expression (see +Some of the prefixes with forms can be also used to restrict +the results to those whose value matches a regular expression (see **regex(7)**) delimited with //, for example:: notmuch search 'from:"/bob@.*[.]example[.]com/"' @@ -87,8 +86,7 @@ thread: of output from **notmuch search** thread:{} - If notmuch is built with **Xapian Field Processors** (see below), - threads may be searched for indirectly by providing an arbitrary + Threads may be searched for indirectly by providing an arbitrary notmuch query in **{}**. For example, the following returns threads containing a message from mallory and one (not necessarily the same message) with Subject containing the word "crypto". @@ -158,9 +156,7 @@ lastmod:.. query: The **query:** prefix allows queries to refer to previously saved - queries added with **notmuch-config(1)**. Named queries are only - available if notmuch is built with **Xapian Field Processors** - (see below). + queries added with **notmuch-config(1)**. property:= The **property:** prefix searches for messages with a particular @@ -353,23 +349,21 @@ since 1970-01-01 00:00:00 UTC. For example: date:@..@ -date:..! can be used as a shorthand for date:... The -expansion takes place before interpretation, and thus, for example, -date:monday..! matches from the beginning of Monday until the end of -Monday. -With **Xapian Field Processor** support (see below), non-range -date queries such as date:yesterday will work, but otherwise -will give unexpected results; if in doubt use date:yesterday..! - -Currently, we do not support spaces in range expressions. You can +Currently, spaces in range expressions are not supported. You can replace the spaces with '\_', or (in most cases) '-', or (in some cases) leave the spaces out altogether. Examples in this man page use spaces for clarity. -Open-ended ranges are supported (since Xapian 1.2.1), i.e. it's possible -to specify date:.. or date:.. to not limit the start or -end time, respectively. Pre-1.2.1 Xapian does not report an error on -open ended ranges, but it does not work as expected either. +Open-ended ranges are supported. I.e. it's possible to specify +date:.. or date:.. to not limit the start or +end time, respectively. + +Single expression +----------------- + +date: works as a shorthand for date:... +For example, date:monday matches from the beginning of Monday until +the end of Monday. Relative date and time ---------------------- @@ -446,24 +440,6 @@ Time zones Some time zone codes, e.g. UTC, EET. -XAPIAN FIELD PROCESSORS -======================= - -Certain optional features of the notmuch query processor rely on the -presence of the Xapian field processor API. You can determine if your -notmuch was built against a sufficiently recent version of Xapian by running - -:: - - % notmuch config get built_with.field_processor - -Currently the following features require field processor support: - -- non-range date queries, e.g. "date:today" -- named queries e.g. "query:my_special_query" -- regular expression searches, e.g. "subject:/^\\[SPAM\\]/" -- thread subqueries, e.g. "thread:{from:bob}" - SEE ALSO ======== From ed40579ad3882e6f9bbe9b1ba5e707ab289ca203 Mon Sep 17 00:00:00 2001 From: Tomi Ollila Date: Sun, 24 May 2020 22:17:56 +0300 Subject: [PATCH 183/427] emacs docstrings: consistent indentation, newlines, periods Fixed emacs docstrings to be consistent. No functional change. - removed some (accidental) indentation - removed some trailing newlines - added trailing periods where missing (some exclusions) --- emacs/notmuch-address.el | 21 ++++----- emacs/notmuch-draft.el | 6 +-- emacs/notmuch-hello.el | 11 ++--- emacs/notmuch-jump.el | 3 +- emacs/notmuch-lib.el | 8 ++-- emacs/notmuch-maildir-fcc.el | 4 +- emacs/notmuch-mua.el | 2 +- emacs/notmuch-query.el | 13 +++--- emacs/notmuch-show.el | 22 ++++----- emacs/notmuch-tag.el | 8 ++-- emacs/notmuch-tree.el | 90 ++++++++++++++++++------------------ emacs/notmuch-wash.el | 2 +- emacs/notmuch.el | 34 +++++++------- 13 files changed, 110 insertions(+), 114 deletions(-) diff --git a/emacs/notmuch-address.el b/emacs/notmuch-address.el index 64887a43..0d56fba7 100644 --- a/emacs/notmuch-address.el +++ b/emacs/notmuch-address.el @@ -29,11 +29,11 @@ (declare-function company-manual-begin "company") (defvar notmuch-address-last-harvest 0 - "Time of last address harvest") + "Time of last address harvest.") (defvar notmuch-address-completions (make-hash-table :test 'equal) "Hash of email addresses for completion during email composition. - This variable is set by calling `notmuch-address-harvest'.") +This variable is set by calling `notmuch-address-harvest'.") (defvar notmuch-address-full-harvest-finished nil "t indicates that full completion address harvesting has been @@ -73,11 +73,11 @@ disabled." "Determines how internal address completion generates candidates. This should be a list of the form '(DIRECTION FILTER), where - DIRECTION is either sent or received and specifies whether the - candidates are searched in messages sent by the user or received - by the user (note received by is much faster), and FILTER is - either nil or a filter-string, such as \"date:1y..\" to append - to the query." +DIRECTION is either sent or received and specifies whether the +candidates are searched in messages sent by the user or received +by the user (note received by is much faster), and FILTER is +either nil or a filter-string, such as \"date:1y..\" to append +to the query." :type '(list :tag "Use internal address completion" (radio :tag "Base completion on messages you have" @@ -126,8 +126,7 @@ to know how address selection is made by default." The completed address is passed as an argument to each function. Note that this hook will be invoked for completion in headers -matching `notmuch-address-completion-headers-regexp'. -" +matching `notmuch-address-completion-headers-regexp'." :type 'hook :group 'notmuch-address :group 'notmuch-hooks) @@ -147,7 +146,7 @@ matching `notmuch-address-completion-headers-regexp'. (message "calling notmuch-address-message-insinuate is no longer needed")) (defcustom notmuch-address-use-company t - "If available, use company mode for address completion" + "If available, use company mode for address completion." :type 'boolean :group 'notmuch-send :group 'notmuch-address) @@ -285,7 +284,7 @@ external commands." (defvar notmuch-address-harvest-procs '(nil . nil) "The currently running harvests. -The car is a partial harvest, and the cdr is a full harvest") +The car is a partial harvest, and the cdr is a full harvest.") (defun notmuch-address-harvest (&optional addr-prefix synchronous callback) "Collect addresses completion candidates. diff --git a/emacs/notmuch-draft.el b/emacs/notmuch-draft.el index 504b33be..27abc7d9 100644 --- a/emacs/notmuch-draft.el +++ b/emacs/notmuch-draft.el @@ -76,7 +76,7 @@ postponing and resuming a message." (defcustom notmuch-draft-save-plaintext 'ask "Should notmuch save/postpone in plaintext messages that seem - like they are intended to be sent encrypted +like they are intended to be sent encrypted (i.e with an mml encryption tag in it)." :type '(radio (const :tag "Never" nil) @@ -87,10 +87,10 @@ postponing and resuming a message." (defvar notmuch-draft-encryption-tag-regex "<#\\(part encrypt\\|secure.*mode=.*encrypt>\\)" - "Regular expression matching mml tags indicating encryption of part or message") + "Regular expression matching mml tags indicating encryption of part or message.") (defvar notmuch-draft-id nil - "Message-id of the most recent saved draft of this message") + "Message-id of the most recent saved draft of this message.") (make-variable-buffer-local 'notmuch-draft-id) (defun notmuch-draft--mark-deleted () diff --git a/emacs/notmuch-hello.el b/emacs/notmuch-hello.el index bdf584e6..0ff5aaff 100644 --- a/emacs/notmuch-hello.el +++ b/emacs/notmuch-hello.el @@ -359,7 +359,7 @@ supported for \"Customized queries section\" items." :type 'boolean) (defvar notmuch-hello-hidden-sections nil - "List of sections titles whose contents are hidden") + "List of sections titles whose contents are hidden.") (defvar notmuch-hello-first-run t "True if `notmuch-hello' is run for the first time, set to nil @@ -501,8 +501,7 @@ If FILTER is a function, it is called with QUERY as a parameter and the string it returns is used as the query. If nil is returned, the entry is hidden. -Otherwise, FILTER is ignored. -" +Otherwise, FILTER is ignored." (cond ((functionp filter) (funcall filter query)) ((stringp filter) @@ -656,7 +655,7 @@ with `notmuch-hello-query-counts'." (defvar notmuch-emacs-version) (defun notmuch-hello-versions () - "Display the notmuch version(s)" + "Display the notmuch version(s)." (interactive) (let ((notmuch-cli-version (notmuch-cli-version))) (message "notmuch version %s" @@ -923,7 +922,7 @@ following: options)) (defun notmuch-hello-insert-inbox () - "Show an entry for each saved search and inboxed messages for each tag" + "Show an entry for each saved search and inboxed messages for each tag." (notmuch-hello-insert-searches "What's in your inbox" (append notmuch-saved-searches @@ -931,7 +930,7 @@ following: :filter "tag:inbox")) (defun notmuch-hello-insert-alltags () - "Insert a section displaying all tags and associated message counts" + "Insert a section displaying all tags and associated message counts." (notmuch-hello-insert-tags-section nil :initially-hidden (not notmuch-show-all-tags-list) diff --git a/emacs/notmuch-jump.el b/emacs/notmuch-jump.el index 92a5a2cc..84fe2b9c 100644 --- a/emacs/notmuch-jump.el +++ b/emacs/notmuch-jump.el @@ -88,8 +88,7 @@ ACTION-MAP must be a list of triples of the form where KEY is a key binding, LABEL is a string label to display in the buffer, and ACTION is a nullary function to call. LABEL may be null, in which case the action will still be bound, but will -not appear in the pop-up buffer. -" +not appear in the pop-up buffer." (let* ((items (notmuch-jump--format-actions action-map)) ;; Format the table of bindings and the full prompt diff --git a/emacs/notmuch-lib.el b/emacs/notmuch-lib.el index 01862f44..0bb08eb2 100644 --- a/emacs/notmuch-lib.el +++ b/emacs/notmuch-lib.el @@ -189,7 +189,7 @@ If notmuch exits with a non-zero status, output from the process will appear in a buffer named \"*Notmuch errors*\" and an error will be signaled. -Otherwise the output will be returned" +Otherwise the output will be returned." (with-temp-buffer (let* ((status (apply #'call-process notmuch-command nil t nil args)) (output (buffer-string))) @@ -299,7 +299,7 @@ This is basically just `format-kbd-macro' but we also convert ESC to M-." (defun notmuch-describe-key (actual-key binding prefix ua-keys tail) - "Prepend cons cells describing prefix-arg ACTUAL-KEY and ACTUAL-KEY to TAIL + "Prepend cons cells describing prefix-arg ACTUAL-KEY and ACTUAL-KEY to TAIL. It does not prepend if ACTUAL-KEY is already listed in TAIL." (let ((key-string (concat prefix (key-description actual-key)))) @@ -530,11 +530,11 @@ This replaces spaces, percents, and double quotes in STR with (cdr xplist))) (defun notmuch-split-content-type (content-type) - "Split content/type into 'content' and 'type'" + "Split content/type into 'content' and 'type'." (split-string content-type "/")) (defun notmuch-match-content-type (t1 t2) - "Return t if t1 and t2 are matching content types, taking wildcards into account" + "Return t if t1 and t2 are matching content types, taking wildcards into account." (let ((st1 (notmuch-split-content-type t1)) (st2 (notmuch-split-content-type t2))) (if (or (string= (cadr st1) "*") diff --git a/emacs/notmuch-maildir-fcc.el b/emacs/notmuch-maildir-fcc.el index b9cca543..e95de63b 100644 --- a/emacs/notmuch-maildir-fcc.el +++ b/emacs/notmuch-maildir-fcc.el @@ -78,7 +78,7 @@ directory if it does not exist yet when sending a mail." :group 'notmuch-send) (defcustom notmuch-maildir-use-notmuch-insert 't - "Should fcc use notmuch insert instead of simple fcc" + "Should fcc use notmuch insert instead of simple fcc." :type '(choice :tag "Fcc Method" (const :tag "Use notmuch insert" t) (const :tag "Use simple fcc" nil)) @@ -93,7 +93,7 @@ directory if it does not exist yet when sending a mail." Sets the Fcc header based on the values of `notmuch-fcc-dirs'. Originally intended to be use a hook function, but now called directly -by notmuch-mua-mail" +by notmuch-mua-mail." (let ((subdir (cond diff --git a/emacs/notmuch-mua.el b/emacs/notmuch-mua.el index 40a1e6bc..1efc88ea 100644 --- a/emacs/notmuch-mua.el +++ b/emacs/notmuch-mua.el @@ -334,7 +334,7 @@ Typically this is added to `notmuch-mua-send-hook'." (set-buffer-modified-p nil)) (define-derived-mode notmuch-message-mode message-mode "Message[Notmuch]" - "Notmuch message composition mode. Mostly like `message-mode'" + "Notmuch message composition mode. Mostly like `message-mode'." (notmuch-address-setup)) (put 'notmuch-message-mode 'flyspell-mode-predicate 'mail-mode-flyspell-verify) diff --git a/emacs/notmuch-query.el b/emacs/notmuch-query.el index 563e4acf..3a835a00 100644 --- a/emacs/notmuch-query.el +++ b/emacs/notmuch-query.el @@ -28,8 +28,7 @@ A thread is a forest or list of trees. A tree is a two element list where the first element is a message, and the second element -is a possibly empty forest of replies. -" +is a possibly empty forest of replies." (let ((args '("show" "--format=sexp" "--format-version=4"))) (if notmuch-show-process-crypto (setq args (append args '("--decrypt=true")))) @@ -40,7 +39,7 @@ is a possibly empty forest of replies. ;; Mapping functions across collections of messages. (defun notmuch-query-map-aux (mapper function seq) - "private function to do the actual mapping and flattening" + "Private function to do the actual mapping and flattening." (apply 'append (mapcar (lambda (tree) @@ -48,20 +47,20 @@ is a possibly empty forest of replies. seq))) (defun notmuch-query-map-threads (fn threads) - "apply FN to every thread in THREADS. Flatten results to a list. + "Apply FN to every thread in THREADS. Flatten results to a list. See the function notmuch-query-get-threads for more information." (notmuch-query-map-aux 'notmuch-query-map-forest fn threads)) (defun notmuch-query-map-forest (fn forest) - "apply function to every message in a forest. Flatten results to a list. + "Apply function to every message in a forest. Flatten results to a list. See the function notmuch-query-get-threads for more information. " (notmuch-query-map-aux 'notmuch-query-map-tree fn forest)) (defun notmuch-query-map-tree (fn tree) - "Apply function FN to every message in TREE. Flatten results to a list + "Apply function FN to every message in TREE. Flatten results to a list. See the function notmuch-query-get-threads for more information." (cons (funcall fn (car tree)) (notmuch-query-map-forest fn (cadr tree)))) @@ -70,7 +69,7 @@ See the function notmuch-query-get-threads for more information." ;; Predefined queries (defun notmuch-query-get-message-ids (&rest search-terms) - "Return a list of message-ids of messages that match SEARCH-TERMS" + "Return a list of message-ids of messages that match SEARCH-TERMS." (notmuch-query-map-threads (lambda (msg) (plist-get msg :id)) (notmuch-query-get-threads search-terms))) diff --git a/emacs/notmuch-show.el b/emacs/notmuch-show.el index 59931453..c9170466 100644 --- a/emacs/notmuch-show.el +++ b/emacs/notmuch-show.el @@ -179,7 +179,7 @@ indentation." (make-variable-buffer-local 'notmuch-show-indent-content) (defvar notmuch-show-attachment-debug nil - "If t log stdout and stderr from attachment handlers + "If t log stdout and stderr from attachment handlers. When set to nil (the default) stdout and stderr from attachment handlers is discarded. When set to t the stdout and stderr from @@ -266,7 +266,7 @@ position of the message in the thread." :group 'notmuch-show) (defmacro with-current-notmuch-show-message (&rest body) - "Evaluate body with current buffer set to the text of current message" + "Evaluate body with current buffer set to the text of current message." `(save-excursion (let ((id (notmuch-show-get-message-id))) (let ((buf (generate-new-buffer (concat "*notmuch-msg-" id "*")))) @@ -887,7 +887,7 @@ will return nil if the CID is unknown or cannot be retrieved." nil)))) (defun notmuch-show-create-part-overlays (button beg end) - "Add an overlay to the part between BEG and END" + "Add an overlay to the part between BEG and END." ;; If there is no button (i.e., the part is text/plain and the first ;; part) or if the part has no content then we don't make the part @@ -898,7 +898,7 @@ will return nil if the CID is unknown or cannot be retrieved." t)) (defun notmuch-show-record-part-information (part beg end) - "Store PART as a text property from BEG to END" + "Store PART as a text property from BEG to END." ;; Record part information. Since we already inserted subparts, ;; don't override existing :notmuch-part properties. @@ -1381,7 +1381,7 @@ This includes: (list win-id-combo (notmuch-show-get-message-ids-for-open-messages)))) (defun notmuch-show-get-query () - "Return the current query in this show buffer" + "Return the current query in this show buffer." (if notmuch-show-query-context (concat notmuch-show-thread-id " and (" @@ -1459,7 +1459,7 @@ reset based on the original query." (define-key map "G" 'notmuch-show-stash-git-send-email) (define-key map "?" 'notmuch-subkeymap-help) map) - "Submap for stash commands") + "Submap for stash commands.") (fset 'notmuch-show-stash-map notmuch-show-stash-map) (defvar notmuch-show-part-map @@ -1471,7 +1471,7 @@ reset based on the original query." (define-key map "m" 'notmuch-show-choose-mime-of-part) (define-key map "?" 'notmuch-subkeymap-help) map) - "Submap for part commands") + "Submap for part commands.") (fset 'notmuch-show-part-map notmuch-show-part-map) (defvar notmuch-show-mode-map @@ -1561,14 +1561,14 @@ All currently available key bindings: #'notmuch-show-imenu-extract-index-name-function)) (defun notmuch-tree-from-show-current-query () - "Call notmuch tree with the current query" + "Call notmuch tree with the current query." (interactive) (notmuch-tree notmuch-show-thread-id notmuch-show-query-context (notmuch-show-get-message-id))) (defun notmuch-unthreaded-from-show-current-query () - "Call notmuch unthreaded with the current query" + "Call notmuch unthreaded with the current query." (interactive) (notmuch-unthreaded notmuch-show-thread-id notmuch-show-query-context @@ -2011,7 +2011,7 @@ to show, nil otherwise." (notmuch-show-message-visible props (plist-get props :match)))) (defun notmuch-show-goto-first-wanted-message () - "Move to the first open message and mark it read" + "Move to the first open message and mark it read." (goto-char (point-min)) (unless (notmuch-show-message-visible-p) (notmuch-show-next-open-message)) @@ -2511,7 +2511,7 @@ the new buffer." (view-buffer buf 'kill-buffer-if-not-modified)))) (defun notmuch-show-choose-mime-of-part (mime-type) - "Choose the mime type to use for displaying part" + "Choose the mime type to use for displaying part." (interactive (list (completing-read "Mime type to use (default text/plain): " (mailcap-mime-types) nil nil nil nil "text/plain"))) diff --git a/emacs/notmuch-tag.el b/emacs/notmuch-tag.el index bc83e3de..c54e6ad0 100644 --- a/emacs/notmuch-tag.el +++ b/emacs/notmuch-tag.el @@ -84,7 +84,7 @@ from TAGGING-OPERATIONS." :group 'notmuch-tag) (define-widget 'notmuch-tag-format-type 'lazy - "Customize widget for notmuch-tag-format and friends" + "Customize widget for notmuch-tag-format and friends." :type '(alist :key-type (regexp :tag "Tag") :extra-offset -3 :value-type @@ -313,7 +313,7 @@ are not in TAGS) are shown using formats from are in TAGS but are not in ORIG-TAGS) are shown using formats from `notmuch-tag-added-formats' and tags which have not been changed (the normal case) are shown using formats from -`notmuch-tag-formats'" +`notmuch-tag-formats'." (let* ((tag-state (cond ((not (member tag tags)) 'deleted) ((not (member tag orig-tags)) 'added))) (formatted-tag (gethash (cons tag tag-state) notmuch-tag--format-cache 'missing))) @@ -351,7 +351,7 @@ changed (the normal case) are shown using formats from 'tag-changes' will contain the tags that are about to be added or removed as a list of strings of the form \"+TAG\" or \"-TAG\". 'query' will be a string containing the search query that determines -the messages that are about to be tagged" +the messages that are about to be tagged." :type 'hook :options '(notmuch-hl-line-mode) @@ -363,7 +363,7 @@ the messages that are about to be tagged" 'tag-changes' will contain the tags that were added or removed as a list of strings of the form \"+TAG\" or \"-TAG\". 'query' will be a string containing the search query that determines -the messages that were tagged" +the messages that were tagged." :type 'hook :options '(notmuch-hl-line-mode) :group 'notmuch-hooks) diff --git a/emacs/notmuch-tree.el b/emacs/notmuch-tree.el index 254664c4..ab90f652 100644 --- a/emacs/notmuch-tree.el +++ b/emacs/notmuch-tree.el @@ -45,7 +45,7 @@ ;; this variable distinguishes the unthreaded display from the normal tree display (defvar notmuch-tree-unthreaded nil - "A buffer local copy of argument unthreaded to the function notmuch-tree") + "A buffer local copy of argument unthreaded to the function notmuch-tree.") (make-variable-buffer-local 'notmuch-tree-unthreaded) (defgroup notmuch-tree nil @@ -72,18 +72,18 @@ ("authors" . "%-20s") ((("tree" . "%s")("subject" . "%s")) ." %-54s ") ("tags" . "(%s)")) - "Result formatting for Tree view. Supported fields are: date, - authors, subject, tree, tags. Tree means the thread tree - box graphics. The field may also be a list in which case - the formatting rules are applied recursively and then the - output of all the fields in the list is inserted - according to format-string. + "Result formatting for tree view. Supported fields are: date, +authors, subject, tree, tags. Tree means the thread tree +box graphics. The field may also be a list in which case +the formatting rules are applied recursively and then the +output of all the fields in the list is inserted +according to format-string. Note the author string should not contain - whitespace (put it in the neighbouring fields instead). - For example: +whitespace (put it in the neighbouring fields instead). +For example: (setq notmuch-tree-result-format \(\(\"authors\" . \"%-40s\"\) - \(\"subject\" . \"%s\"\)\)\)" + \(\"subject\" . \"%s\"\)\)\)" :type '(alist :key-type (string) :value-type (string)) :group 'notmuch-tree) @@ -92,18 +92,18 @@ Note the author string should not contain ("authors" . "%-20s") ((("subject" . "%s")) ." %-54s ") ("tags" . "(%s)")) - "Result formatting for unthreaded Tree view. Supported fields are: date, - authors, subject, tree, tags. Tree means the thread tree - box graphics. The field may also be a list in which case - the formatting rules are applied recursively and then the - output of all the fields in the list is inserted - according to format-string. + "Result formatting for unthreaded tree view. Supported fields are: date, +authors, subject, tree, tags. Tree means the thread tree +box graphics. The field may also be a list in which case +the formatting rules are applied recursively and then the +output of all the fields in the list is inserted +according to format-string. Note the author string should not contain - whitespace (put it in the neighbouring fields instead). - For example: +whitespace (put it in the neighbouring fields instead). +For example: (setq notmuch-tree-result-format \(\(\"authors\" . \"%-40s\"\) - \(\"subject\" . \"%s\"\)\)\)" + \(\"subject\" . \"%s\"\)\)\)" :type '(alist :key-type (string) :value-type (string)) :group 'notmuch-tree) @@ -166,7 +166,7 @@ Note the author string should not contain ;; Faces for messages that do not match the query. (defface notmuch-tree-no-match-face '((t (:foreground "gray"))) - "Default face used in tree mode face for non-matching messages" + "Default face used in tree mode face for non-matching messages." :group 'notmuch-tree :group 'notmuch-faces) @@ -201,23 +201,23 @@ Note the author string should not contain :group 'notmuch-faces) (defvar notmuch-tree-previous-subject - "The subject of the most recent result shown during the async display") + "The subject of the most recent result shown during the async display.") (make-variable-buffer-local 'notmuch-tree-previous-subject) (defvar notmuch-tree-basic-query nil - "A buffer local copy of argument query to the function notmuch-tree") + "A buffer local copy of argument query to the function notmuch-tree.") (make-variable-buffer-local 'notmuch-tree-basic-query) (defvar notmuch-tree-query-context nil - "A buffer local copy of argument query-context to the function notmuch-tree") + "A buffer local copy of argument query-context to the function notmuch-tree.") (make-variable-buffer-local 'notmuch-tree-query-context) (defvar notmuch-tree-target-msg nil - "A buffer local copy of argument target to the function notmuch-tree") + "A buffer local copy of argument target to the function notmuch-tree.") (make-variable-buffer-local 'notmuch-tree-target-msg) (defvar notmuch-tree-open-target nil - "A buffer local copy of argument open-target to the function notmuch-tree") + "A buffer local copy of argument open-target to the function notmuch-tree.") (make-variable-buffer-local 'notmuch-tree-open-target) (defvar notmuch-tree-message-window nil @@ -251,14 +251,14 @@ open (if the message pane is closed it does nothing)." (call-interactively #',func))))) (defun notmuch-tree-inherit-from-message-pane (sym) - "Return value of SYM in message-pane if open, or tree-pane if not" + "Return value of SYM in message-pane if open, or tree-pane if not." (if (window-live-p notmuch-tree-message-window) (with-selected-window notmuch-tree-message-window (symbol-value sym)) (symbol-value sym))) (defun notmuch-tree-button-activate (&optional button) - "Activate BUTTON or button at point + "Activate BUTTON or button at point. This function does not give an error if there is no button." (interactive) @@ -346,7 +346,7 @@ FUNC." Some useful entries are: :headers - Property list containing the headers :Date, :Subject, :From, etc. -:tags - Tags for this message" +:tags - Tags for this message." (save-excursion (beginning-of-line) (get-text-property (point) :notmuch-message-properties))) @@ -432,7 +432,7 @@ NOT change the database." (notmuch-show-update-tags new-tags))))))) (defun notmuch-tree-tag (tag-changes) - "Change tags for the current message" + "Change tags for the current message." (interactive (list (notmuch-read-tag-changes (notmuch-tree-get-tags) "Tag message"))) (notmuch-tag (notmuch-tree-get-message-id) tag-changes) @@ -472,26 +472,26 @@ NOT change the database." (notmuch-search query))) (defun notmuch-tree-to-tree () - "Run a query and display results in Tree view" + "Run a query and display results in tree view." (interactive) (let ((query (notmuch-read-query "Notmuch tree view search: "))) (notmuch-tree-close-message-window) (notmuch-tree query))) (defun notmuch-unthreaded-from-tree-current-query () - "Switch from tree view to unthreaded view" + "Switch from tree view to unthreaded view." (interactive) (unless notmuch-tree-unthreaded (notmuch-tree-refresh-view 'unthreaded))) (defun notmuch-tree-from-unthreaded-current-query () - "Switch from unthreaded view to tree view" + "Switch from unthreaded view to tree view." (interactive) (when notmuch-tree-unthreaded (notmuch-tree-refresh-view 'tree))) (defun notmuch-search-from-tree-current-query () - "Call notmuch search with the current query" + "Call notmuch search with the current query." (interactive) (notmuch-tree-close-message-window) (notmuch-search (notmuch-tree-get-query))) @@ -563,7 +563,7 @@ Shows in split pane or whole window according to value of (notmuch-tree-show-message-in))) (defun notmuch-tree-scroll-message-window () - "Scroll the message window (if it exists)" + "Scroll the message window (if it exists)." (interactive) (when (window-live-p notmuch-tree-message-window) (with-selected-window notmuch-tree-message-window @@ -572,7 +572,7 @@ Shows in split pane or whole window according to value of (scroll-up))))) (defun notmuch-tree-scroll-message-window-back () - "Scroll the message window back(if it exists)" + "Scroll the message window back(if it exists)." (interactive) (when (window-live-p notmuch-tree-message-window) (with-selected-window notmuch-tree-message-window @@ -661,7 +661,7 @@ nil otherwise." (not (funcall eobfn)))) (defun notmuch-tree-matching-message (&optional prev pop-at-end) - "Move to the next or previous matching message" + "Move to the next or previous matching message." (interactive "P") (forward-line (if prev -1 nil)) (if (and (not (notmuch-tree-goto-matching-message prev)) pop-at-end) @@ -734,7 +734,7 @@ found or nil if not." " or ")) (defun notmuch-tree-tag-thread (tag-changes) - "Tag all messages in the current thread" + "Tag all messages in the current thread." (interactive (let ((tags (apply #'append (notmuch-tree-thread-mapcar (lambda () (notmuch-tree-get-tags)))))) @@ -775,7 +775,7 @@ unchanged ADDRESS if parsing fails." (or p-name p-address))) (defun notmuch-tree-format-field (field format-string msg) - "Format a FIELD of MSG according to FORMAT-STRING and return string" + "Format a FIELD of MSG according to FORMAT-STRING and return string." (let* ((headers (plist-get msg :headers)) (match (plist-get msg :match))) (cond @@ -831,7 +831,7 @@ unchanged ADDRESS if parsing fails." (format format-string (notmuch-tag-format-tags tags orig-tags face))))))) (defun notmuch-tree-format-field-list (field-list msg) - "Format fields of MSG according to FIELD-LIST and return string" + "Format fields of MSG according to FIELD-LIST and return string." (let ((face (if (plist-get msg :match) 'notmuch-tree-match-face 'notmuch-tree-no-match-face)) @@ -842,7 +842,7 @@ unchanged ADDRESS if parsing fails." (notmuch-apply-face result-string face t))) (defun notmuch-tree-insert-msg (msg) - "Insert the message MSG according to notmuch-tree-result-format" + "Insert the message MSG according to notmuch-tree-result-format." ;; We need to save the previous subject as it will get overwritten ;; by the insert-field calls. (let ((previous-subject notmuch-tree-previous-subject)) @@ -852,7 +852,7 @@ unchanged ADDRESS if parsing fails." (insert "\n"))) (defun notmuch-tree-goto-and-insert-msg (msg) - "Insert msg at the end of the buffer. Move point to msg if it is the target" + "Insert msg at the end of the buffer. Move point to msg if it is the target." (save-excursion (goto-char (point-max)) (notmuch-tree-insert-msg msg)) @@ -946,7 +946,7 @@ Complete list of currently available key bindings: truncate-lines t)) (defun notmuch-tree-process-sentinel (proc msg) - "Add a message to let user know when \"notmuch tree\" exits" + "Add a message to let user know when \"notmuch tree\" exits." (let ((buffer (process-buffer proc)) (status (process-status proc)) (exit-status (process-exit-status proc)) @@ -968,7 +968,7 @@ Complete list of currently available key bindings: (insert "\n"))))))))) (defun notmuch-tree-process-filter (proc string) - "Process and filter the output of \"notmuch show\" for tree view" + "Process and filter the output of \"notmuch show\" for tree view." (let ((results-buf (process-buffer proc)) (parse-buf (process-get proc 'parse-buf)) (inhibit-read-only t) @@ -1026,7 +1026,7 @@ the same as for the function notmuch-tree." (set-process-query-on-exit-flag proc nil)))) (defun notmuch-tree-get-query () - "Return the current query in this tree buffer" + "Return the current query in this tree buffer." (if notmuch-tree-query-context (concat notmuch-tree-basic-query " and (" @@ -1035,7 +1035,7 @@ the same as for the function notmuch-tree." notmuch-tree-basic-query)) (defun notmuch-tree (&optional query query-context target buffer-name open-target unthreaded) - "Display threads matching QUERY in Tree View. + "Display threads matching QUERY in tree view. The arguments are: QUERY: the main query. This can be any query but in many cases will be diff --git a/emacs/notmuch-wash.el b/emacs/notmuch-wash.el index 54108d93..fd0314cc 100644 --- a/emacs/notmuch-wash.el +++ b/emacs/notmuch-wash.el @@ -192,7 +192,7 @@ message parts." (format label-format lines-count))) (defun notmuch-wash-region-to-button (msg beg end type &optional prefix) - "Auxiliary function to do the actual making of overlays and buttons + "Auxiliary function to do the actual making of overlays and buttons. BEG and END are buffer locations. TYPE should a string, either \"citation\" or \"signature\". Optional PREFIX is some arbitrary diff --git a/emacs/notmuch.el b/emacs/notmuch.el index 5a24d8a7..dd18f2e1 100644 --- a/emacs/notmuch.el +++ b/emacs/notmuch.el @@ -113,7 +113,7 @@ there will be called at other points of notmuch execution." :group 'notmuch) (defvar notmuch-query-history nil - "Variable to store minibuffer history for notmuch queries") + "Variable to store minibuffer history for notmuch queries.") (defun notmuch-foreach-mime-part (function mm-handle) (cond ((stringp (car mm-handle)) @@ -202,7 +202,7 @@ there will be called at other points of notmuch execution." (define-key map "q" 'notmuch-stash-query) (define-key map "?" 'notmuch-subkeymap-help) map) - "Submap for stash commands") + "Submap for stash commands.") (fset 'notmuch-search-stash-map notmuch-search-stash-map) (defun notmuch-search-stash-thread-id () @@ -417,7 +417,7 @@ If there is no thread at POS (or point), returns nil." The returned point will be just after the newline character that ends the result line. If there is no thread at POS (or point), -returns nil" +returns nil." (when (notmuch-search-get-result pos) (next-single-property-change (or pos (point)) 'notmuch-search-result nil (point-max)))) @@ -457,9 +457,9 @@ BEG." output)) (defun notmuch-search-find-thread-id (&optional bare) - "Return the thread for the current thread + "Return the thread for the current thread. -If BARE is set then do not prefix with \"thread:\"" +If BARE is set then do not prefix with \"thread:\"." (let ((thread (plist-get (notmuch-search-get-result) :thread))) (when thread (concat (unless bare "thread:") thread)))) @@ -486,19 +486,19 @@ no messages in the region then return nil." (concat "(" (mapconcat 'identity query-list ") or (") ")")))) (defun notmuch-search-find-authors () - "Return the authors for the current thread" + "Return the authors for the current thread." (plist-get (notmuch-search-get-result) :authors)) (defun notmuch-search-find-authors-region (beg end) - "Return a list of authors for the current region" + "Return a list of authors for the current region." (notmuch-search-properties-in-region :authors beg end)) (defun notmuch-search-find-subject () - "Return the subject for the current thread" + "Return the subject for the current thread." (plist-get (notmuch-search-get-result) :subject)) (defun notmuch-search-find-subject-region (beg end) - "Return a list of authors for the current region" + "Return a list of authors for the current region." (notmuch-search-properties-in-region :subject beg end)) (defun notmuch-search-show-thread (&optional elide-toggle) @@ -520,22 +520,22 @@ thread." (message "End of search results.")))) (defun notmuch-tree-from-search-current-query () - "Call notmuch tree with the current query" + "Call notmuch tree with the current query." (interactive) (notmuch-tree notmuch-search-query-string)) (defun notmuch-unthreaded-from-search-current-query () - "Call notmuch tree with the current query" + "Call notmuch tree with the current query." (interactive) (notmuch-unthreaded notmuch-search-query-string)) (defun notmuch-tree-from-search-thread () - "Show the selected thread with notmuch-tree" + "Show the selected thread with notmuch-tree." (interactive) (notmuch-tree (notmuch-search-find-thread-id) - notmuch-search-query-string + notmuch-search-query-string nil - (notmuch-prettify-subject (notmuch-search-find-subject)) + (notmuch-prettify-subject (notmuch-search-find-subject)) t)) (defun notmuch-search-reply-to-thread (&optional prompt-for-sender) @@ -660,7 +660,7 @@ of the result." (goto-char new-point))))) (defun notmuch-search-process-sentinel (proc msg) - "Add a message to let user know when \"notmuch search\" exits" + "Add a message to let user know when \"notmuch search\" exits." (let ((buffer (process-buffer proc)) (status (process-status proc)) (exit-status (process-exit-status proc)) @@ -862,7 +862,7 @@ sets the :orig-tag property." (goto-char pos)))) (defun notmuch-search-process-filter (proc string) - "Process and filter the output of \"notmuch search\"" + "Process and filter the output of \"notmuch search\"." (let ((results-buf (process-buffer proc)) (parse-buf (process-get proc 'parse-buf)) (inhibit-read-only t) @@ -952,7 +952,7 @@ PROMPT is the string to prompt with." 'notmuch-search-history current-query nil))))) (defun notmuch-search-get-query () - "Return the current query in this search buffer" + "Return the current query in this search buffer." notmuch-search-query-string) (put 'notmuch-search 'notmuch-doc "Search for messages.") From 3bb546b30fd9509de63e5f31c093b8b08f79a39b Mon Sep 17 00:00:00 2001 From: David Bremner Date: Sat, 6 Jun 2020 08:02:16 -0300 Subject: [PATCH 184/427] version: bump to 0.30~rc1 --- bindings/python/notmuch/version.py | 2 +- version | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/bindings/python/notmuch/version.py b/bindings/python/notmuch/version.py index d0c9b219..1a5d0579 100644 --- a/bindings/python/notmuch/version.py +++ b/bindings/python/notmuch/version.py @@ -1,3 +1,3 @@ # this file should be kept in sync with ../../../version -__VERSION__ = '0.30~rc0' +__VERSION__ = '0.30~rc1' SOVERSION = '5' diff --git a/version b/version index f792fd29..81bdbf1d 100644 --- a/version +++ b/version @@ -1 +1 @@ -0.30~rc0 +0.30~rc1 From 411229f26b8e71b78411b0e9dae1cd27a00f15b8 Mon Sep 17 00:00:00 2001 From: David Bremner Date: Sat, 6 Jun 2020 08:03:25 -0300 Subject: [PATCH 185/427] debian: changelog stanza for 0.30~rc1-1 --- debian/changelog | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/debian/changelog b/debian/changelog index 8fa2dcbf..704b54f6 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,10 @@ +notmuch (0.30~rc1-1) experimental; urgency=medium + + * New upstream release candidate + * Update debian/changelog (new copyright holders) + + -- David Bremner Sat, 06 Jun 2020 08:06:56 -0300 + notmuch (0.30~rc0-2) experimental; urgency=medium * New upstream release candidate From 963e363a234f1c8bdf1ae68956f80a2538bee7dc Mon Sep 17 00:00:00 2001 From: Tomi Ollila Date: Tue, 9 Jun 2020 15:32:27 +0300 Subject: [PATCH 186/427] configure: use cffi.FFI().verify() to test buildability of CFFI bindings Checking existence of pyconfig.h to determine whether CFFI-based notmuch bindings are buildable is not enough; for example Fedora 32 ships pyconfig.h in python3-libs package, but python3-devel is required to be installed for the bindings to build. Executing cffi.FFI().verify() is pretty close to what is done in bindings/python-cffi/notmuch2/_build.py to get the c code part of the bindings built. --- configure | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/configure b/configure index f4b3c61a..05ade05b 100755 --- a/configure +++ b/configure @@ -711,14 +711,12 @@ if [ $have_python -eq 0 ]; then errors=$((errors + 1)) fi -have_python3_dev=0 +have_python3=0 if [ $have_python -eq 1 ]; then - printf "Checking for python3 dev (>= 3.5)..." - if "$python" -c 'import os, sys, sysconfig; -assert sys.version_info >= (3,5) -assert os.path.isfile(sysconfig.get_config_h_filename())' >/dev/null 2>&1; then + printf "Checking for python3 (>= 3.5)..." + if "$python" -c 'import sys, sysconfig; assert sys.version_info >= (3,5)'; >/dev/null 2>&1; then printf "Yes.\n" - have_python3_dev=1 + have_python3=1 else printf "No (will not install CFFI-based python bindings).\n" fi @@ -726,14 +724,15 @@ fi have_python3_cffi=0 have_python3_pytest=0 -if [ $have_python3_dev -eq 1 ]; then +if [ $have_python3 -eq 1 ]; then printf "Checking for python3 cffi and setuptools... " - if "$python" -c 'import cffi; import setuptools' >/dev/null 2>&1; then + if "$python" -c 'import cffi,setuptools; cffi.FFI().verify()' >/dev/null 2>&1; then printf "Yes.\n" have_python3_cffi=1 else printf "No (will not install CFFI-based python bindings).\n" fi + rm -rf __pycache__ # cffi.FFI().verify() uses this space printf "Checking for python3 pytest (>= 3.0)... " conf=$(mktemp) From a00f3a1f7a646d9834a5de57a29cf3e900dbdcdf Mon Sep 17 00:00:00 2001 From: Floris Bruynooghe Date: Sun, 14 Jun 2020 20:33:55 +0200 Subject: [PATCH 187/427] Add missing set methods to tagsets Even though we use collections.abc.Set which implements all these methods under their operator names, the actual named variations of these methods are shockingly missing. So let's add them manually. --- bindings/python-cffi/notmuch2/_tags.py | 21 +++++++++ bindings/python-cffi/tests/test_tags.py | 62 +++++++++++++++++++++++++ 2 files changed, 83 insertions(+) diff --git a/bindings/python-cffi/notmuch2/_tags.py b/bindings/python-cffi/notmuch2/_tags.py index 212852a8..3b14c981 100644 --- a/bindings/python-cffi/notmuch2/_tags.py +++ b/bindings/python-cffi/notmuch2/_tags.py @@ -110,6 +110,27 @@ class ImmutableTagSet(base.NotmuchObject, collections.abc.Set): def __eq__(self, other): return tuple(sorted(self.iter())) == tuple(sorted(other.iter())) + def issubset(self, other): + return self <= other + + def issuperset(self, other): + return self >= other + + def union(self, other): + return self | other + + def intersection(self, other): + return self & other + + def difference(self, other): + return self - other + + def symmetric_difference(self, other): + return self ^ other + + def copy(self): + return set(self) + def __hash__(self): return hash(tuple(self.iter())) diff --git a/bindings/python-cffi/tests/test_tags.py b/bindings/python-cffi/tests/test_tags.py index f12fa1e6..faf3947b 100644 --- a/bindings/python-cffi/tests/test_tags.py +++ b/bindings/python-cffi/tests/test_tags.py @@ -50,6 +50,22 @@ class TestImmutable: assert 'unread' in tagset assert 'foo' not in tagset + def test_isdisjoint(self, tagset): + assert tagset.isdisjoint(set(['spam', 'ham'])) + assert not tagset.isdisjoint(set(['inbox'])) + + def test_issubset(self, tagset): + assert {'inbox'} <= tagset + assert {'inbox'}.issubset(tagset) + assert tagset <= {'inbox', 'unread', 'spam'} + assert tagset.issubset({'inbox', 'unread', 'spam'}) + + def test_issuperset(self, tagset): + assert {'inbox', 'unread', 'spam'} >= tagset + assert {'inbox', 'unread', 'spam'}.issuperset(tagset) + assert tagset >= {'inbox'} + assert tagset.issuperset({'inbox'}) + def test_iter(self, tagset): expected = sorted(['unread', 'inbox']) found = [] @@ -78,18 +94,30 @@ class TestImmutable: assert isinstance(common, set) assert isinstance(common, collections.abc.Set) assert common == {'unread'} + common = tagset.intersection({'unread'}) + assert isinstance(common, set) + assert isinstance(common, collections.abc.Set) + assert common == {'unread'} def test_or(self, tagset): res = tagset | {'foo'} assert isinstance(res, set) assert isinstance(res, collections.abc.Set) assert res == {'unread', 'inbox', 'foo'} + res = tagset.union({'foo'}) + assert isinstance(res, set) + assert isinstance(res, collections.abc.Set) + assert res == {'unread', 'inbox', 'foo'} def test_sub(self, tagset): res = tagset - {'unread'} assert isinstance(res, set) assert isinstance(res, collections.abc.Set) assert res == {'inbox'} + res = tagset.difference({'unread'}) + assert isinstance(res, set) + assert isinstance(res, collections.abc.Set) + assert res == {'inbox'} def test_rsub(self, tagset): res = {'foo', 'unread'} - tagset @@ -102,6 +130,10 @@ class TestImmutable: assert isinstance(res, set) assert isinstance(res, collections.abc.Set) assert res == {'inbox', 'foo'} + res = tagset.symmetric_difference({'unread', 'foo'}) + assert isinstance(res, set) + assert isinstance(res, collections.abc.Set) + assert res == {'inbox', 'foo'} def test_rxor(self, tagset): res = {'unread', 'foo'} ^ tagset @@ -109,6 +141,12 @@ class TestImmutable: assert isinstance(res, collections.abc.Set) assert res == {'inbox', 'foo'} + def test_copy(self, tagset): + res = tagset.copy() + assert isinstance(res, set) + assert isinstance(res, collections.abc.Set) + assert res == {'inbox', 'unread'} + class TestMutableTagset: @@ -175,3 +213,27 @@ class TestMutableTagset: msg.tags.to_maildir_flags() flags = msg.path.name.split(',')[-1] assert 'F' not in flags + + def test_isdisjoint(self, tagset): + assert tagset.isdisjoint(set(['spam', 'ham'])) + assert not tagset.isdisjoint(set(['inbox'])) + + def test_issubset(self, tagset): + assert {'inbox'} <= tagset + assert {'inbox'}.issubset(tagset) + assert not {'spam'} <= tagset + assert not {'spam'}.issubset(tagset) + assert tagset <= {'inbox', 'unread', 'spam'} + assert tagset.issubset({'inbox', 'unread', 'spam'}) + assert not {'inbox', 'unread', 'spam'} <= tagset + assert not {'inbox', 'unread', 'spam'}.issubset(tagset) + + def test_issuperset(self, tagset): + assert {'inbox', 'unread', 'spam'} >= tagset + assert {'inbox', 'unread', 'spam'}.issuperset(tagset) + assert tagset >= {'inbox'} + assert tagset.issuperset({'inbox'}) + + def test_union(self, tagset): + assert {'spam'}.union(tagset) == {'inbox', 'unread', 'spam'} + assert tagset.union({'spam'}) == {'inbox', 'unread', 'spam'} From b7e3a347ac71d2d31747259d4b3bfa089fbaa2b1 Mon Sep 17 00:00:00 2001 From: Floris Bruynooghe Date: Sun, 14 Jun 2020 21:25:46 +0200 Subject: [PATCH 188/427] Update tox.ini for python3.8 and fix pypy3.6 Python 3.8 has been released for a while now, make sure we keep supporting it correctly. PyPy 3.6 wasn not configured correctly. --- bindings/python-cffi/tox.ini | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/bindings/python-cffi/tox.ini b/bindings/python-cffi/tox.ini index 34148a11..7cf93be0 100644 --- a/bindings/python-cffi/tox.ini +++ b/bindings/python-cffi/tox.ini @@ -3,7 +3,7 @@ minversion = 3.0 addopts = -ra --cov=notmuch2 --cov=tests [tox] -envlist = py35,py36,py37,pypy35,pypy36 +envlist = py35,py36,py37,py38,pypy35,pypy36 [testenv] deps = @@ -14,3 +14,6 @@ commands = pytest --cov={envsitepackagesdir}/notmuch2 {posargs} [testenv:pypy35] basepython = pypy3.5 + +[testenv:pypy36] +basepython = pypy3.6 From 5a58754841f4d3e62d104ad338c8ca2c481dc32e Mon Sep 17 00:00:00 2001 From: Anton Khirnov Date: Mon, 15 Jun 2020 23:55:52 +0200 Subject: [PATCH 189/427] python/notmuch2: add bindings for the database config strings --- bindings/python-cffi/notmuch2/_build.py | 17 +++++ bindings/python-cffi/notmuch2/_config.py | 84 ++++++++++++++++++++++ bindings/python-cffi/notmuch2/_database.py | 23 ++++++ 3 files changed, 124 insertions(+) create mode 100644 bindings/python-cffi/notmuch2/_config.py diff --git a/bindings/python-cffi/notmuch2/_build.py b/bindings/python-cffi/notmuch2/_build.py index 5e1fcac1..f269f2a1 100644 --- a/bindings/python-cffi/notmuch2/_build.py +++ b/bindings/python-cffi/notmuch2/_build.py @@ -314,6 +314,23 @@ ffibuilder.cdef( notmuch_indexopts_get_decrypt_policy (const notmuch_indexopts_t *indexopts); void notmuch_indexopts_destroy (notmuch_indexopts_t *options); + + notmuch_status_t + notmuch_database_set_config (notmuch_database_t *db, const char *key, const char *value); + notmuch_status_t + notmuch_database_get_config (notmuch_database_t *db, const char *key, char **value); + notmuch_status_t + notmuch_database_get_config_list (notmuch_database_t *db, const char *prefix, notmuch_config_list_t **out); + notmuch_bool_t + notmuch_config_list_valid (notmuch_config_list_t *config_list); + const char * + notmuch_config_list_key (notmuch_config_list_t *config_list); + const char * + notmuch_config_list_value (notmuch_config_list_t *config_list); + void + notmuch_config_list_move_to_next (notmuch_config_list_t *config_list); + void + notmuch_config_list_destroy (notmuch_config_list_t *config_list); """ ) diff --git a/bindings/python-cffi/notmuch2/_config.py b/bindings/python-cffi/notmuch2/_config.py new file mode 100644 index 00000000..58383c16 --- /dev/null +++ b/bindings/python-cffi/notmuch2/_config.py @@ -0,0 +1,84 @@ +import collections.abc + +import notmuch2._base as base +import notmuch2._capi as capi +import notmuch2._errors as errors + +__all__ = ['ConfigMapping'] + +class ConfigIter(base.NotmuchIter): + def __init__(self, parent, iter_p): + super().__init__( + parent, iter_p, + fn_destroy=capi.lib.notmuch_config_list_destroy, + fn_valid=capi.lib.notmuch_config_list_valid, + fn_get=capi.lib.notmuch_config_list_key, + fn_next=capi.lib.notmuch_config_list_move_to_next) + + def __next__(self): + item = super().__next__() + return base.BinString.from_cffi(item) + +class ConfigMapping(base.NotmuchObject, collections.abc.MutableMapping): + """The config key/value pairs stored in the database. + + The entries are exposed as a :class:`collections.abc.MutableMapping` object. + Note that setting a value to an empty string is the same as deleting it. + + :param parent: the parent object + :param ptr_name: the name of the attribute on the parent which will + return the memory pointer. This allows this object to + access the pointer via the parent's descriptor and thus + trigger :class:`MemoryPointer`'s memory safety. + """ + + def __init__(self, parent, ptr_name): + self._parent = parent + self._ptr = lambda: getattr(parent, ptr_name) + + @property + def alive(self): + return self._parent.alive + + def _destroy(self): + pass + + def __getitem__(self, key): + if isinstance(key, str): + key = key.encode('utf-8') + val_pp = capi.ffi.new('char**') + ret = capi.lib.notmuch_database_get_config(self._ptr(), key, val_pp) + if ret != capi.lib.NOTMUCH_STATUS_SUCCESS: + raise errors.NotmuchError(ret) + if val_pp[0] == "": + capi.lib.free(val_pp[0]) + raise KeyError + val = base.BinString.from_cffi(val_pp[0]) + capi.lib.free(val_pp[0]) + return val + + def __setitem__(self, key, val): + if isinstance(key, str): + key = key.encode('utf-8') + if isinstance(val, str): + val = val.encode('utf-8') + ret = capi.lib.notmuch_database_set_config(self._ptr(), key, val) + if ret != capi.lib.NOTMUCH_STATUS_SUCCESS: + raise errors.NotmuchError(ret) + + def __delitem__(self, key): + self[key] = "" + + def __iter__(self): + """Return an iterator over the config items. + + :raises NullPointerError: If the iterator can not be created. + """ + configlist_pp = capi.ffi.new('notmuch_config_list_t**') + ret = capi.lib.notmuch_database_get_config_list(self._ptr(), b'', configlist_pp) + if ret != capi.lib.NOTMUCH_STATUS_SUCCESS: + raise errors.NotmuchError(ret) + return ConfigIter(self._parent, configlist_pp[0]) + + def __len__(self): + return sum(1 for t in self) diff --git a/bindings/python-cffi/notmuch2/_database.py b/bindings/python-cffi/notmuch2/_database.py index 95f59ca0..3c06402d 100644 --- a/bindings/python-cffi/notmuch2/_database.py +++ b/bindings/python-cffi/notmuch2/_database.py @@ -7,6 +7,7 @@ import pathlib import weakref import notmuch2._base as base +import notmuch2._config as config import notmuch2._capi as capi import notmuch2._errors as errors import notmuch2._message as message @@ -536,6 +537,28 @@ class Database(base.NotmuchObject): self._cached_tagset = weakref.ref(tagset) return tagset + @property + def config(self): + """Return a mutable mapping with the settings stored in this database. + + This returns an mutable dict-like object implementing the + collections.abc.MutableMapping Abstract Base Class. + + :rtype: Config + + :raises ObjectDestroyedError: if used after destroyed. + """ + try: + ref = self._cached_config + except AttributeError: + config_mapping = None + else: + config_mapping = ref() + if config_mapping is None: + config_mapping = config.ConfigMapping(self, '_db_p') + self._cached_config = weakref.ref(config_mapping) + return config_mapping + def _create_query(self, query, *, omit_excluded=EXCLUDE.TRUE, sort=SORT.UNSORTED, # Check this default From 1bca41698a03980b701558fb5c481ef0a340460d Mon Sep 17 00:00:00 2001 From: Floris Bruynooghe Date: Mon, 15 Jun 2020 23:55:53 +0200 Subject: [PATCH 190/427] python config access: fix style and KeyError bug This fixes some minor style/pep8 things and adds tests for the new config support. Also fixes a bug where KeyError was never raised on a missing key. --- bindings/python-cffi/notmuch2/_config.py | 9 ++-- bindings/python-cffi/tests/test_config.py | 56 +++++++++++++++++++++++ 2 files changed, 62 insertions(+), 3 deletions(-) create mode 100644 bindings/python-cffi/tests/test_config.py diff --git a/bindings/python-cffi/notmuch2/_config.py b/bindings/python-cffi/notmuch2/_config.py index 58383c16..29de6495 100644 --- a/bindings/python-cffi/notmuch2/_config.py +++ b/bindings/python-cffi/notmuch2/_config.py @@ -4,9 +4,12 @@ import notmuch2._base as base import notmuch2._capi as capi import notmuch2._errors as errors + __all__ = ['ConfigMapping'] + class ConfigIter(base.NotmuchIter): + def __init__(self, parent, iter_p): super().__init__( parent, iter_p, @@ -19,6 +22,7 @@ class ConfigIter(base.NotmuchIter): item = super().__next__() return base.BinString.from_cffi(item) + class ConfigMapping(base.NotmuchObject, collections.abc.MutableMapping): """The config key/value pairs stored in the database. @@ -50,11 +54,10 @@ class ConfigMapping(base.NotmuchObject, collections.abc.MutableMapping): ret = capi.lib.notmuch_database_get_config(self._ptr(), key, val_pp) if ret != capi.lib.NOTMUCH_STATUS_SUCCESS: raise errors.NotmuchError(ret) - if val_pp[0] == "": - capi.lib.free(val_pp[0]) - raise KeyError val = base.BinString.from_cffi(val_pp[0]) capi.lib.free(val_pp[0]) + if val == '': + raise KeyError return val def __setitem__(self, key, val): diff --git a/bindings/python-cffi/tests/test_config.py b/bindings/python-cffi/tests/test_config.py new file mode 100644 index 00000000..1b2695f5 --- /dev/null +++ b/bindings/python-cffi/tests/test_config.py @@ -0,0 +1,56 @@ +import collections.abc + +import pytest + +import notmuch2._database as dbmod + +import notmuch2._config as config + + +class TestIter: + + @pytest.fixture + def db(self, maildir): + with dbmod.Database.create(maildir.path) as db: + yield db + + def test_type(self, db): + assert isinstance(db.config, collections.abc.MutableMapping) + assert isinstance(db.config, config.ConfigMapping) + + def test_alive(self, db): + assert db.config.alive + + def test_set_get(self, maildir): + # Ensure get-set works from different db objects + with dbmod.Database.create(maildir.path) as db0: + db0.config['spam'] = 'ham' + with dbmod.Database(maildir.path) as db1: + assert db1.config['spam'] == 'ham' + + def test_get_keyerror(self, db): + with pytest.raises(KeyError): + val = db.config['not-a-key'] + print(repr(val)) + + def test_iter(self, db): + assert list(db.config) == [] + db.config['spam'] = 'ham' + db.config['eggs'] = 'bacon' + assert set(db.config) == {'spam', 'eggs'} + assert set(db.config.keys()) == {'spam', 'eggs'} + assert set(db.config.values()) == {'ham', 'bacon'} + assert set(db.config.items()) == {('spam', 'ham'), ('eggs', 'bacon')} + + def test_len(self, db): + assert len(db.config) == 0 + db.config['spam'] = 'ham' + assert len(db.config) == 1 + db.config['eggs'] = 'bacon' + assert len(db.config) == 2 + + def test_del(self, db): + db.config['spam'] = 'ham' + assert db.config.get('spam') == 'ham' + del db.config['spam'] + assert db.config.get('spam') is None From 131757907907380213b32934d9e73cec942ace43 Mon Sep 17 00:00:00 2001 From: Anton Khirnov Date: Mon, 15 Jun 2020 22:58:49 +0200 Subject: [PATCH 191/427] python/notmuch2: do not destroy messages owned by a query Any messages retrieved from a query - either directly via search_messages() or indirectly via thread objects - are owned by that query. Retrieving the same message (i.e. corresponding to the same message ID / database object) several times will always yield the same C object. The caller is allowed to destroy message objects owned by a query before the query itself - which can save memory for long-lived queries. However, that message must then never be retrieved again from that query. The python-notmuch2 bindings will currently destroy every message object in Message._destroy(), which will lead to an invalid free if the same message is then retrieved again. E.g. the following python program leads to libtalloc abort()ing: import notmuch2 db = notmuch2.Database(mode = notmuch2.Database.MODE.READ_ONLY) t = next(db.threads('*')) msgs = list(zip(t.toplevel(), t.toplevel())) msgs = list(zip(t.toplevel(), t.toplevel())) Fix this issue by creating a subclass of Message, which is used for "standalone" message which have to be freed by the caller. Message class is then used only for messages descended from a query, which do not need to be freed by the caller. --- bindings/python-cffi/notmuch2/_database.py | 6 ++-- bindings/python-cffi/notmuch2/_message.py | 42 ++++++++++++++-------- 2 files changed, 30 insertions(+), 18 deletions(-) diff --git a/bindings/python-cffi/notmuch2/_database.py b/bindings/python-cffi/notmuch2/_database.py index 3c06402d..fc55fea8 100644 --- a/bindings/python-cffi/notmuch2/_database.py +++ b/bindings/python-cffi/notmuch2/_database.py @@ -400,7 +400,7 @@ class Database(base.NotmuchObject): capi.lib.NOTMUCH_STATUS_DUPLICATE_MESSAGE_ID] if ret not in ok: raise errors.NotmuchError(ret) - msg = message.Message(self, msg_pp[0], db=self) + msg = message.StandaloneMessage(self, msg_pp[0], db=self) if sync_flags: msg.tags.from_maildir_flags() return self.AddedMessage( @@ -469,7 +469,7 @@ class Database(base.NotmuchObject): msg_p = msg_pp[0] if msg_p == capi.ffi.NULL: raise LookupError - msg = message.Message(self, msg_p, db=self) + msg = message.StandaloneMessage(self, msg_p, db=self) return msg def get(self, filename): @@ -502,7 +502,7 @@ class Database(base.NotmuchObject): msg_p = msg_pp[0] if msg_p == capi.ffi.NULL: raise LookupError - msg = message.Message(self, msg_p, db=self) + msg = message.StandaloneMessage(self, msg_p, db=self) return msg @property diff --git a/bindings/python-cffi/notmuch2/_message.py b/bindings/python-cffi/notmuch2/_message.py index c5fdbf6d..416ce7ca 100644 --- a/bindings/python-cffi/notmuch2/_message.py +++ b/bindings/python-cffi/notmuch2/_message.py @@ -14,7 +14,7 @@ __all__ = ['Message'] class Message(base.NotmuchObject): - """An email message stored in the notmuch database. + """An email message stored in the notmuch database retrieved via a query. This should not be directly created, instead it will be returned by calling methods on :class:`Database`. A message keeps a @@ -61,22 +61,10 @@ class Message(base.NotmuchObject): @property def alive(self): - if not self._parent.alive: - return False - try: - self._msg_p - except errors.ObjectDestroyedError: - return False - else: - return True - - def __del__(self): - self._destroy() + return self._parent.alive def _destroy(self): - if self.alive: - capi.lib.notmuch_message_destroy(self._msg_p) - self._msg_p = None + pass @property def messageid(self): @@ -375,6 +363,30 @@ class Message(base.NotmuchObject): if isinstance(other, self.__class__): return self.messageid == other.messageid +class StandaloneMessage(Message): + """An email message stored in the notmuch database. + + This subclass of Message is used for messages that are retrieved from the + database directly and are not owned by a query. + """ + @property + def alive(self): + if not self._parent.alive: + return False + try: + self._msg_p + except errors.ObjectDestroyedError: + return False + else: + return True + + def __del__(self): + self._destroy() + + def _destroy(self): + if self.alive: + capi.lib.notmuch_message_destroy(self._msg_p) + self._msg_p = None class FilenamesIter(base.NotmuchIter): """Iterator for binary filenames objects.""" From 2d895a0119b423b117d10e890c9e0eb5d2a9cdf8 Mon Sep 17 00:00:00 2001 From: Floris Bruynooghe Date: Mon, 15 Jun 2020 22:58:50 +0200 Subject: [PATCH 192/427] Make messages returned by Thread objects owned This reverses the logic of StandaloneMessage to instead create a OwnedMessage. Only the Thread class allows retrieving messages more then once so it can explicitly create such messages. The added test fails with SIGABRT without the fix for the message re-use in threads being present. --- bindings/python-cffi/notmuch2/_database.py | 6 +-- bindings/python-cffi/notmuch2/_message.py | 55 ++++++++++++--------- bindings/python-cffi/notmuch2/_thread.py | 8 ++- bindings/python-cffi/tests/test_database.py | 11 +++++ 4 files changed, 51 insertions(+), 29 deletions(-) diff --git a/bindings/python-cffi/notmuch2/_database.py b/bindings/python-cffi/notmuch2/_database.py index fc55fea8..3c06402d 100644 --- a/bindings/python-cffi/notmuch2/_database.py +++ b/bindings/python-cffi/notmuch2/_database.py @@ -400,7 +400,7 @@ class Database(base.NotmuchObject): capi.lib.NOTMUCH_STATUS_DUPLICATE_MESSAGE_ID] if ret not in ok: raise errors.NotmuchError(ret) - msg = message.StandaloneMessage(self, msg_pp[0], db=self) + msg = message.Message(self, msg_pp[0], db=self) if sync_flags: msg.tags.from_maildir_flags() return self.AddedMessage( @@ -469,7 +469,7 @@ class Database(base.NotmuchObject): msg_p = msg_pp[0] if msg_p == capi.ffi.NULL: raise LookupError - msg = message.StandaloneMessage(self, msg_p, db=self) + msg = message.Message(self, msg_p, db=self) return msg def get(self, filename): @@ -502,7 +502,7 @@ class Database(base.NotmuchObject): msg_p = msg_pp[0] if msg_p == capi.ffi.NULL: raise LookupError - msg = message.StandaloneMessage(self, msg_p, db=self) + msg = message.Message(self, msg_p, db=self) return msg @property diff --git a/bindings/python-cffi/notmuch2/_message.py b/bindings/python-cffi/notmuch2/_message.py index 416ce7ca..02de50ad 100644 --- a/bindings/python-cffi/notmuch2/_message.py +++ b/bindings/python-cffi/notmuch2/_message.py @@ -47,9 +47,7 @@ class Message(base.NotmuchObject): :type db: Database :param msg_p: The C pointer to the ``notmuch_message_t``. :type msg_p: - :param dup: Whether the message was a duplicate on insertion. - :type dup: None or bool """ _msg_p = base.MemoryPointer() @@ -61,10 +59,22 @@ class Message(base.NotmuchObject): @property def alive(self): - return self._parent.alive + if not self._parent.alive: + return False + try: + self._msg_p + except errors.ObjectDestroyedError: + return False + else: + return True + + def __del__(self): + self._destroy() def _destroy(self): - pass + if self.alive: + capi.lib.notmuch_message_destroy(self._msg_p) + self._msg_p = None @property def messageid(self): @@ -363,30 +373,26 @@ class Message(base.NotmuchObject): if isinstance(other, self.__class__): return self.messageid == other.messageid -class StandaloneMessage(Message): - """An email message stored in the notmuch database. - This subclass of Message is used for messages that are retrieved from the - database directly and are not owned by a query. +class OwnedMessage(Message): + """An email message owned by parent thread object. + + This subclass of Message is used for messages that are retrieved + from the notmuch database via a parent :class:`notmuch2.Thread` + object, which "owns" this message. This means that when this + message object is destroyed, by calling :func:`del` or + :meth:`_destroy` directly or indirectly, the message is not freed + in the notmuch API and the parent :class:`notmuch2.Thread` object + can return the same object again when needed. """ + @property def alive(self): - if not self._parent.alive: - return False - try: - self._msg_p - except errors.ObjectDestroyedError: - return False - else: - return True - - def __del__(self): - self._destroy() + return self._parent.alive def _destroy(self): - if self.alive: - capi.lib.notmuch_message_destroy(self._msg_p) - self._msg_p = None + pass + class FilenamesIter(base.NotmuchIter): """Iterator for binary filenames objects.""" @@ -690,8 +696,9 @@ collections.abc.ValuesView.register(PropertiesValuesView) class MessageIter(base.NotmuchIter): - def __init__(self, parent, msgs_p, *, db): + def __init__(self, parent, msgs_p, *, db, msg_cls=Message): self._db = db + self._msg_cls = msg_cls super().__init__(parent, msgs_p, fn_destroy=capi.lib.notmuch_messages_destroy, fn_valid=capi.lib.notmuch_messages_valid, @@ -700,4 +707,4 @@ class MessageIter(base.NotmuchIter): def __next__(self): msg_p = super().__next__() - return Message(self, msg_p, db=self._db) + return self._msg_cls(self, msg_p, db=self._db) diff --git a/bindings/python-cffi/notmuch2/_thread.py b/bindings/python-cffi/notmuch2/_thread.py index bb76f2dc..e883f308 100644 --- a/bindings/python-cffi/notmuch2/_thread.py +++ b/bindings/python-cffi/notmuch2/_thread.py @@ -62,7 +62,9 @@ class Thread(base.NotmuchObject, collections.abc.Iterable): :raises ObjectDestroyedError: if used after destroyed. """ msgs_p = capi.lib.notmuch_thread_get_toplevel_messages(self._thread_p) - return message.MessageIter(self, msgs_p, db=self._db) + return message.MessageIter(self, msgs_p, + db=self._db, + msg_cls=message.OwnedMessage) def __iter__(self): """Return an iterator over all the messages in the thread. @@ -72,7 +74,9 @@ class Thread(base.NotmuchObject, collections.abc.Iterable): :raises ObjectDestroyedError: if used after destroyed. """ msgs_p = capi.lib.notmuch_thread_get_messages(self._thread_p) - return message.MessageIter(self, msgs_p, db=self._db) + return message.MessageIter(self, msgs_p, + db=self._db, + msg_cls=message.OwnedMessage) @property def matched(self): diff --git a/bindings/python-cffi/tests/test_database.py b/bindings/python-cffi/tests/test_database.py index e3a8344d..55069b5e 100644 --- a/bindings/python-cffi/tests/test_database.py +++ b/bindings/python-cffi/tests/test_database.py @@ -324,3 +324,14 @@ class TestQuery: threads = db.threads('*') thread = next(threads) assert isinstance(thread, notmuch2.Thread) + + def test_use_threaded_message_twice(self, db): + thread = next(db.threads('*')) + for msg in thread.toplevel(): + assert isinstance(msg, notmuch2.Message) + assert msg.alive + del msg + for msg in thread: + assert isinstance(msg, notmuch2.Message) + assert msg.alive + del msg From 776a54a0e437651abd69a03b9f7d591ea1f992cb Mon Sep 17 00:00:00 2001 From: Floris Bruynooghe Date: Sun, 14 Jun 2020 17:23:19 +0200 Subject: [PATCH 193/427] Support aborting the atomic context Since it is possible to use an atomic context to abort a number of changes support this usage. Because the only way to actually abort the transaction is to close the database this must also do so. Amended by db: Note the limitation requiring close is a limitation of the underlying notmuch API, which should be fixed in a future notmuch release. --- bindings/python-cffi/notmuch2/_database.py | 16 +++++++++++++++- bindings/python-cffi/tests/test_database.py | 5 +++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/bindings/python-cffi/notmuch2/_database.py b/bindings/python-cffi/notmuch2/_database.py index 3c06402d..7db5a7f8 100644 --- a/bindings/python-cffi/notmuch2/_database.py +++ b/bindings/python-cffi/notmuch2/_database.py @@ -664,6 +664,7 @@ class AtomicContext: def __init__(self, db, ptr_name): self._db = db self._ptr = lambda: getattr(db, ptr_name) + self._exit_fn = lambda: None def __del__(self): self._destroy() @@ -679,13 +680,17 @@ class AtomicContext: ret = capi.lib.notmuch_database_begin_atomic(self._ptr()) if ret != capi.lib.NOTMUCH_STATUS_SUCCESS: raise errors.NotmuchError(ret) + self._exit_fn = self._end_atomic return self - def __exit__(self, exc_type, exc_value, traceback): + def _end_atomic(self): ret = capi.lib.notmuch_database_end_atomic(self._ptr()) if ret != capi.lib.NOTMUCH_STATUS_SUCCESS: raise errors.NotmuchError(ret) + def __exit__(self, exc_type, exc_value, traceback): + self._exit_fn() + def force_end(self): """Force ending the atomic section. @@ -704,6 +709,15 @@ class AtomicContext: if ret != capi.lib.NOTMUCH_STATUS_SUCCESS: raise errors.NotmuchError(ret) + def abort(self): + """Abort the transaction. + + Aborting a transaction will not commit any of the changes, but + will also implicitly close the database. + """ + self._exit_fn = lambda: None + self._db.close() + @functools.total_ordering class DbRevision: diff --git a/bindings/python-cffi/tests/test_database.py b/bindings/python-cffi/tests/test_database.py index 55069b5e..a2c69de6 100644 --- a/bindings/python-cffi/tests/test_database.py +++ b/bindings/python-cffi/tests/test_database.py @@ -127,6 +127,11 @@ class TestAtomic: with pytest.raises(errors.UnbalancedAtomicError): ctx.force_end() + def test_abort(self, db): + with db.atomic() as txn: + txn.abort() + assert db.closed + class TestRevision: From 5e163465935f42ed5d4c2af13cf8fc535ae5b729 Mon Sep 17 00:00:00 2001 From: David Bremner Date: Tue, 16 Jun 2020 08:26:55 -0300 Subject: [PATCH 194/427] update AUTHORS Yay, we gained a new author, thanks Anton. --- AUTHORS | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/AUTHORS b/AUTHORS index f8989f6e..83113f4c 100644 --- a/AUTHORS +++ b/AUTHORS @@ -33,9 +33,9 @@ Notmuch 0.30 release (calculated by devel/author-scan.sh). Mark Walters Floris Bruynooghe David Edmondson + Tomi Ollila Sebastian Spaeth Ali Polatel - Tomi Ollila Michal Sojka Justus Winter Sebastien Binet @@ -63,6 +63,7 @@ Notmuch 0.30 release (calculated by devel/author-scan.sh). Jonas Bernoulli Damien Cassou Vladimir Panteleev + Anton Khirnov Matt Armstrong Örjan Ekeberg Jan Janak From 6b3b0ae18602252d333dee77149363b5ffc67a13 Mon Sep 17 00:00:00 2001 From: David Bremner Date: Tue, 16 Jun 2020 08:28:08 -0300 Subject: [PATCH 195/427] debian/copyright: update to match AUTHORS --- debian/copyright | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/debian/copyright b/debian/copyright index 3c8eeea1..ba221e6b 100644 --- a/debian/copyright +++ b/debian/copyright @@ -13,9 +13,9 @@ Copyright: Copyright 2009-2020 Mark Walters Floris Bruynooghe David Edmondson + Tomi Ollila Sebastian Spaeth Ali Polatel - Tomi Ollila Michal Sojka Justus Winter Sebastien Binet @@ -43,6 +43,7 @@ Copyright: Copyright 2009-2020 Jonas Bernoulli Damien Cassou Vladimir Panteleev + Anton Khirnov Matt Armstrong Örjan Ekeberg Jan Janak From cda6e4d1046641871413710f3786c43697bd6d19 Mon Sep 17 00:00:00 2001 From: David Bremner Date: Tue, 16 Jun 2020 08:29:39 -0300 Subject: [PATCH 196/427] version: update to 0.30~rc2 --- bindings/python/notmuch/version.py | 2 +- version | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/bindings/python/notmuch/version.py b/bindings/python/notmuch/version.py index 1a5d0579..a5447027 100644 --- a/bindings/python/notmuch/version.py +++ b/bindings/python/notmuch/version.py @@ -1,3 +1,3 @@ # this file should be kept in sync with ../../../version -__VERSION__ = '0.30~rc1' +__VERSION__ = '0.30~rc2' SOVERSION = '5' diff --git a/version b/version index 81bdbf1d..71971d9b 100644 --- a/version +++ b/version @@ -1 +1 @@ -0.30~rc1 +0.30~rc2 From af51e67127e5817e3795973da9c5e2148b7fdeec Mon Sep 17 00:00:00 2001 From: David Bremner Date: Tue, 16 Jun 2020 08:32:51 -0300 Subject: [PATCH 197/427] debian: changelog stanza for 0.30~rc2-1 --- debian/changelog | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/debian/changelog b/debian/changelog index 704b54f6..a858b5db 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,11 @@ +notmuch (0.30~rc2-1) experimental; urgency=medium + + * New upstream release candidate. + * Upstream fixes for new python bindings (python3-notmuch2). + * Update debian/copyright (one new author). + + -- David Bremner Tue, 16 Jun 2020 08:32:16 -0300 + notmuch (0.30~rc1-1) experimental; urgency=medium * New upstream release candidate From 81057164cddf6a5c1d4c30a23767c4de8e615c1c Mon Sep 17 00:00:00 2001 From: Floris Bruynooghe Date: Fri, 19 Jun 2020 11:46:28 +0200 Subject: [PATCH 198/427] python-cffi: read version from notmuch version file This keeps it in sync with the main notmuch version which is less confusing to users. --- bindings/python-cffi/setup.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/bindings/python-cffi/setup.py b/bindings/python-cffi/setup.py index 37918e3d..1effcfc6 100644 --- a/bindings/python-cffi/setup.py +++ b/bindings/python-cffi/setup.py @@ -1,9 +1,17 @@ +import pathlib + import setuptools +THIS_FILE = pathlib.Path(__file__).absolute() +PROJECT_ROOT = THIS_FILE.parent.parent.parent +with open(PROJECT_ROOT.joinpath('version')) as fp: + VERSION = fp.read().strip() + + setuptools.setup( name='notmuch2', - version='0.1', + version=VERSION, description='Pythonic bindings for the notmuch mail database using CFFI', author='Floris Bruynooghe', author_email='flub@devork.be', From 751f6109225ccbe9a9008a2ff9381e4abfb4f71d Mon Sep 17 00:00:00 2001 From: Tomi Ollila Date: Wed, 24 Jun 2020 23:11:20 +0300 Subject: [PATCH 199/427] Makefile.global: drop -std=gnu99. C11 (or later) compiler required Since October 2018 building notmuch has actually required compiler that knows C11. Also this -std=gnu99 was not used in code compiled by configure, so in theory this could have caused problems... ...but no related reports have been sent, perhaps ever. Both gcc and clang has been shipping compilers supporting C11 (or later) by default for more than four years now. Therefore, just dropping -std=gnu99 (and not checking C11 compatibility for now, for simplicity) is easiest to do, and removes inconsistency between configure and build time compilations. --- Makefile.global | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Makefile.global b/Makefile.global index 0aee5876..98b6962e 100644 --- a/Makefile.global +++ b/Makefile.global @@ -49,8 +49,7 @@ DETACHED_SIG_FILE=$(TAR_FILE).asc PV_FILE=bindings/python/notmuch/version.py # Smash together user's values with our extra values -STD_CFLAGS := -std=gnu99 -FINAL_CFLAGS = -DNOTMUCH_VERSION=$(VERSION) $(CPPFLAGS) $(STD_CFLAGS) $(CFLAGS) $(WARN_CFLAGS) $(extra_cflags) $(CONFIGURE_CFLAGS) +FINAL_CFLAGS = -DNOTMUCH_VERSION=$(VERSION) $(CPPFLAGS) $(CFLAGS) $(WARN_CFLAGS) $(extra_cflags) $(CONFIGURE_CFLAGS) FINAL_CXXFLAGS = $(CPPFLAGS) $(CXXFLAGS) $(WARN_CXXFLAGS) $(extra_cflags) $(extra_cxxflags) $(CONFIGURE_CXXFLAGS) FINAL_NOTMUCH_LDFLAGS = $(LDFLAGS) -Lutil -lnotmuch_util -Llib -lnotmuch ifeq ($(LIBDIR_IN_LDCONFIG),0) From b96ccdf336243ccf5dd44bd26febf8c12fe17ad2 Mon Sep 17 00:00:00 2001 From: David Bremner Date: Wed, 24 Jun 2020 11:32:33 -0300 Subject: [PATCH 200/427] configure: detect 64 bit time_t Certain tests involving timestamps > 32 bits cannot pass with the current libnotmuch API. We will avoid this issue for now by disabling those tests on "old" architectures with 32 bit time_t. --- configure | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/configure b/configure index 05ade05b..2e01034b 100755 --- a/configure +++ b/configure @@ -1045,6 +1045,22 @@ else fi rm -f compat/have_timegm +cat < _time_t.c +#include +#include +static_assert(sizeof(time_t) >= 8, "sizeof(time_t) < 8"); +EOF + +printf "Checking for 64 bit time_t... " +if ${CC} -c _time_t.c -o /dev/null +then + printf "Yes.\n" + have_64bit_time_t=1 +else + printf "No.\n" + have_64bit_time_t=0 +fi + printf "Checking for dirent.d_type... " if ${CC} -o compat/have_d_type "$srcdir"/compat/have_d_type.c > /dev/null 2>&1 then @@ -1128,7 +1144,7 @@ for flag in -Wmissing-declarations; do done printf "\n\t%s\n" "${WARN_CFLAGS}" -rm -f minimal minimal.c _libversion.c _libversion _libversion.sh _check_session_keys.c _check_session_keys _check_x509_validity.c _check_x509_validity +rm -f minimal minimal.c _time_t.c _libversion.c _libversion _libversion.sh _check_session_keys.c _check_session_keys _check_x509_validity.c _check_x509_validity # construct the Makefile.config cat > Makefile.config < Date: Wed, 24 Jun 2020 11:32:34 -0300 Subject: [PATCH 201/427] test: mark two tests broken on machines with 32 bit time_t I haven't traced the code path as exhaustively for the SMIME test, but the expiry date in question is larger then representable in a signed 32 bit integer. --- test/T160-json.sh | 3 +++ test/T355-smime.sh | 3 +++ 2 files changed, 6 insertions(+) diff --git a/test/T160-json.sh b/test/T160-json.sh index d975efa7..e8b75605 100755 --- a/test/T160-json.sh +++ b/test/T160-json.sh @@ -65,6 +65,9 @@ test_expect_equal_json "$output" "[{\"thread\": \"XXX\", \"unread\"]}]" test_begin_subtest "Search message: json, 64-bit timestamp" +if [ $NOTMUCH_HAVE_64BIT_TIME_T -ne 1 ]; then + test_subtest_known_broken +fi add_message "[subject]=\"json-search-64bit-timestamp-subject\"" "[date]=\"Tue, 01 Jan 2999 12:00:00 -0000\"" "[body]=\"json-search-64bit-timestamp-message\"" output=$(notmuch search --format=json "json-search-64bit-timestamp-message" | notmuch_search_sanitize) test_expect_equal_json "$output" "[{\"thread\": \"XXX\", diff --git a/test/T355-smime.sh b/test/T355-smime.sh index 170f8649..f8cec62c 100755 --- a/test/T355-smime.sh +++ b/test/T355-smime.sh @@ -176,6 +176,9 @@ output=$(notmuch show --format=json id:smime-onepart-signed@protected-headers.ex test_valid_json "$output" test_begin_subtest "Verify signature on PKCS#7 SignedData message" +if [ $NOTMUCH_HAVE_64BIT_TIME_T -ne 1 ]; then + test_subtest_known_broken +fi output=$(notmuch show --format=json id:smime-onepart-signed@protected-headers.example) test_json_nodes <<<"$output" \ From e624cc132af462641b19db3762a58a5776047890 Mon Sep 17 00:00:00 2001 From: Daniel Kahn Gillmor Date: Thu, 2 Jul 2020 14:00:48 -0400 Subject: [PATCH 202/427] configure: can gpgme can verify signatures when decrypting with a session key? If https://dev.gnupg.org/T3464 is unresolved in the version of gpgme we are testing against, then we should know about it, because it affects the behavior of notmuch. Signed-off-by: Daniel Kahn Gillmor --- configure | 78 ++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 77 insertions(+), 1 deletion(-) diff --git a/configure b/configure index 2e01034b..2caa08c8 100755 --- a/configure +++ b/configure @@ -620,6 +620,78 @@ EOF if [ -n "$TEMP_GPG" -a -d "$TEMP_GPG" ]; then rm -rf "$TEMP_GPG" fi + + # see https://dev.gnupg.org/T3464 + # there are problems verifying signatures when decrypting with session keys with GPGME 1.13.0 and 1.13.1 + printf "Checking signature verification when decrypting using session keys... " + + cat > _verify_sig_with_session_key.c < +#include + +int main () { + GError *error = NULL; + GMimeParser *parser = NULL; + GMimeMultipartEncrypted *body = NULL; + GMimeDecryptResult *result = NULL; + GMimeSignatureList *sig_list = NULL; + GMimeSignature *sig = NULL; + GMimeObject *output = NULL; + GMimeSignatureStatus status; + int len; + + g_mime_init (); + parser = g_mime_parser_new (); + g_mime_parser_init_with_stream (parser, g_mime_stream_file_open("$srcdir/test/corpora/crypto/encrypted-signed.eml", "r", &error)); + if (error) return !! fprintf (stderr, "failed to instantiate parser with test/corpora/pkcs7/smime-onepart-signed.eml\n"); + + body = GMIME_MULTIPART_ENCRYPTED(g_mime_message_get_mime_part (g_mime_parser_construct_message (parser, NULL))); + if (body == NULL) return !! fprintf (stderr, "did not find a multipart/encrypted message\n"); + + output = g_mime_multipart_encrypted_decrypt (body, GMIME_DECRYPT_NONE, "9:13607E4217515A70EC8DF9DBC16C5327B94577561D98AD1246FA8756659C7899", &result, &error); + if (error || output == NULL) return !! fprintf (stderr, "decrypt failed\n"); + + sig_list = g_mime_decrypt_result_get_signatures (result); + if (sig_list == NULL) return !! fprintf (stderr, "sig_list is NULL\n"); + + if (sig_list == NULL) return !! fprintf (stderr, "no GMimeSignatureList found\n"); + len = g_mime_signature_list_length (sig_list); + if (len != 1) return !! fprintf (stderr, "expected 1 signature, got %d\n", len); + sig = g_mime_signature_list_get_signature (sig_list, 0); + if (sig == NULL) return !! fprintf (stderr, "no GMimeSignature found at position 0\n"); + status = g_mime_signature_get_status (sig); + if (status & GMIME_SIGNATURE_STATUS_KEY_MISSING) return !! fprintf (stderr, "signature status contains KEY_MISSING (see https://dev.gnupg.org/T3464)\n"); + + return 0; +} +EOF + if ! TEMP_GPG=$(mktemp -d "${TMPDIR:-/tmp}/notmuch.XXXXXX"); then + printf 'No.\nCould not make tempdir for testing signature verification when decrypting with session keys.\n' + errors=$((errors + 1)) + elif ${CC} ${CFLAGS} ${gmime_cflags} _verify_sig_with_session_key.c ${gmime_ldflags} -o _verify_sig_with_session_key \ + && GNUPGHOME=${TEMP_GPG} gpg --batch --quiet --import < "$srcdir"/test/gnupg-secret-key.asc \ + && rm -f ${TEMP_GPG}/private-keys-v1.d/*.key + then + if GNUPGHOME=${TEMP_GPG} ./_verify_sig_with_session_key; then + gmime_verify_with_session_key=1 + printf "Yes.\n" + else + gmime_verify_with_session_key=0 + printf "No.\n" + cat < Makefile.config < Date: Thu, 2 Jul 2020 14:00:49 -0400 Subject: [PATCH 203/427] tests: mark sig verification known-broken with session keys on buggy gpgme We make use of the just-introduced configure test. Signed-off-by: Daniel Kahn Gillmor --- test/T357-index-decryption.sh | 3 +++ 1 file changed, 3 insertions(+) diff --git a/test/T357-index-decryption.sh b/test/T357-index-decryption.sh index 1ac2836a..1ed5f28c 100755 --- a/test/T357-index-decryption.sh +++ b/test/T357-index-decryption.sh @@ -306,6 +306,9 @@ test_json_nodes <<<"$output" "$goodsig" test_begin_subtest "verify signature with stashed session key" output=$(notmuch show --format=json id:encrypted-signed@crypto.notmuchmail.org) +if [ $NOTMUCH_GMIME_VERIFY_WITH_SESSION_KEY -ne 1 ]; then + test_subtest_known_broken +fi test_json_nodes <<<"$output" "$goodsig" # TODO: test removal of a message from the message store between From f981f5bae0e33219b7e0e9f2d5e03d54d0d88cce Mon Sep 17 00:00:00 2001 From: David Bremner Date: Fri, 26 Jun 2020 08:11:00 -0300 Subject: [PATCH 204/427] doc: update install suggestions for fedora derivatives Fedora still has an old gmime-devel which is 2.6.x. This is no longer supported by notmuch. Also apparently dnf is a better choice than yum. --- INSTALL | 2 +- configure | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/INSTALL b/INSTALL index f1236e71..40ea377a 100644 --- a/INSTALL +++ b/INSTALL @@ -95,7 +95,7 @@ dependencies with a single simple command line. For example: For Fedora and similar: - sudo yum install xapian-core-devel gmime-devel libtalloc-devel zlib-devel python3-sphinx texinfo info + sudo dnf install xapian-core-devel gmime30-devel libtalloc-devel zlib-devel python3-sphinx texinfo info On other systems, a similar command can be used, but the details of the package names may be different. diff --git a/configure b/configure index 2caa08c8..80cbac4f 100755 --- a/configure +++ b/configure @@ -1028,7 +1028,7 @@ On Debian and similar systems: Or on Fedora and similar systems: - sudo yum install xapian-core-devel gmime-devel libtalloc-devel zlib-devel + sudo dnf install xapian-core-devel gmime30-devel libtalloc-devel zlib-devel On other systems, similar commands can be used, but the details of the package names may be different. @@ -1043,7 +1043,7 @@ to install pkg-config with a command such as: sudo apt-get install pkg-config Or: - sudo yum install pkgconfig + sudo dnf install pkgconfig But if pkg-config is not available for your system, then you will need to modify the configure script to manually set the cflags and ldflags From 3a42abb456893b71b530f099a1467400f2b0ea71 Mon Sep 17 00:00:00 2001 From: David Bremner Date: Mon, 29 Jun 2020 21:22:47 -0300 Subject: [PATCH 205/427] bindings/python-cffi: copy version file into bindings dir Attempt to avoid breaking "pip install ." As far as I can tell, we need to have a copy (not just a relative symlink) of the version file. --- Makefile.local | 1 + bindings/python-cffi/setup.py | 8 +------- bindings/python-cffi/version | 1 + 3 files changed, 3 insertions(+), 7 deletions(-) create mode 100644 bindings/python-cffi/version diff --git a/Makefile.local b/Makefile.local index 586cdf75..314238ef 100644 --- a/Makefile.local +++ b/Makefile.local @@ -54,6 +54,7 @@ update-versions: sed -i -e "s/^__VERSION__[[:blank:]]*=.*$$/__VERSION__ = \'${VERSION}\'/" \ -e "s/^SOVERSION[[:blank:]]*=.*$$/SOVERSION = \'${LIBNOTMUCH_VERSION_MAJOR}\'/" \ ${PV_FILE} + cp version bindings/python-cffi # We invoke make recursively only to force ordering of our phony # targets in the case of parallel invocation of make (-j). diff --git a/bindings/python-cffi/setup.py b/bindings/python-cffi/setup.py index 1effcfc6..b0060835 100644 --- a/bindings/python-cffi/setup.py +++ b/bindings/python-cffi/setup.py @@ -1,14 +1,8 @@ -import pathlib - import setuptools - -THIS_FILE = pathlib.Path(__file__).absolute() -PROJECT_ROOT = THIS_FILE.parent.parent.parent -with open(PROJECT_ROOT.joinpath('version')) as fp: +with open('version') as fp: VERSION = fp.read().strip() - setuptools.setup( name='notmuch2', version=VERSION, diff --git a/bindings/python-cffi/version b/bindings/python-cffi/version new file mode 100644 index 00000000..71971d9b --- /dev/null +++ b/bindings/python-cffi/version @@ -0,0 +1 @@ +0.30~rc2 From 038b3e7c302fed34b8734d3dcac2c5568d4147f3 Mon Sep 17 00:00:00 2001 From: David Bremner Date: Fri, 3 Jul 2020 06:45:17 -0300 Subject: [PATCH 206/427] version: bump to 0.30~rc3 --- bindings/python-cffi/version | 2 +- bindings/python/notmuch/version.py | 2 +- version | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/bindings/python-cffi/version b/bindings/python-cffi/version index 71971d9b..db90aec2 100644 --- a/bindings/python-cffi/version +++ b/bindings/python-cffi/version @@ -1 +1 @@ -0.30~rc2 +0.30~rc3 diff --git a/bindings/python/notmuch/version.py b/bindings/python/notmuch/version.py index a5447027..a25b1d7d 100644 --- a/bindings/python/notmuch/version.py +++ b/bindings/python/notmuch/version.py @@ -1,3 +1,3 @@ # this file should be kept in sync with ../../../version -__VERSION__ = '0.30~rc2' +__VERSION__ = '0.30~rc3' SOVERSION = '5' diff --git a/version b/version index 71971d9b..db90aec2 100644 --- a/version +++ b/version @@ -1 +1 @@ -0.30~rc2 +0.30~rc3 From 4539d68e3bd1ade7a4e47600223a1e76bd99f3fa Mon Sep 17 00:00:00 2001 From: David Bremner Date: Fri, 3 Jul 2020 06:50:16 -0300 Subject: [PATCH 207/427] debian: changelog fo 0.30~rc3-1 --- debian/changelog | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/debian/changelog b/debian/changelog index a858b5db..db853fa0 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,11 @@ +notmuch (0.30~rc3-1) experimental; urgency=medium + + * New upstream release candidate + * Mark two tests broken on legacy (32 bit time_t) architectures. + * Drop -std=c99 + + -- David Bremner Fri, 03 Jul 2020 06:48:51 -0300 + notmuch (0.30~rc2-1) experimental; urgency=medium * New upstream release candidate. From e9f8ffd882aba446ee27a9c32bb1e1475512d3f7 Mon Sep 17 00:00:00 2001 From: David Bremner Date: Fri, 3 Jul 2020 07:23:32 -0300 Subject: [PATCH 208/427] test: fix python 3.8.4 related regression in T210-raw It seems (at least in 3.8.4~rc1-1 on Debian) that set_content requires at least one line. --- test/T210-raw.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/T210-raw.sh b/test/T210-raw.sh index 85e707d4..9a8b990c 100755 --- a/test/T210-raw.sh +++ b/test/T210-raw.sh @@ -40,7 +40,7 @@ for pow in range(10,21): msg['To'] = msg['From'] msg['Message-Id'] = 'size-{:07d}@notmuch-test-suite'.format(size) content = "" - msg.set_content("") + msg.set_content("\n") padding = size - len(bytes(msg)) lines = [] while padding > 0: From 552029f7482ea76806f18b8ef2cbe9c8706b39f4 Mon Sep 17 00:00:00 2001 From: David Bremner Date: Tue, 16 Jun 2020 11:17:55 -0300 Subject: [PATCH 209/427] build: produce signatures for release candidate tarballs. Today Defalos on #notmuch asked for a signed tarball for 0.30~rc2. This is a minimal change to support this in the future. The question of automagically uploading will need more thought; currently I like the fact that tags from pre-releases are only pushed manually. --- Makefile.local | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile.local b/Makefile.local index 314238ef..22577617 100644 --- a/Makefile.local +++ b/Makefile.local @@ -89,7 +89,7 @@ pre-release: $(MAKE) VERSION=$(VERSION) test git tag -s -m "$(PACKAGE) $(VERSION) release" $(UPSTREAM_TAG) git tag -s -m "$(PACKAGE) Debian $(VERSION)-1 upload (same as $(VERSION))" $(DEB_TAG) - $(MAKE) VERSION=$(VERSION) $(TAR_FILE) + $(MAKE) VERSION=$(VERSION) $(SHA256_FILE) $(DETACHED_SIG_FILE) ln -sf $(TAR_FILE) $(DEB_TAR_FILE) pristine-tar commit $(DEB_TAR_FILE) $(UPSTREAM_TAG) mkdir -p releases From 2c17327ee5c428e3d52a188b0433a130b4684438 Mon Sep 17 00:00:00 2001 From: David Bremner Date: Mon, 29 Jun 2020 22:14:08 -0300 Subject: [PATCH 210/427] test: add known broken test for error handling on closed database Based on id:87d05je1j6.fsf@powell.devork.be --- test/T560-lib-error.sh | 41 +++++++++++++++++++++++++++++++++++++++++ test/notmuch-test.h | 1 + 2 files changed, 42 insertions(+) diff --git a/test/T560-lib-error.sh b/test/T560-lib-error.sh index 06a6b860..7284a92b 100755 --- a/test/T560-lib-error.sh +++ b/test/T560-lib-error.sh @@ -318,4 +318,45 @@ EOF test_expect_equal_file EXPECTED OUTPUT.clean restore_database +cat < c_head2 +#include +#include +#include +#include +int main (int argc, char** argv) +{ + notmuch_database_t *db; + notmuch_status_t stat; + char *msg = NULL; + notmuch_message_t *message = NULL; + const char *id = "1258471718-6781-1-git-send-email-dottedmag@dottedmag.net"; + + stat = notmuch_database_open_verbose (argv[1], NOTMUCH_DATABASE_MODE_READ_WRITE, &db, &msg); + if (stat != NOTMUCH_STATUS_SUCCESS) { + fprintf (stderr, "error opening database: %d %s\n", stat, msg ? msg : ""); + exit (1); + } + EXPECT0(notmuch_database_find_message (db, id, &message)); + assert(message != NULL); + EXPECT0(notmuch_database_close (db)); +EOF + +backup_database +test_begin_subtest "Handle getting message-id from closed database" +test_subtest_known_broken +cat c_head2 - c_tail <<'EOF' | test_C ${MAIL_DIR} + { + const char *id2; + id2=notmuch_message_get_message_id (message); + printf("%s\n%d\n", id, id2==NULL); + } +EOF +cat < EXPECTED +== stdout == +1258471718-6781-1-git-send-email-dottedmag@dottedmag.net +1 +== stderr == +EOF +test_expect_equal_file EXPECTED OUTPUT + test_done diff --git a/test/notmuch-test.h b/test/notmuch-test.h index df852da9..34dbb8e0 100644 --- a/test/notmuch-test.h +++ b/test/notmuch-test.h @@ -1,6 +1,7 @@ #ifndef _NOTMUCH_TEST_H #define _NOTMUCH_TEST_H #include +#include #include inline static void From 87d462a20423a25eaf4b54a90bfd538dd93da675 Mon Sep 17 00:00:00 2001 From: David Bremner Date: Mon, 29 Jun 2020 22:14:09 -0300 Subject: [PATCH 211/427] lib: catch error from closed db in n_m_get_message_id By catching it at the library top level, we can return an error value. --- lib/message.cc | 23 +++++++++++++++++++---- lib/notmuch.h | 5 ++--- test/T560-lib-error.sh | 1 - 3 files changed, 21 insertions(+), 8 deletions(-) diff --git a/lib/message.cc b/lib/message.cc index 0fa0eb3a..b7a64b1c 100644 --- a/lib/message.cc +++ b/lib/message.cc @@ -90,6 +90,18 @@ _notmuch_message_destructor (notmuch_message_t *message) return 0; } +#define LOG_XAPIAN_EXCEPTION(message, error) _log_xapian_exception (__location__, message, error) + +static void +_log_xapian_exception (const char *where, notmuch_message_t *message, const Xapian::Error error) { + notmuch_database_t *notmuch = notmuch_message_get_database (message); + _notmuch_database_log (notmuch, + "A Xapian exception occurred %s retrieving %s : %s\n", + where, + error.get_msg ().c_str ()); + notmuch->exception_reported = true; +} + static notmuch_message_t * _notmuch_message_create_for_document (const void *talloc_owner, notmuch_database_t *notmuch, @@ -447,9 +459,6 @@ _notmuch_message_ensure_metadata (notmuch_message_t *message, void *field) if (status != NOTMUCH_STATUS_SUCCESS) INTERNAL_ERROR ("unhandled error from notmuch_database_reopen: %s\n", notmuch_status_to_string (status)); - } catch (const Xapian::Error &error) { - INTERNAL_ERROR ("A Xapian exception occurred fetching message metadata: %s\n", - error.get_msg ().c_str ()); } } message->last_view = message->notmuch->view; @@ -507,7 +516,13 @@ _notmuch_message_get_doc_id (notmuch_message_t *message) const char * notmuch_message_get_message_id (notmuch_message_t *message) { - _notmuch_message_ensure_metadata (message, message->message_id); + try { + _notmuch_message_ensure_metadata (message, message->message_id); + } catch (const Xapian::Error &error) { + LOG_XAPIAN_EXCEPTION (message, error); + return NULL; + } + if (! message->message_id) INTERNAL_ERROR ("Message with document ID of %u has no message ID.\n", message->doc_id); diff --git a/lib/notmuch.h b/lib/notmuch.h index ceb5a018..0dc89547 100644 --- a/lib/notmuch.h +++ b/lib/notmuch.h @@ -1363,9 +1363,8 @@ notmuch_message_get_database (const notmuch_message_t *message); * message is valid, (which is until the query from which it derived * is destroyed). * - * This function will not return NULL since Notmuch ensures that every - * message has a unique message ID, (Notmuch will generate an ID for a - * message if the original file does not contain one). + * This function will return NULL if triggers an unhandled Xapian + * exception. */ const char * notmuch_message_get_message_id (notmuch_message_t *message); diff --git a/test/T560-lib-error.sh b/test/T560-lib-error.sh index 7284a92b..5a5f66b8 100755 --- a/test/T560-lib-error.sh +++ b/test/T560-lib-error.sh @@ -343,7 +343,6 @@ EOF backup_database test_begin_subtest "Handle getting message-id from closed database" -test_subtest_known_broken cat c_head2 - c_tail <<'EOF' | test_C ${MAIL_DIR} { const char *id2; From bb51f3aa29af43fe52e4545b83f8af80ae4e3712 Mon Sep 17 00:00:00 2001 From: David Bremner Date: Mon, 29 Jun 2020 22:14:10 -0300 Subject: [PATCH 212/427] test: add known broken test for n_m_get_thread_id on closed db This will be fixed in the next commit. --- test/T560-lib-error.sh | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/test/T560-lib-error.sh b/test/T560-lib-error.sh index 5a5f66b8..b5600851 100755 --- a/test/T560-lib-error.sh +++ b/test/T560-lib-error.sh @@ -358,4 +358,22 @@ cat < EXPECTED EOF test_expect_equal_file EXPECTED OUTPUT +backup_database +test_begin_subtest "Handle getting thread-id from closed database" +test_subtest_known_broken +cat c_head2 - c_tail <<'EOF' | test_C ${MAIL_DIR} + { + const char *id2; + id2=notmuch_message_get_thread_id (message); + printf("%s\n%d\n", id, id2==NULL); + } +EOF +cat < EXPECTED +== stdout == +1258471718-6781-1-git-send-email-dottedmag@dottedmag.net +1 +== stderr == +EOF +test_expect_equal_file EXPECTED OUTPUT + test_done From dbdb860bb92b5eef0eadc6ffd1fd6d5bf64553b9 Mon Sep 17 00:00:00 2001 From: David Bremner Date: Mon, 29 Jun 2020 22:14:11 -0300 Subject: [PATCH 213/427] lib/message: catch exception in n_m_get_thread_id This allows us to return an error value from the library. --- lib/message.cc | 7 ++++++- test/T560-lib-error.sh | 1 - 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/lib/message.cc b/lib/message.cc index b7a64b1c..3ca7b902 100644 --- a/lib/message.cc +++ b/lib/message.cc @@ -604,7 +604,12 @@ _notmuch_message_get_in_reply_to (notmuch_message_t *message) const char * notmuch_message_get_thread_id (notmuch_message_t *message) { - _notmuch_message_ensure_metadata (message, message->thread_id); + try { + _notmuch_message_ensure_metadata (message, message->thread_id); + } catch (Xapian::Error &error) { + LOG_XAPIAN_EXCEPTION (message, error); + return NULL; + } if (! message->thread_id) INTERNAL_ERROR ("Message with document ID of %u has no thread ID.\n", message->doc_id); diff --git a/test/T560-lib-error.sh b/test/T560-lib-error.sh index b5600851..81500536 100755 --- a/test/T560-lib-error.sh +++ b/test/T560-lib-error.sh @@ -360,7 +360,6 @@ test_expect_equal_file EXPECTED OUTPUT backup_database test_begin_subtest "Handle getting thread-id from closed database" -test_subtest_known_broken cat c_head2 - c_tail <<'EOF' | test_C ${MAIL_DIR} { const char *id2; From e56a207ce14386b3e826dd267c549427b2c3f9ec Mon Sep 17 00:00:00 2001 From: David Bremner Date: Fri, 10 Jul 2020 22:21:19 -0300 Subject: [PATCH 214/427] version: set to 0.30 --- bindings/python-cffi/version | 2 +- bindings/python/notmuch/version.py | 2 +- version | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/bindings/python-cffi/version b/bindings/python-cffi/version index db90aec2..f7c6c31b 100644 --- a/bindings/python-cffi/version +++ b/bindings/python-cffi/version @@ -1 +1 @@ -0.30~rc3 +0.30 diff --git a/bindings/python/notmuch/version.py b/bindings/python/notmuch/version.py index a25b1d7d..36b4cfca 100644 --- a/bindings/python/notmuch/version.py +++ b/bindings/python/notmuch/version.py @@ -1,3 +1,3 @@ # this file should be kept in sync with ../../../version -__VERSION__ = '0.30~rc3' +__VERSION__ = '0.30' SOVERSION = '5' diff --git a/version b/version index db90aec2..f7c6c31b 100644 --- a/version +++ b/version @@ -1 +1 @@ -0.30~rc3 +0.30 From fbc341f7122029bcd35d451792002edbfd0db54b Mon Sep 17 00:00:00 2001 From: David Bremner Date: Fri, 10 Jul 2020 22:24:41 -0300 Subject: [PATCH 215/427] debian: changelog for 0.30 --- debian/changelog | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/debian/changelog b/debian/changelog index db853fa0..b6117ec3 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,12 @@ +notmuch (0.30-1) unstable; urgency=medium + + * New upstream release + * Improvements to S/MIME handling + * Repairs to some mangled MIME messages + * New python bindings (notmuch2) compatible with current python 3 + + -- David Bremner Fri, 10 Jul 2020 22:24:14 -0300 + notmuch (0.30~rc3-1) experimental; urgency=medium * New upstream release candidate From b502e26563010503e77da7188c24db4cee592185 Mon Sep 17 00:00:00 2001 From: David Bremner Date: Fri, 10 Jul 2020 22:25:04 -0300 Subject: [PATCH 216/427] NEWS: update date for release --- NEWS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NEWS b/NEWS index 556172bb..0d8b930f 100644 --- a/NEWS +++ b/NEWS @@ -1,4 +1,4 @@ -Notmuch 0.30 (2020-06-01) +Notmuch 0.30 (2020-07-10) ========================= S/MIME From a962842d9b0b43b7d218860b196eecd5ef666088 Mon Sep 17 00:00:00 2001 From: Jonas Witschel Date: Sat, 11 Jul 2020 18:04:35 +0200 Subject: [PATCH 217/427] doc: make gzipped man pages reproducible gzip includes the name of the uncompressed file and its modification timestamp into the compressed archive. The latter makes it hard to reproduce the generated files bit for bit at a later time, so omit this information from the archive using the "--no-name" option. This is a reproducibility best practice, see https://wiki.debian.org/ReproducibleBuilds/TimestampsInGzipHeaders --- doc/Makefile.local | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/Makefile.local b/doc/Makefile.local index 769438ed..19b953ed 100644 --- a/doc/Makefile.local +++ b/doc/Makefile.local @@ -40,7 +40,7 @@ INFO_INFO_FILES := $(INFO_TEXI_FILES:.texi=.info) .PHONY: install-man build-man apidocs install-apidocs %.gz: % - rm -f $@ && gzip --stdout $^ > $@ + rm -f $@ && gzip --no-name --stdout $^ > $@ ifeq ($(WITH_EMACS),1) $(DOCBUILDDIR)/.roff.stamp sphinx-html sphinx-texinfo: docstring.stamp From a1b7cc834bf559184113c837f7511125db18a1aa Mon Sep 17 00:00:00 2001 From: David Bremner Date: Tue, 7 Jul 2020 07:56:45 -0300 Subject: [PATCH 218/427] lib: migrate to post Xapian 1.3.4 compact support The old API was deprecated in Xapian 1.3.4 and (will be) removed in 1.5.0 --- lib/database.cc | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/lib/database.cc b/lib/database.cc index 78b5fec9..43bfac4e 100644 --- a/lib/database.cc +++ b/lib/database.cc @@ -1281,11 +1281,7 @@ notmuch_database_compact (const char *path, try { NotmuchCompactor compactor (status_cb, closure); - - compactor.set_renumber (false); - compactor.add_source (xapian_path); - compactor.set_destdir (compact_xapian_path); - compactor.compact (); + notmuch->xapian_db->compact (compact_xapian_path, Xapian::DBCOMPACT_NO_RENUMBER, 0, compactor); } catch (const Xapian::Error &error) { _notmuch_database_log (notmuch, "Error while compacting: %s\n", error.get_msg ().c_str ()); ret = NOTMUCH_STATUS_XAPIAN_EXCEPTION; From b90d852a2fb377d5b7db4ab2ac389964f8c0c3b3 Mon Sep 17 00:00:00 2001 From: David Bremner Date: Tue, 7 Jul 2020 07:56:46 -0300 Subject: [PATCH 219/427] lib: migrate from Xapian ValueRangeProcessor to RangeProcessor This will be mandatory as of Xapian 1.5. The API is also more consistent with the FieldProcessor API, which helps code re-use a bit. Note that this switches to using the built-in Xapian support for prefixes on ranges (i.e. deleted code at beginning of ParseTimeRangeProcessor::operator(), added prefix to constructor). Another side effect of the migration is that we are generating smaller queries, using one OP_VALUE_RANGE instead of an AND of two OP_VALUE_* queries. --- lib/database-private.h | 6 ++-- lib/database.cc | 21 ++++++------- lib/parse-time-vrp.cc | 69 +++++++++++++++++++++--------------------- lib/parse-time-vrp.h | 18 +++++------ 4 files changed, 56 insertions(+), 58 deletions(-) diff --git a/lib/database-private.h b/lib/database-private.h index 87ae1bdf..76359007 100644 --- a/lib/database-private.h +++ b/lib/database-private.h @@ -218,9 +218,9 @@ struct _notmuch_database { unsigned long view; Xapian::QueryParser *query_parser; Xapian::TermGenerator *term_gen; - Xapian::ValueRangeProcessor *value_range_processor; - Xapian::ValueRangeProcessor *date_range_processor; - Xapian::ValueRangeProcessor *last_mod_range_processor; + Xapian::RangeProcessor *value_range_processor; + Xapian::RangeProcessor *date_range_processor; + Xapian::RangeProcessor *last_mod_range_processor; /* XXX it's slightly gross to use two parallel string->string maps * here, but at least they are small */ diff --git a/lib/database.cc b/lib/database.cc index 43bfac4e..2f794164 100644 --- a/lib/database.cc +++ b/lib/database.cc @@ -385,8 +385,8 @@ _setup_query_field (const prefix_t *prefix, notmuch_database_t *notmuch) Xapian::FieldProcessor *fp; if (STRNCMP_LITERAL (prefix->name, "date") == 0) - fp = (new DateFieldProcessor ())->release (); - else if (STRNCMP_LITERAL (prefix->name, "query") == 0) + fp = (new DateFieldProcessor(NOTMUCH_VALUE_TIMESTAMP))->release (); + else if (STRNCMP_LITERAL(prefix->name, "query") == 0) fp = (new QueryFieldProcessor (*notmuch->query_parser, notmuch))->release (); else if (STRNCMP_LITERAL (prefix->name, "thread") == 0) fp = (new ThreadFieldProcessor (*notmuch->query_parser, notmuch))->release (); @@ -1036,17 +1036,16 @@ notmuch_database_open_verbose (const char *path, notmuch->query_parser = new Xapian::QueryParser; notmuch->term_gen = new Xapian::TermGenerator; notmuch->term_gen->set_stemmer (Xapian::Stem ("english")); - notmuch->value_range_processor = new Xapian::NumberValueRangeProcessor (NOTMUCH_VALUE_TIMESTAMP); - notmuch->date_range_processor = new ParseTimeValueRangeProcessor (NOTMUCH_VALUE_TIMESTAMP); - notmuch->last_mod_range_processor = new Xapian::NumberValueRangeProcessor (NOTMUCH_VALUE_LAST_MOD, "lastmod:"); - + notmuch->value_range_processor = new Xapian::NumberRangeProcessor (NOTMUCH_VALUE_TIMESTAMP); + notmuch->date_range_processor = new ParseTimeRangeProcessor (NOTMUCH_VALUE_TIMESTAMP, "date:"); + notmuch->last_mod_range_processor = new Xapian::NumberRangeProcessor (NOTMUCH_VALUE_LAST_MOD, "lastmod:"); notmuch->query_parser->set_default_op (Xapian::Query::OP_AND); notmuch->query_parser->set_database (*notmuch->xapian_db); notmuch->query_parser->set_stemmer (Xapian::Stem ("english")); notmuch->query_parser->set_stemming_strategy (Xapian::QueryParser::STEM_SOME); - notmuch->query_parser->add_valuerangeprocessor (notmuch->value_range_processor); - notmuch->query_parser->add_valuerangeprocessor (notmuch->date_range_processor); - notmuch->query_parser->add_valuerangeprocessor (notmuch->last_mod_range_processor); + notmuch->query_parser->add_rangeprocessor (notmuch->value_range_processor); + notmuch->query_parser->add_rangeprocessor (notmuch->date_range_processor); + notmuch->query_parser->add_rangeprocessor (notmuch->last_mod_range_processor); for (i = 0; i < ARRAY_SIZE (prefix_table); i++) { const prefix_t *prefix = &prefix_table[i]; @@ -1401,8 +1400,8 @@ handle_sigalrm (unused (int signal)) */ notmuch_status_t notmuch_database_upgrade (notmuch_database_t *notmuch, - void (*progress_notify)(void *closure, - double progress), + void (*progress_notify) (void *closure, + double progress), void *closure) { void *local = talloc_new (NULL); diff --git a/lib/parse-time-vrp.cc b/lib/parse-time-vrp.cc index 168d5810..10809aa3 100644 --- a/lib/parse-time-vrp.cc +++ b/lib/parse-time-vrp.cc @@ -24,64 +24,63 @@ #include "parse-time-vrp.h" #include "parse-time-string.h" -#define PREFIX "date:" - -/* See *ValueRangeProcessor in xapian-core/api/valuerangeproc.cc */ -Xapian::valueno -ParseTimeValueRangeProcessor::operator() (std::string &begin, std::string &end) +Xapian::Query +ParseTimeRangeProcessor::operator() (const std::string &begin, const std::string &end) { - time_t t, now; - std::string b; - - /* Require date: prefix in start of the range... */ - if (STRNCMP_LITERAL (begin.c_str (), PREFIX)) - return Xapian::BAD_VALUENO; - - /* ...and remove it. */ - begin.erase (0, sizeof (PREFIX) - 1); - b = begin; + double from = DBL_MIN, to = DBL_MAX; + time_t parsed_time, now; + std::string str; /* Use the same 'now' for begin and end. */ if (time (&now) == (time_t) -1) - return Xapian::BAD_VALUENO; + throw Xapian::QueryParserError ("unable to get current time"); - if (! begin.empty ()) { - if (parse_time_string (begin.c_str (), &t, &now, PARSE_TIME_ROUND_DOWN)) - return Xapian::BAD_VALUENO; - - begin.assign (Xapian::sortable_serialise ((double) t)); + if (!begin.empty ()) { + if (parse_time_string (begin.c_str (), &parsed_time, &now, PARSE_TIME_ROUND_DOWN)) + throw Xapian::QueryParserError ("Didn't understand date specification '" + begin + "'"); + else + from = (double) parsed_time; } - if (! end.empty ()) { - if (end == "!" && ! b.empty ()) - end = b; + if (!end.empty ()) { + if (end == "!" && ! begin.empty ()) + str = begin; + else + str = end; - if (parse_time_string (end.c_str (), &t, &now, PARSE_TIME_ROUND_UP_INCLUSIVE)) - return Xapian::BAD_VALUENO; - - end.assign (Xapian::sortable_serialise ((double) t)); + if (parse_time_string (str.c_str (), &parsed_time, &now, PARSE_TIME_ROUND_UP_INCLUSIVE)) + throw Xapian::QueryParserError ("Didn't understand date specification '" + str + "'"); + else + to = (double) parsed_time; } - return valno; + return Xapian::Query (Xapian::Query::OP_VALUE_RANGE, slot, + Xapian::sortable_serialise (from), + Xapian::sortable_serialise (to)); } /* XXX TODO: is throwing an exception the right thing to do here? */ Xapian::Query DateFieldProcessor::operator() (const std::string & str) { - time_t from, to, now; + double from = DBL_MIN, to = DBL_MAX; + time_t parsed_time, now; /* Use the same 'now' for begin and end. */ if (time (&now) == (time_t) -1) throw Xapian::QueryParserError ("Unable to get current time"); - if (parse_time_string (str.c_str (), &from, &now, PARSE_TIME_ROUND_DOWN)) + if (parse_time_string (str.c_str (), &parsed_time, &now, PARSE_TIME_ROUND_DOWN)) throw Xapian::QueryParserError ("Didn't understand date specification '" + str + "'"); + else + from = (double) parsed_time; - if (parse_time_string (str.c_str (), &to, &now, PARSE_TIME_ROUND_UP_INCLUSIVE)) + if (parse_time_string (str.c_str (), &parsed_time, &now, PARSE_TIME_ROUND_UP_INCLUSIVE)) throw Xapian::QueryParserError ("Didn't understand date specification '" + str + "'"); + else + to = (double) parsed_time; - return Xapian::Query (Xapian::Query::OP_AND, - Xapian::Query (Xapian::Query::OP_VALUE_GE, 0, Xapian::sortable_serialise ((double) from)), - Xapian::Query (Xapian::Query::OP_VALUE_LE, 0, Xapian::sortable_serialise ((double) to))); + return Xapian::Query (Xapian::Query::OP_VALUE_RANGE, slot, + Xapian::sortable_serialise (from), + Xapian::sortable_serialise (to)); } diff --git a/lib/parse-time-vrp.h b/lib/parse-time-vrp.h index 9fb1af60..f495e716 100644 --- a/lib/parse-time-vrp.h +++ b/lib/parse-time-vrp.h @@ -26,21 +26,21 @@ #include /* see *ValueRangeProcessor in xapian-core/include/xapian/queryparser.h */ -class ParseTimeValueRangeProcessor : public Xapian::ValueRangeProcessor { -protected: - Xapian::valueno valno; +class ParseTimeRangeProcessor : public Xapian::RangeProcessor { public: - ParseTimeValueRangeProcessor (Xapian::valueno slot_) - : valno (slot_) - { - } + ParseTimeRangeProcessor (Xapian::valueno slot_, const std::string prefix_) + : Xapian::RangeProcessor(slot_, prefix_, 0) { } - Xapian::valueno operator() (std::string &begin, std::string &end); + Xapian::Query operator() (const std::string &begin, const std::string &end); }; class DateFieldProcessor : public Xapian::FieldProcessor { - Xapian::Query operator() (const std::string & str); +private: + Xapian::valueno slot; +public: + DateFieldProcessor(Xapian::valueno slot_) : slot(slot_) { }; + Xapian::Query operator()(const std::string & str); }; #endif /* NOTMUCH_PARSE_TIME_VRP_H */ From ee897cab8b7210ae0c9098f278e38ee355c2ef3d Mon Sep 17 00:00:00 2001 From: David Bremner Date: Tue, 7 Jul 2020 07:56:47 -0300 Subject: [PATCH 220/427] test: drop upgrade from v1 tests These are less crucial since we stopped generating new database versions and relied primarily on features. They also rely on a pre-generated v1 database which happens to be chert format. This backend is not supported by Xapian 1.5. Also drop the tool gen-testdb.sh, which is currently broken, due to changes in the testing infrastructure. --- .travis.yml | 1 - configure | 2 +- devel/gen-testdb.sh | 131 ----------------- test/T530-upgrade.sh | 136 ------------------ test/test-databases/.gitignore | 1 - test/test-databases/Makefile | 7 - test/test-databases/Makefile.local | 20 --- test/test-databases/database-v1.tar.xz.sha256 | 1 - 8 files changed, 1 insertion(+), 298 deletions(-) delete mode 100755 devel/gen-testdb.sh delete mode 100755 test/T530-upgrade.sh delete mode 100644 test/test-databases/.gitignore delete mode 100644 test/test-databases/Makefile delete mode 100644 test/test-databases/Makefile.local delete mode 100644 test/test-databases/database-v1.tar.xz.sha256 diff --git a/.travis.yml b/.travis.yml index 9dc03619..9dcec1ff 100644 --- a/.travis.yml +++ b/.travis.yml @@ -20,7 +20,6 @@ addons: script: - ./configure - - make download-test-databases - make test notifications: diff --git a/configure b/configure index 80cbac4f..4e16ff40 100755 --- a/configure +++ b/configure @@ -48,7 +48,7 @@ case $PWD in ( *["$IFS"]* ) esac subdirs="util compat lib parse-time-string completion doc emacs" -subdirs="${subdirs} performance-test test test/test-databases" +subdirs="${subdirs} performance-test test" subdirs="${subdirs} bindings" # For a non-srcdir configure invocation (such as ../configure), create diff --git a/devel/gen-testdb.sh b/devel/gen-testdb.sh deleted file mode 100755 index 61ae48a3..00000000 --- a/devel/gen-testdb.sh +++ /dev/null @@ -1,131 +0,0 @@ -#!/usr/bin/env bash -# -# NAME -# gen-testdb.sh - generate test databases -# -# SYNOPSIS -# gen-testdb.sh -v NOTMUCH-VERSION [-c CORPUS-PATH] [-s TAR-SUFFIX] -# -# DESCRIPTION -# Generate a tarball containing the specified test corpus and -# the corresponding notmuch database, indexed using a specific -# version of notmuch, resulting in a specific version of the -# database. -# -# The specific version of notmuch will be built on the fly. -# Therefore the script must be run within a git repository to be -# able to build the old versions of notmuch. -# -# This script reuses the test infrastructure, and the script -# must be run from within the test directory. -# -# The output tarballs, named database-.tar.gz, are -# placed in the test/test-databases directory. -# -# OPTIONS -# -v NOTMUCH-VERSION -# Notmuch version in terms of a git tag or commit to use -# for generating the database. Required. -# -# -c CORPUS-PATH -# Path to a corpus to use for generating the -# database. Due to CWD changes within the test -# infrastructure, use absolute paths. Defaults to the -# test corpus. -# -# -s TAR-SUFFIX -# Suffix for the tarball basename. Empty by default. -# -# EXAMPLE -# -# Generate a database indexed with notmuch 0.17. Use the default -# test corpus. Name the tarball database-v1.tar.gz to reflect -# the fact that notmuch 0.17 used database version 1. -# -# $ cd test -# $ ../devel/gen-testdb.sh -v 0.17 -s v1 -# -# CAVEATS -# Test infrastructure options won't work. -# -# Any existing databases with the same name will be overwritten. -# -# It may not be possible to build old versions of notmuch with -# the set of dependencies that satisfy building the current -# version of notmuch. -# -# AUTHOR -# Jani Nikula -# -# LICENSE -# Same as notmuch test infrastructure (GPLv2+). -# - -test_description="database generation abusing test infrastructure" - -# immediate exit on subtest failure; see test_failure_ in test-lib.sh -immediate=t - -VERSION= -CORPUS= -SUFFIX= - -while getopts v:c:s: opt; do - case "$opt" in - v) VERSION="$OPTARG";; - c) CORPUS="$OPTARG";; - s) SUFFIX="-$OPTARG";; - esac -done -shift `expr $OPTIND - 1` - -. ./test-lib.sh || exit 1 - -SHORT_CORPUS=$(basename ${CORPUS:-database}) -DBNAME=${SHORT_CORPUS}${SUFFIX} -TARBALLNAME=${DBNAME}.tar.xz - -CORPUS=${CORPUS:-${TEST_DIRECTORY}/corpus} - -test_expect_code 0 "notmuch version specified on the command line" \ - "test -n ${VERSION}" - -test_expect_code 0 "the specified version ${VERSION} refers to a commit" \ - "git show ${VERSION} >/dev/null 2>&1" - -BUILD_DIR="notmuch-${VERSION}" -test_expect_code 0 "generate snapshot of notmuch version ${VERSION}" \ - "git -C $TEST_DIRECTORY/.. archive --prefix=${BUILD_DIR}/ --format=tar ${VERSION} | tar x" - -# force version string -git describe --match '[0-9.]*' ${VERSION} > ${BUILD_DIR}/version - -test_expect_code 0 "configure and build notmuch version ${VERSION}" \ - "make -C ${BUILD_DIR}" - -# use the newly built notmuch -export PATH=./${BUILD_DIR}:$PATH - -test_begin_subtest "verify the newly built notmuch version" -test_expect_equal "`notmuch --version`" "notmuch `cat ${BUILD_DIR}/version`" - -# replace the existing mails, if any, with the specified corpus -rm -rf ${MAIL_DIR} -cp -a ${CORPUS} ${MAIL_DIR} - -test_expect_code 0 "index the corpus" \ - "notmuch new" - -# wrap the resulting mail store and database in a tarball - -cp -a ${MAIL_DIR} ${TMP_DIRECTORY}/${DBNAME} -tar Jcf ${TMP_DIRECTORY}/${TARBALLNAME} -C ${TMP_DIRECTORY} ${DBNAME} -mkdir -p ${TEST_DIRECTORY}/test-databases -cp -a ${TMP_DIRECTORY}/${TARBALLNAME} ${TEST_DIRECTORY}/test-databases -test_expect_code 0 "create the output tarball ${TARBALLNAME}" \ - "test -f ${TEST_DIRECTORY}/test-databases/${TARBALLNAME}" - -# generate a checksum file -test_expect_code 0 "compute checksum" \ - "(cd ${TEST_DIRECTORY}/test-databases/ && sha256sum ${TARBALLNAME} > ${TARBALLNAME}.sha256)" -test_done diff --git a/test/T530-upgrade.sh b/test/T530-upgrade.sh deleted file mode 100755 index 2124dde2..00000000 --- a/test/T530-upgrade.sh +++ /dev/null @@ -1,136 +0,0 @@ -#!/usr/bin/env bash -test_description="database upgrade" - -. $(dirname "$0")/test-lib.sh || exit 1 - -dbtarball=database-v1.tar.xz - -# XXX: Accomplish the same with test lib helpers -if [ ! -e ${TEST_DIRECTORY}/test-databases/${dbtarball} ]; then - test_subtest_missing_external_prereq_["${dbtarball} - fetch with 'make download-test-databases'"]=t -fi - -test_begin_subtest "database checksum" -test_expect_success \ - '( cd $TEST_DIRECTORY/test-databases && - sha256sum --quiet --check --status ${dbtarball}.sha256 )' - -tar xf $TEST_DIRECTORY/test-databases/${dbtarball} -C ${MAIL_DIR} --strip-components=1 - -test_begin_subtest "folder: search does not work with old database version" -output=$(notmuch search folder:foo) -test_expect_equal "$output" "" - -test_begin_subtest "path: search does not work with old database version" -output=$(notmuch search path:foo) -test_expect_equal "$output" "" - -test_begin_subtest "pre upgrade dump" -test_expect_success 'notmuch dump | sort > pre-upgrade-dump' - -test_begin_subtest "database upgrade from format version 1" -output=$(notmuch new | sed -e 's/^Backing up tags to .*$/Backing up tags to FILENAME/') -test_expect_equal "$output" "\ -Welcome to a new version of notmuch! Your database will now be upgraded. -This process is safe to interrupt. -Backing up tags to FILENAME -Your notmuch database has now been upgraded. -No new mail." - -test_begin_subtest "tag backup matches pre-upgrade dump" -gunzip -c ${MAIL_DIR}/.notmuch/dump-*.gz | sort > backup-dump -test_expect_equal_file pre-upgrade-dump backup-dump - -test_begin_subtest "folder: no longer matches in the middle of path" -output=$(notmuch search folder:baz) -test_expect_equal "$output" "" - -test_begin_subtest "folder: search" -output=$(notmuch search --output=files folder:foo | notmuch_search_files_sanitize | sort) -test_expect_equal "$output" "MAIL_DIR/foo/06:2, -MAIL_DIR/foo/cur/07:2, -MAIL_DIR/foo/cur/08:2, -MAIL_DIR/foo/new/03:2, -MAIL_DIR/foo/new/09:2, -MAIL_DIR/foo/new/10:2," - -test_begin_subtest "top level folder: search" -output=$(notmuch search --output=files folder:'""' | notmuch_search_files_sanitize | sort) -# bar/18:2, is a duplicate of cur/51:2, -test_expect_equal "$output" "MAIL_DIR/01:2, -MAIL_DIR/02:2, -MAIL_DIR/bar/18:2, -MAIL_DIR/cur/29:2, -MAIL_DIR/cur/30:2, -MAIL_DIR/cur/31:2, -MAIL_DIR/cur/32:2, -MAIL_DIR/cur/33:2, -MAIL_DIR/cur/34:2, -MAIL_DIR/cur/35:2, -MAIL_DIR/cur/36:2, -MAIL_DIR/cur/37:2, -MAIL_DIR/cur/38:2, -MAIL_DIR/cur/39:2, -MAIL_DIR/cur/40:2, -MAIL_DIR/cur/41:2, -MAIL_DIR/cur/42:2, -MAIL_DIR/cur/43:2, -MAIL_DIR/cur/44:2, -MAIL_DIR/cur/45:2, -MAIL_DIR/cur/46:2, -MAIL_DIR/cur/47:2, -MAIL_DIR/cur/48:2, -MAIL_DIR/cur/49:2, -MAIL_DIR/cur/50:2, -MAIL_DIR/cur/51:2, -MAIL_DIR/cur/52:2, -MAIL_DIR/cur/53:2, -MAIL_DIR/new/04:2," - -test_begin_subtest "path: search" -output=$(notmuch search --output=files path:"bar" | notmuch_search_files_sanitize | sort) -# cur/51:2, is a duplicate of bar/18:2, -test_expect_equal "$output" "MAIL_DIR/bar/17:2, -MAIL_DIR/bar/18:2, -MAIL_DIR/cur/51:2," - -test_begin_subtest "top level path: search" -output=$(notmuch search --output=files path:'""' | notmuch_search_files_sanitize | sort) -test_expect_equal "$output" "MAIL_DIR/01:2, -MAIL_DIR/02:2," - -test_begin_subtest "recursive path: search" -output=$(notmuch search --output=files path:"bar/**" | notmuch_search_files_sanitize | sort) -# cur/51:2, is a duplicate of bar/18:2, -test_expect_equal "$output" "MAIL_DIR/bar/17:2, -MAIL_DIR/bar/18:2, -MAIL_DIR/bar/baz/05:2, -MAIL_DIR/bar/baz/23:2, -MAIL_DIR/bar/baz/24:2, -MAIL_DIR/bar/baz/cur/25:2, -MAIL_DIR/bar/baz/cur/26:2, -MAIL_DIR/bar/baz/new/27:2, -MAIL_DIR/bar/baz/new/28:2, -MAIL_DIR/bar/cur/19:2, -MAIL_DIR/bar/cur/20:2, -MAIL_DIR/bar/new/21:2, -MAIL_DIR/bar/new/22:2, -MAIL_DIR/cur/51:2," - -test_begin_subtest "body: same as unprefixed before reindex" -notmuch search --output=messages body:close > OUTPUT -notmuch search --output=messages close > EXPECTED -test_expect_equal_file EXPECTED OUTPUT - -test_begin_subtest "body: subset of unprefixed after reindex" -notmuch reindex '*' -notmuch search --output=messages body:close | sort > BODY -notmuch search --output=messages close | sort > UNPREFIXED -diff -e UNPREFIXED BODY | cut -c2- > OUTPUT -cat < EXPECTED -d -d -EOF -test_expect_equal_file EXPECTED OUTPUT - -test_done diff --git a/test/test-databases/.gitignore b/test/test-databases/.gitignore deleted file mode 100644 index 9452199f..00000000 --- a/test/test-databases/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/*.tar.xz diff --git a/test/test-databases/Makefile b/test/test-databases/Makefile deleted file mode 100644 index b250a8be..00000000 --- a/test/test-databases/Makefile +++ /dev/null @@ -1,7 +0,0 @@ -# See Makefile.local for the list of files to be compiled in this -# directory. -all: - $(MAKE) -C ../.. all - -.DEFAULT: - $(MAKE) -C ../.. $@ diff --git a/test/test-databases/Makefile.local b/test/test-databases/Makefile.local deleted file mode 100644 index 7aedff70..00000000 --- a/test/test-databases/Makefile.local +++ /dev/null @@ -1,20 +0,0 @@ -# -*- makefile -*- - -TEST_DATABASE_MIRROR=https://notmuchmail.org/releases/test-databases - -dir := test/test-databases - -test_databases := $(dir)/database-v1.tar.xz - -%.tar.xz: - @exec 1>&2 ;\ - if command -v wget >/dev/null ;\ - then set -x; wget -nv -O $@ ${TEST_DATABASE_MIRROR}/$(notdir $@) ;\ - elif command -v curl >/dev/null ;\ - then set -x; curl -L -s -o $@ ${TEST_DATABASE_MIRROR}/$(notdir $@) ;\ - else echo Cannot fetch databases, no wget nor curl available; exit 1 ;\ - fi - -download-test-databases: ${test_databases} - -DATACLEAN := $(DATACLEAN) ${test_databases} diff --git a/test/test-databases/database-v1.tar.xz.sha256 b/test/test-databases/database-v1.tar.xz.sha256 deleted file mode 100644 index 2cc4f965..00000000 --- a/test/test-databases/database-v1.tar.xz.sha256 +++ /dev/null @@ -1 +0,0 @@ -4299e051b10e1fa7b33ea2862790a09ebfe96859681804e5251e130f800e69d2 database-v1.tar.xz From 30e43c7c276133bd9ab6fbbb5e26fda8653b6a07 Mon Sep 17 00:00:00 2001 From: David Bremner Date: Sat, 11 Jul 2020 19:52:21 -0300 Subject: [PATCH 221/427] debian: drop ValueRangeProcessor symbols These are not used since b90d852 --- debian/libnotmuch5.symbols | 2 -- 1 file changed, 2 deletions(-) diff --git a/debian/libnotmuch5.symbols b/debian/libnotmuch5.symbols index 2ae73dad..59c4fde5 100644 --- a/debian/libnotmuch5.symbols +++ b/debian/libnotmuch5.symbols @@ -129,8 +129,6 @@ libnotmuch.so.5 libnotmuch5 #MINVER# (c++)"typeinfo for Xapian::DatabaseError@Base" 0.24~rc0 (c++)"typeinfo for Xapian::DatabaseModifiedError@Base" 0.24~rc0 (c++|optional=present with Xapian 1.4)"typeinfo for Xapian::QueryParserError@Base" 0.23~rc0 - (c++)"typeinfo for Xapian::QueryParser::add_valuerangeprocessor(Xapian::ValueRangeProcessor*)::ShimRangeProcessor@Base" 0.25~rc0 - (c++)"typeinfo name for Xapian::QueryParser::add_valuerangeprocessor(Xapian::ValueRangeProcessor*)::ShimRangeProcessor@Base" 0.25~rc0 (c++)"typeinfo name for Xapian::LogicError@Base" 0.6.1 (c++)"typeinfo name for Xapian::RuntimeError@Base" 0.6.1 (c++)"typeinfo name for Xapian::DocNotFoundError@Base" 0.6.1 From a280d2a1a18183c870f6332da751b2c566cbef16 Mon Sep 17 00:00:00 2001 From: David Bremner Date: Sat, 4 Jul 2020 08:12:21 -0300 Subject: [PATCH 222/427] test: remove unused backup_database calls Since these backups are never restored, they should be safe to remove. --- test/T560-lib-error.sh | 2 -- 1 file changed, 2 deletions(-) diff --git a/test/T560-lib-error.sh b/test/T560-lib-error.sh index 81500536..5af3eab2 100755 --- a/test/T560-lib-error.sh +++ b/test/T560-lib-error.sh @@ -341,7 +341,6 @@ int main (int argc, char** argv) EXPECT0(notmuch_database_close (db)); EOF -backup_database test_begin_subtest "Handle getting message-id from closed database" cat c_head2 - c_tail <<'EOF' | test_C ${MAIL_DIR} { @@ -358,7 +357,6 @@ cat < EXPECTED EOF test_expect_equal_file EXPECTED OUTPUT -backup_database test_begin_subtest "Handle getting thread-id from closed database" cat c_head2 - c_tail <<'EOF' | test_C ${MAIL_DIR} { From cca551a7366bfe29e5c97cea53c2d50fbffc27dd Mon Sep 17 00:00:00 2001 From: David Bremner Date: Sat, 4 Jul 2020 09:29:41 -0300 Subject: [PATCH 223/427] test: drop use of assert in closed db tests Instead of printing the same static string for each test, can replace the assert with something simpler (or at least easier to integrate into the test suite). --- test/T560-lib-error.sh | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/test/T560-lib-error.sh b/test/T560-lib-error.sh index 5af3eab2..6432faaa 100755 --- a/test/T560-lib-error.sh +++ b/test/T560-lib-error.sh @@ -322,7 +322,6 @@ cat < c_head2 #include #include #include -#include int main (int argc, char** argv) { notmuch_database_t *db; @@ -337,7 +336,6 @@ int main (int argc, char** argv) exit (1); } EXPECT0(notmuch_database_find_message (db, id, &message)); - assert(message != NULL); EXPECT0(notmuch_database_close (db)); EOF @@ -346,12 +344,12 @@ cat c_head2 - c_tail <<'EOF' | test_C ${MAIL_DIR} { const char *id2; id2=notmuch_message_get_message_id (message); - printf("%s\n%d\n", id, id2==NULL); + printf("%d\n%d\n", message != NULL, id2==NULL); } EOF cat < EXPECTED == stdout == -1258471718-6781-1-git-send-email-dottedmag@dottedmag.net +1 1 == stderr == EOF @@ -362,12 +360,12 @@ cat c_head2 - c_tail <<'EOF' | test_C ${MAIL_DIR} { const char *id2; id2=notmuch_message_get_thread_id (message); - printf("%s\n%d\n", id, id2==NULL); + printf("%d\n%d\n", message != NULL, id2==NULL); } EOF cat < EXPECTED == stdout == -1258471718-6781-1-git-send-email-dottedmag@dottedmag.net +1 1 == stderr == EOF From 9201c502047816f2a22346fb02c971c50e5a7fcf Mon Sep 17 00:00:00 2001 From: David Bremner Date: Sat, 4 Jul 2020 08:33:41 -0300 Subject: [PATCH 224/427] lib/message: use LOG_XAPIAN_EXCEPTION in n_m_get_header This is just for consistency, and a small reduction in the amount of boilerplate. --- lib/message.cc | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/lib/message.cc b/lib/message.cc index 3ca7b902..0551a427 100644 --- a/lib/message.cc +++ b/lib/message.cc @@ -572,9 +572,7 @@ notmuch_message_get_header (notmuch_message_t *message, const char *header) return talloc_strdup (message, value.c_str ()); } catch (Xapian::Error &error) { - _notmuch_database_log (notmuch_message_get_database (message), "A Xapian exception occurred when reading header: %s\n", - error.get_msg ().c_str ()); - message->notmuch->exception_reported = true; + LOG_XAPIAN_EXCEPTION (message, error); return NULL; } } From 056794a60d4f7c1f1d88d239e21f761f91cf03ce Mon Sep 17 00:00:00 2001 From: David Bremner Date: Sat, 4 Jul 2020 08:35:08 -0300 Subject: [PATCH 225/427] test: add regression test for n_m_get_header This function already catches Xapian exceptions, and we want to make sure it stays that way. --- test/T560-lib-error.sh | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/test/T560-lib-error.sh b/test/T560-lib-error.sh index 6432faaa..777eb375 100755 --- a/test/T560-lib-error.sh +++ b/test/T560-lib-error.sh @@ -371,4 +371,20 @@ cat < EXPECTED EOF test_expect_equal_file EXPECTED OUTPUT +test_begin_subtest "Handle getting header from closed database" +cat c_head2 - c_tail <<'EOF' | test_C ${MAIL_DIR} + { + const char *from; + from=notmuch_message_get_header (message, "from"); + printf("%s\n%d\n", id, from == NULL); + } +EOF +cat < EXPECTED +== stdout == +1258471718-6781-1-git-send-email-dottedmag@dottedmag.net +1 +== stderr == +EOF +test_expect_equal_file EXPECTED OUTPUT + test_done From a962bd2bf801b80e2bdfaa8b05797bf01bc3fc1f Mon Sep 17 00:00:00 2001 From: David Bremner Date: Sat, 4 Jul 2020 09:09:09 -0300 Subject: [PATCH 226/427] lib/n_m_get_replies: doc return, initial regression test We need to to set a query and retrieve the threads to meaningfully test this function. --- lib/notmuch.h | 5 ++++- test/T560-lib-error.sh | 18 ++++++++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/lib/notmuch.h b/lib/notmuch.h index 0dc89547..037913f4 100644 --- a/lib/notmuch.h +++ b/lib/notmuch.h @@ -1402,7 +1402,10 @@ notmuch_message_get_thread_id (notmuch_message_t *message); * NULL. (Note that notmuch_messages_valid will accept that NULL * value as legitimate, and simply return FALSE for it.) * - * The returned list will be destroyed when the thread is destroyed. + * This function also returns NULL if it triggers a Xapian exception. + * + * The returned list will be destroyed when the thread is + * destroyed. */ notmuch_messages_t * notmuch_message_get_replies (notmuch_message_t *message); diff --git a/test/T560-lib-error.sh b/test/T560-lib-error.sh index 777eb375..d7caed5a 100755 --- a/test/T560-lib-error.sh +++ b/test/T560-lib-error.sh @@ -387,4 +387,22 @@ cat < EXPECTED EOF test_expect_equal_file EXPECTED OUTPUT +# XXX TODO: test on a message from notmuch_thread_get_toplevel_messages +# XXX this test only tests the trivial code path +test_begin_subtest "Handle getting replies from closed database" +cat c_head2 - c_tail <<'EOF' | test_C ${MAIL_DIR} + { + notmuch_messages_t *replies; + replies = notmuch_message_get_replies (message); + printf("%d\n%d\n", message != NULL, replies==NULL); + } +EOF +cat < EXPECTED +== stdout == +1 +1 +== stderr == +EOF +test_expect_equal_file EXPECTED OUTPUT + test_done From 46e8076281073c8df5ae8e7e5b386981e2ef4c62 Mon Sep 17 00:00:00 2001 From: David Bremner Date: Sat, 4 Jul 2020 09:16:30 -0300 Subject: [PATCH 227/427] lib: add known broken test for notmuch_message_get_filename This will be fixed in the next commit --- test/T560-lib-error.sh | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/test/T560-lib-error.sh b/test/T560-lib-error.sh index d7caed5a..7555197f 100755 --- a/test/T560-lib-error.sh +++ b/test/T560-lib-error.sh @@ -405,4 +405,21 @@ cat < EXPECTED EOF test_expect_equal_file EXPECTED OUTPUT +test_begin_subtest "Handle getting message filename from closed database" +test_subtest_known_broken +cat c_head2 - c_tail <<'EOF' | test_C ${MAIL_DIR} + { + const char *filename; + filename = notmuch_message_get_filename (message); + printf("%d\n%d\n", message != NULL, filename == NULL); + } +EOF +cat < EXPECTED +== stdout == +1 +1 +== stderr == +EOF +test_expect_equal_file EXPECTED OUTPUT + test_done From a606cba32b017f947c2d9f32477df69645c1c383 Mon Sep 17 00:00:00 2001 From: David Bremner Date: Sat, 4 Jul 2020 09:20:31 -0300 Subject: [PATCH 228/427] lib/n_m_g_filename: catch Xapian exceptions, document NULL return This is the same machinery as applied for notmuch_message_get_{thread,message}_id --- lib/message.cc | 7 ++++++- lib/notmuch.h | 2 ++ test/T560-lib-error.sh | 1 - 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/lib/message.cc b/lib/message.cc index 0551a427..1a9eaffe 100644 --- a/lib/message.cc +++ b/lib/message.cc @@ -1122,7 +1122,12 @@ _notmuch_message_ensure_filename_list (notmuch_message_t *message) const char * notmuch_message_get_filename (notmuch_message_t *message) { - _notmuch_message_ensure_filename_list (message); + try { + _notmuch_message_ensure_filename_list (message); + } catch (Xapian::Error &error) { + LOG_XAPIAN_EXCEPTION (message, error); + return NULL; + } if (message->filename_list == NULL) return NULL; diff --git a/lib/notmuch.h b/lib/notmuch.h index 037913f4..5c17ec7c 100644 --- a/lib/notmuch.h +++ b/lib/notmuch.h @@ -1433,6 +1433,8 @@ notmuch_message_count_files (notmuch_message_t *message); * this function will arbitrarily return a single one of those * filenames. See notmuch_message_get_filenames for returning the * complete list of filenames. + * + * This function returns NULL if it triggers a Xapian exception. */ const char * notmuch_message_get_filename (notmuch_message_t *message); diff --git a/test/T560-lib-error.sh b/test/T560-lib-error.sh index 7555197f..57c74a2b 100755 --- a/test/T560-lib-error.sh +++ b/test/T560-lib-error.sh @@ -406,7 +406,6 @@ EOF test_expect_equal_file EXPECTED OUTPUT test_begin_subtest "Handle getting message filename from closed database" -test_subtest_known_broken cat c_head2 - c_tail <<'EOF' | test_C ${MAIL_DIR} { const char *filename; From 2c2ba7d6e2bbdcb055901a97704778d1d823e7d4 Mon Sep 17 00:00:00 2001 From: David Bremner Date: Sat, 4 Jul 2020 11:06:37 -0300 Subject: [PATCH 229/427] test: add known broken test for n_m_get_filenames This will be fixed in the next commit --- test/T560-lib-error.sh | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/test/T560-lib-error.sh b/test/T560-lib-error.sh index 57c74a2b..8d9b4cc0 100755 --- a/test/T560-lib-error.sh +++ b/test/T560-lib-error.sh @@ -421,4 +421,21 @@ cat < EXPECTED EOF test_expect_equal_file EXPECTED OUTPUT +test_begin_subtest "Handle getting all message filenames from closed database" +test_subtest_known_broken +cat c_head2 - c_tail <<'EOF' | test_C ${MAIL_DIR} + { + notmuch_filenames_t *filenames; + filenames = notmuch_message_get_filenames (message); + printf("%d\n%d\n", message != NULL, filenames == NULL); + } +EOF +cat < EXPECTED +== stdout == +1 +1 +== stderr == +EOF +test_expect_equal_file EXPECTED OUTPUT + test_done From 286161b703e0b5be4d54323970ff88f25f65e0eb Mon Sep 17 00:00:00 2001 From: David Bremner Date: Sat, 4 Jul 2020 11:09:09 -0300 Subject: [PATCH 230/427] lib: catch exceptions in n_m_get_filenames This is essentially copied from the change to notmuch_message_get_filename --- lib/message.cc | 7 ++++++- lib/notmuch.h | 2 ++ test/T560-lib-error.sh | 1 - 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/lib/message.cc b/lib/message.cc index 1a9eaffe..8e43a393 100644 --- a/lib/message.cc +++ b/lib/message.cc @@ -1143,7 +1143,12 @@ notmuch_message_get_filename (notmuch_message_t *message) notmuch_filenames_t * notmuch_message_get_filenames (notmuch_message_t *message) { - _notmuch_message_ensure_filename_list (message); + try { + _notmuch_message_ensure_filename_list (message); + } catch (Xapian::Error &error) { + LOG_XAPIAN_EXCEPTION (message, error); + return NULL; + } return _notmuch_filenames_create (message, message->filename_list); } diff --git a/lib/notmuch.h b/lib/notmuch.h index 5c17ec7c..e544aafd 100644 --- a/lib/notmuch.h +++ b/lib/notmuch.h @@ -1448,6 +1448,8 @@ notmuch_message_get_filename (notmuch_message_t *message); * * Each filename in the iterator is an absolute filename, (the initial * component will match notmuch_database_get_path() ). + * + * This function returns NULL if it triggers a Xapian exception. */ notmuch_filenames_t * notmuch_message_get_filenames (notmuch_message_t *message); diff --git a/test/T560-lib-error.sh b/test/T560-lib-error.sh index 8d9b4cc0..ce3526a4 100755 --- a/test/T560-lib-error.sh +++ b/test/T560-lib-error.sh @@ -422,7 +422,6 @@ EOF test_expect_equal_file EXPECTED OUTPUT test_begin_subtest "Handle getting all message filenames from closed database" -test_subtest_known_broken cat c_head2 - c_tail <<'EOF' | test_C ${MAIL_DIR} { notmuch_filenames_t *filenames; From ef15946defec52182c59d00c23fe6167fbca8855 Mon Sep 17 00:00:00 2001 From: David Bremner Date: Sat, 4 Jul 2020 11:22:25 -0300 Subject: [PATCH 231/427] test: add known broken for n_m_get_flag on closed db Exception caught in next commit. Note that FLAG_GHOST is the only one that triggers the I/O code path. --- test/T560-lib-error.sh | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/test/T560-lib-error.sh b/test/T560-lib-error.sh index ce3526a4..68aadcdb 100755 --- a/test/T560-lib-error.sh +++ b/test/T560-lib-error.sh @@ -437,4 +437,22 @@ cat < EXPECTED EOF test_expect_equal_file EXPECTED OUTPUT +test_begin_subtest "Handle getting ghost flag from closed database" +test_subtest_known_broken +cat c_head2 - c_tail <<'EOF' | test_C ${MAIL_DIR} + { + notmuch_bool_t result; + result = notmuch_message_get_flag (message, NOTMUCH_MESSAGE_FLAG_GHOST); + printf("%d\n%d\n", message != NULL, result == FALSE); + } +EOF +cat < EXPECTED +== stdout == +1 +1 +== stderr == +EOF +test_expect_equal_file EXPECTED OUTPUT + + test_done From 6eaadb43adae2cca2f8023830d498ef3164f7fe9 Mon Sep 17 00:00:00 2001 From: David Bremner Date: Sun, 5 Jul 2020 10:00:16 -0300 Subject: [PATCH 232/427] lib: add regression test for n_m_get_date; clarify API This function catches Xapian exceptions. The test is intended to make sure it stays that way. --- lib/notmuch.h | 2 ++ test/T560-lib-error.sh | 16 ++++++++++++++++ 2 files changed, 18 insertions(+) diff --git a/lib/notmuch.h b/lib/notmuch.h index e544aafd..50d804fa 100644 --- a/lib/notmuch.h +++ b/lib/notmuch.h @@ -1503,6 +1503,8 @@ notmuch_message_set_flag (notmuch_message_t *message, * For the original textual representation of the Date header from the * message call notmuch_message_get_header() with a header value of * "date". + * + * Returns 0 in case of error. */ time_t notmuch_message_get_date (notmuch_message_t *message); diff --git a/test/T560-lib-error.sh b/test/T560-lib-error.sh index 68aadcdb..c043e35e 100755 --- a/test/T560-lib-error.sh +++ b/test/T560-lib-error.sh @@ -454,5 +454,21 @@ cat < EXPECTED EOF test_expect_equal_file EXPECTED OUTPUT +test_begin_subtest "Handle getting date from closed database" +cat c_head2 - c_tail <<'EOF' | test_C ${MAIL_DIR} + { + time_t result; + result = notmuch_message_get_date (message); + printf("%d\n%d\n", message != NULL, result == 0); + } +EOF +cat < EXPECTED +== stdout == +1 +1 +== stderr == +EOF +test_expect_equal_file EXPECTED OUTPUT + test_done From e404d8a51d81a7dff85e28aeb64acdca5483652f Mon Sep 17 00:00:00 2001 From: David Bremner Date: Sun, 5 Jul 2020 10:00:17 -0300 Subject: [PATCH 233/427] lib: use LOG_XAPIAN_EXCEPTION in n_m_get_date This should not change functionality, but does slightly reduce code duplication. Perhaps more importantly it allows consistent changes to all of the similar exception handling in message.cc. --- lib/message.cc | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/lib/message.cc b/lib/message.cc index 8e43a393..f4306e2c 100644 --- a/lib/message.cc +++ b/lib/message.cc @@ -1191,9 +1191,7 @@ notmuch_message_get_date (notmuch_message_t *message) try { value = message->doc.get_value (NOTMUCH_VALUE_TIMESTAMP); } catch (Xapian::Error &error) { - _notmuch_database_log (notmuch_message_get_database (message), "A Xapian exception occurred when reading date: %s\n", - error.get_msg ().c_str ()); - message->notmuch->exception_reported = true; + LOG_XAPIAN_EXCEPTION (message, error); return 0; } From d284dc00dafb144065bc50242e15e84d4579e3fd Mon Sep 17 00:00:00 2001 From: David Bremner Date: Sun, 5 Jul 2020 10:00:18 -0300 Subject: [PATCH 234/427] test: add known broken test for n_m_get_tags This will be fixed in the next commit. --- test/T560-lib-error.sh | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/test/T560-lib-error.sh b/test/T560-lib-error.sh index c043e35e..05d41615 100755 --- a/test/T560-lib-error.sh +++ b/test/T560-lib-error.sh @@ -470,5 +470,22 @@ cat < EXPECTED EOF test_expect_equal_file EXPECTED OUTPUT +test_begin_subtest "Handle getting tags from closed database" +test_subtest_known_broken +cat c_head2 - c_tail <<'EOF' | test_C ${MAIL_DIR} + { + notmuch_tags_t *result; + result = notmuch_message_get_tags (message); + printf("%d\n%d\n", message != NULL, result == NULL); + } +EOF +cat < EXPECTED +== stdout == +1 +1 +== stderr == +EOF +test_expect_equal_file EXPECTED OUTPUT + test_done From 00f1abfdf45025f176e079b794f4df0ee4afa9cb Mon Sep 17 00:00:00 2001 From: David Bremner Date: Sun, 5 Jul 2020 10:00:19 -0300 Subject: [PATCH 235/427] lib: catch Xapian exceptions in n_m_get_tags This allows the function to return an error value rather than crashing. --- lib/message.cc | 7 ++++++- test/T560-lib-error.sh | 1 - 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/lib/message.cc b/lib/message.cc index f4306e2c..364cdc9d 100644 --- a/lib/message.cc +++ b/lib/message.cc @@ -1206,7 +1206,12 @@ notmuch_message_get_tags (notmuch_message_t *message) { notmuch_tags_t *tags; - _notmuch_message_ensure_metadata (message, message->tag_list); + try { + _notmuch_message_ensure_metadata (message, message->tag_list); + } catch (Xapian::Error &error) { + LOG_XAPIAN_EXCEPTION (message, error); + return NULL; + } tags = _notmuch_tags_create (message, message->tag_list); /* _notmuch_tags_create steals the reference to the tag_list, but diff --git a/test/T560-lib-error.sh b/test/T560-lib-error.sh index 05d41615..fff215e4 100755 --- a/test/T560-lib-error.sh +++ b/test/T560-lib-error.sh @@ -471,7 +471,6 @@ EOF test_expect_equal_file EXPECTED OUTPUT test_begin_subtest "Handle getting tags from closed database" -test_subtest_known_broken cat c_head2 - c_tail <<'EOF' | test_C ${MAIL_DIR} { notmuch_tags_t *result; From 6dc62d8bea7ce537ba30e7f68061a27f03e0bda7 Mon Sep 17 00:00:00 2001 From: David Bremner Date: Sun, 5 Jul 2020 10:00:20 -0300 Subject: [PATCH 236/427] test: add known broken test for n_m_count_files This will be fixed in the next commit. --- test/T560-lib-error.sh | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/test/T560-lib-error.sh b/test/T560-lib-error.sh index fff215e4..e1843d91 100755 --- a/test/T560-lib-error.sh +++ b/test/T560-lib-error.sh @@ -486,5 +486,22 @@ cat < EXPECTED EOF test_expect_equal_file EXPECTED OUTPUT +test_begin_subtest "Handle counting files from closed database" +test_subtest_known_broken +cat c_head2 - c_tail <<'EOF' | test_C ${MAIL_DIR} + { + int result; + result = notmuch_message_count_files (message); + printf("%d\n%d\n", message != NULL, result < 0); + } +EOF +cat < EXPECTED +== stdout == +1 +1 +== stderr == +EOF +test_expect_equal_file EXPECTED OUTPUT + test_done From 96befd0dd075c5d3a63902d72eff7fc1928ba541 Mon Sep 17 00:00:00 2001 From: David Bremner Date: Sun, 5 Jul 2020 10:00:21 -0300 Subject: [PATCH 237/427] lib: catch Xapian exceptions in n_m_count_files This will require some care for the caller to check the sign, and not just add error returns into a running total. --- lib/message.cc | 7 ++++++- lib/notmuch.h | 3 ++- test/T560-lib-error.sh | 1 - 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/lib/message.cc b/lib/message.cc index 364cdc9d..7713050a 100644 --- a/lib/message.cc +++ b/lib/message.cc @@ -1156,7 +1156,12 @@ notmuch_message_get_filenames (notmuch_message_t *message) int notmuch_message_count_files (notmuch_message_t *message) { - _notmuch_message_ensure_filename_list (message); + try { + _notmuch_message_ensure_filename_list (message); + } catch (Xapian::Error &error) { + LOG_XAPIAN_EXCEPTION (message, error); + return -1; + } return _notmuch_string_list_length (message->filename_list); } diff --git a/lib/notmuch.h b/lib/notmuch.h index 50d804fa..693e5076 100644 --- a/lib/notmuch.h +++ b/lib/notmuch.h @@ -1412,7 +1412,8 @@ notmuch_message_get_replies (notmuch_message_t *message); /** * Get the total number of files associated with a message. - * @returns Non-negative integer + * @returns Non-negative integer for file count. + * @returns Negative integer for error. * @since libnotmuch 5.0 (notmuch 0.25) */ int diff --git a/test/T560-lib-error.sh b/test/T560-lib-error.sh index e1843d91..1635d0e7 100755 --- a/test/T560-lib-error.sh +++ b/test/T560-lib-error.sh @@ -487,7 +487,6 @@ EOF test_expect_equal_file EXPECTED OUTPUT test_begin_subtest "Handle counting files from closed database" -test_subtest_known_broken cat c_head2 - c_tail <<'EOF' | test_C ${MAIL_DIR} { int result; From 503c035077a2054cad7d82e5a2e040db2f8d253b Mon Sep 17 00:00:00 2001 From: David Bremner Date: Sun, 5 Jul 2020 10:00:22 -0300 Subject: [PATCH 238/427] test: add known broken test for n_m_add_tag with closed db Exception will be caught in next commit. --- test/T560-lib-error.sh | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/test/T560-lib-error.sh b/test/T560-lib-error.sh index 1635d0e7..cc3ffc6e 100755 --- a/test/T560-lib-error.sh +++ b/test/T560-lib-error.sh @@ -502,5 +502,21 @@ cat < EXPECTED EOF test_expect_equal_file EXPECTED OUTPUT +test_begin_subtest "Handle adding tag with closed database" +test_subtest_known_broken +cat c_head2 - c_tail <<'EOF' | test_C ${MAIL_DIR} + { + notmuch_status_t status; + status = notmuch_message_add_tag (message, "boom"); + printf("%d\n%d\n", message != NULL, status == NOTMUCH_STATUS_XAPIAN_EXCEPTION); + } +EOF +cat < EXPECTED +== stdout == +1 +1 +== stderr == +EOF +test_expect_equal_file EXPECTED OUTPUT test_done From 33dd5fdc6997faee43aa0f79693fc4a8b5143756 Mon Sep 17 00:00:00 2001 From: David Bremner Date: Sun, 5 Jul 2020 10:00:23 -0300 Subject: [PATCH 239/427] lib: catch Xapian exceptions in n_m_add_tag This is mostly just (horizontal) code movement due to wrapping everything in a try / catch. --- lib/message.cc | 34 ++++++++++++++++++++-------------- test/T560-lib-error.sh | 1 - 2 files changed, 20 insertions(+), 15 deletions(-) diff --git a/lib/message.cc b/lib/message.cc index 7713050a..62a181c1 100644 --- a/lib/message.cc +++ b/lib/message.cc @@ -1589,25 +1589,31 @@ notmuch_message_add_tag (notmuch_message_t *message, const char *tag) notmuch_private_status_t private_status; notmuch_status_t status; - status = _notmuch_database_ensure_writable (message->notmuch); - if (status) - return status; + try { + status = _notmuch_database_ensure_writable (message->notmuch); + if (status) + return status; - if (tag == NULL) - return NOTMUCH_STATUS_NULL_POINTER; + if (tag == NULL) + return NOTMUCH_STATUS_NULL_POINTER; - if (strlen (tag) > NOTMUCH_TAG_MAX) - return NOTMUCH_STATUS_TAG_TOO_LONG; + if (strlen (tag) > NOTMUCH_TAG_MAX) + return NOTMUCH_STATUS_TAG_TOO_LONG; - private_status = _notmuch_message_add_term (message, "tag", tag); - if (private_status) { - INTERNAL_ERROR ("_notmuch_message_add_term return unexpected value: %d\n", - private_status); + private_status = _notmuch_message_add_term (message, "tag", tag); + if (private_status) { + INTERNAL_ERROR ("_notmuch_message_add_term return unexpected value: %d\n", + private_status); + } + + if (! message->frozen) + _notmuch_message_sync (message); + + } catch (Xapian::Error &error) { + LOG_XAPIAN_EXCEPTION (message, error); + return NOTMUCH_STATUS_XAPIAN_EXCEPTION; } - if (! message->frozen) - _notmuch_message_sync (message); - return NOTMUCH_STATUS_SUCCESS; } diff --git a/test/T560-lib-error.sh b/test/T560-lib-error.sh index cc3ffc6e..ccdab4be 100755 --- a/test/T560-lib-error.sh +++ b/test/T560-lib-error.sh @@ -503,7 +503,6 @@ EOF test_expect_equal_file EXPECTED OUTPUT test_begin_subtest "Handle adding tag with closed database" -test_subtest_known_broken cat c_head2 - c_tail <<'EOF' | test_C ${MAIL_DIR} { notmuch_status_t status; From 0fc769aa4fa45b2fcd9f1624fc45946581d6f34f Mon Sep 17 00:00:00 2001 From: David Bremner Date: Sun, 5 Jul 2020 10:00:24 -0300 Subject: [PATCH 240/427] test: add broken test for n_m_remove_tag Exception will be caught in next commit. --- test/T560-lib-error.sh | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/test/T560-lib-error.sh b/test/T560-lib-error.sh index ccdab4be..ce83bc96 100755 --- a/test/T560-lib-error.sh +++ b/test/T560-lib-error.sh @@ -518,4 +518,21 @@ cat < EXPECTED EOF test_expect_equal_file EXPECTED OUTPUT +test_begin_subtest "Handle removing tag with closed database" +test_subtest_known_broken +cat c_head2 - c_tail <<'EOF' | test_C ${MAIL_DIR} + { + notmuch_status_t status; + status = notmuch_message_remove_tag (message, "boom"); + printf("%d\n%d\n", message != NULL, status == NOTMUCH_STATUS_XAPIAN_EXCEPTION); + } +EOF +cat < EXPECTED +== stdout == +1 +1 +== stderr == +EOF +test_expect_equal_file EXPECTED OUTPUT + test_done From aa8e3f4487897f0c650dc37d99153099c3d3df5e Mon Sep 17 00:00:00 2001 From: David Bremner Date: Sun, 5 Jul 2020 10:00:25 -0300 Subject: [PATCH 241/427] lib: catch Xapian exceptions in n_m_remove_tag The churn here is again mainly re-indentation. --- lib/message.cc | 33 +++++++++++++++++++-------------- test/T560-lib-error.sh | 1 - 2 files changed, 19 insertions(+), 15 deletions(-) diff --git a/lib/message.cc b/lib/message.cc index 62a181c1..e4848f83 100644 --- a/lib/message.cc +++ b/lib/message.cc @@ -1623,25 +1623,30 @@ notmuch_message_remove_tag (notmuch_message_t *message, const char *tag) notmuch_private_status_t private_status; notmuch_status_t status; - status = _notmuch_database_ensure_writable (message->notmuch); - if (status) - return status; + try { + status = _notmuch_database_ensure_writable (message->notmuch); + if (status) + return status; - if (tag == NULL) - return NOTMUCH_STATUS_NULL_POINTER; + if (tag == NULL) + return NOTMUCH_STATUS_NULL_POINTER; - if (strlen (tag) > NOTMUCH_TAG_MAX) - return NOTMUCH_STATUS_TAG_TOO_LONG; + if (strlen (tag) > NOTMUCH_TAG_MAX) + return NOTMUCH_STATUS_TAG_TOO_LONG; - private_status = _notmuch_message_remove_term (message, "tag", tag); - if (private_status) { - INTERNAL_ERROR ("_notmuch_message_remove_term return unexpected value: %d\n", - private_status); + private_status = _notmuch_message_remove_term (message, "tag", tag); + if (private_status) { + INTERNAL_ERROR ("_notmuch_message_remove_term return unexpected value: %d\n", + private_status); + } + + if (! message->frozen) + _notmuch_message_sync (message); + } catch (Xapian::Error &error) { + LOG_XAPIAN_EXCEPTION (message, error); + return NOTMUCH_STATUS_XAPIAN_EXCEPTION; } - if (! message->frozen) - _notmuch_message_sync (message); - return NOTMUCH_STATUS_SUCCESS; } diff --git a/test/T560-lib-error.sh b/test/T560-lib-error.sh index ce83bc96..ebace888 100755 --- a/test/T560-lib-error.sh +++ b/test/T560-lib-error.sh @@ -519,7 +519,6 @@ EOF test_expect_equal_file EXPECTED OUTPUT test_begin_subtest "Handle removing tag with closed database" -test_subtest_known_broken cat c_head2 - c_tail <<'EOF' | test_C ${MAIL_DIR} { notmuch_status_t status; From 78e9b3467d9f09228a444bb2eec93e4c765b7e9b Mon Sep 17 00:00:00 2001 From: David Bremner Date: Sun, 5 Jul 2020 10:00:26 -0300 Subject: [PATCH 242/427] lib: use COERCE_STATUS in n_m_{add,remove}_tag Currently I don't know of a good way of testing this, but at least in principle a Xapian exception in _notmuch_message_{add,remove}_term would cause an abort in the library. --- lib/message.cc | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/lib/message.cc b/lib/message.cc index e4848f83..5ea5aa22 100644 --- a/lib/message.cc +++ b/lib/message.cc @@ -1602,8 +1602,9 @@ notmuch_message_add_tag (notmuch_message_t *message, const char *tag) private_status = _notmuch_message_add_term (message, "tag", tag); if (private_status) { - INTERNAL_ERROR ("_notmuch_message_add_term return unexpected value: %d\n", - private_status); + return COERCE_STATUS (private_status, + "_notmuch_message_remove_term return unexpected value: %d\n", + private_status); } if (! message->frozen) @@ -1636,8 +1637,9 @@ notmuch_message_remove_tag (notmuch_message_t *message, const char *tag) private_status = _notmuch_message_remove_term (message, "tag", tag); if (private_status) { - INTERNAL_ERROR ("_notmuch_message_remove_term return unexpected value: %d\n", - private_status); + return COERCE_STATUS (private_status, + "_notmuch_message_remove_term return unexpected value: %d\n", + private_status); } if (! message->frozen) From 0e03e2d45e36edb635229f356bf41f153c30a70f Mon Sep 17 00:00:00 2001 From: David Bremner Date: Sat, 11 Jul 2020 10:20:26 -0300 Subject: [PATCH 243/427] doc: replace use of environment variables with a generated config It is getting unwieldy to pass configuration options on the sphinx-build command line, and I anticipate further use of conditionals. As far as I could tell, execing a string is the idiomatic way to emulate include in Python. --- configure | 8 ++++++++ doc/Makefile.local | 2 +- doc/conf.py | 11 ++++++++--- 3 files changed, 17 insertions(+), 4 deletions(-) diff --git a/configure b/configure index 4e16ff40..c8690d3a 100755 --- a/configure +++ b/configure @@ -1548,6 +1548,14 @@ NOTMUCH_HAVE_PYTHON3_PYTEST=${have_python3_pytest} PLATFORM=${platform} EOF +cat > sphinx.config <> sphinx.config +fi +printf "rsti_dir = '%s'\n" $(realpath emacs) >> sphinx.config + # Finally, after everything configured, inform the user how to continue. cat < Date: Sat, 11 Jul 2020 10:20:27 -0300 Subject: [PATCH 244/427] doc: add new python bindings to main documentation tree. A separate conf.py and doc directory (or will be needed if someone wants to build the bindings docs separately from notmuch. --- configure | 4 ++++ doc/conf.py | 8 ++++++++ doc/index.rst | 1 + doc/python-bindings.rst | 5 +++++ 4 files changed, 18 insertions(+) create mode 100644 doc/python-bindings.rst diff --git a/configure b/configure index c8690d3a..b4ee7f37 100755 --- a/configure +++ b/configure @@ -801,6 +801,7 @@ if [ $have_python3 -eq 1 ]; then if "$python" -c 'import cffi,setuptools; cffi.FFI().verify()' >/dev/null 2>&1; then printf "Yes.\n" have_python3_cffi=1 + WITH_PYTHON_DOCS=1 else printf "No (will not install CFFI-based python bindings).\n" fi @@ -1554,6 +1555,9 @@ EOF if [ $WITH_EMACS = "1" ]; then printf "tags.add('WITH_EMACS')\n" >> sphinx.config fi +if [ $WITH_PYTHON_DOCS = "1" ]; then + printf "tags.add('WITH_PYTHON')\n" >> sphinx.config +fi printf "rsti_dir = '%s'\n" $(realpath emacs) >> sphinx.config # Finally, after everything configured, inform the user how to continue. diff --git a/doc/conf.py b/doc/conf.py index fdff2a2c..94e266af 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -4,6 +4,8 @@ import sys import os +extensions = [ 'sphinx.ext.autodoc' ] + # The suffix of source filenames. source_suffix = '.rst' @@ -22,6 +24,9 @@ for pathdir in ['.', '..']: with open(version_file,'r') as infile: version=infile.read().replace('\n','') +# for autodoc +sys.path.insert(0, os.path.join(location, '..', 'bindings', 'python-cffi', 'notmuch2')) + # read generated config for pathdir in ['.', '..']: conf_file = os.path.join(location,pathdir,'sphinx.config') @@ -50,6 +55,9 @@ else: # the docstring include files exclude_patterns.append('notmuch-emacs.rst') +if not tags.has('WITH_PYTHON'): + exclude_patterns.append('python-bindings.rst') + # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'sphinx' diff --git a/doc/index.rst b/doc/index.rst index 4440d93a..a3bf3480 100644 --- a/doc/index.rst +++ b/doc/index.rst @@ -26,6 +26,7 @@ Contents: man7/notmuch-search-terms man1/notmuch-show man1/notmuch-tag + python-bindings Indices and tables ================== diff --git a/doc/python-bindings.rst b/doc/python-bindings.rst new file mode 100644 index 00000000..e1ad26ad --- /dev/null +++ b/doc/python-bindings.rst @@ -0,0 +1,5 @@ +Python Bindings +=============== + +.. automodule:: notmuch2 + :members: From 13116c5cedf58171d04b5f518b3d6f2fe8aea99d Mon Sep 17 00:00:00 2001 From: David Bremner Date: Thu, 16 Jul 2020 15:59:45 -0300 Subject: [PATCH 245/427] configure: make sure WITH_PYTHON_DOCS is initialized Thanks to pazz for reporting the problem. --- configure | 2 ++ 1 file changed, 2 insertions(+) diff --git a/configure b/configure index b4ee7f37..1986e666 100755 --- a/configure +++ b/configure @@ -108,6 +108,7 @@ PREFIX=/usr/local LIBDIR= WITH_DOCS=1 WITH_API_DOCS=1 +WITH_PYTHON_DOCS=1 WITH_EMACS=1 WITH_DESKTOP=1 WITH_BASH=1 @@ -803,6 +804,7 @@ if [ $have_python3 -eq 1 ]; then have_python3_cffi=1 WITH_PYTHON_DOCS=1 else + WITH_PYTHON_DOCS=0 printf "No (will not install CFFI-based python bindings).\n" fi rm -rf __pycache__ # cffi.FFI().verify() uses this space From 2d04ed263121d970cd24a8c26ac924425a7ae3d2 Mon Sep 17 00:00:00 2001 From: David Bremner Date: Sat, 4 Jul 2020 12:18:05 -0300 Subject: [PATCH 246/427] lib: catch exceptions in n_m_get_flag, provide n_m_get_flag_st It's not very nice to return FALSE for an error, so provide notmuch_message_get_flag_st as a migration path. Bump LIBNOTMUCH_MINOR_VERSION because the API is extended. --- lib/message.cc | 33 +++++++++++++++++++++++++++++---- lib/notmuch.h | 27 ++++++++++++++++++++++++++- test/T560-lib-error.sh | 1 - 3 files changed, 55 insertions(+), 6 deletions(-) diff --git a/lib/message.cc b/lib/message.cc index 5ea5aa22..4e1be986 100644 --- a/lib/message.cc +++ b/lib/message.cc @@ -1166,15 +1166,40 @@ notmuch_message_count_files (notmuch_message_t *message) return _notmuch_string_list_length (message->filename_list); } +notmuch_status_t +notmuch_message_get_flag_st (notmuch_message_t *message, + notmuch_message_flag_t flag, + notmuch_bool_t *is_set) +{ + if (! is_set) + return NOTMUCH_STATUS_NULL_POINTER; + + try { + if (flag == NOTMUCH_MESSAGE_FLAG_GHOST && + ! NOTMUCH_TEST_BIT (message->lazy_flags, flag)) + _notmuch_message_ensure_metadata (message, NULL); + } catch (Xapian::Error &error) { + LOG_XAPIAN_EXCEPTION (message, error); + return NOTMUCH_STATUS_XAPIAN_EXCEPTION; + } + + *is_set = NOTMUCH_TEST_BIT (message->flags, flag); + return NOTMUCH_STATUS_SUCCESS; +} + notmuch_bool_t notmuch_message_get_flag (notmuch_message_t *message, notmuch_message_flag_t flag) { - if (flag == NOTMUCH_MESSAGE_FLAG_GHOST && - ! NOTMUCH_TEST_BIT (message->lazy_flags, flag)) - _notmuch_message_ensure_metadata (message, NULL); + notmuch_bool_t is_set; + notmuch_status_t status; - return NOTMUCH_TEST_BIT (message->flags, flag); + status = notmuch_message_get_flag_st (message, flag, &is_set); + + if (status) + return FALSE; + else + return is_set; } void diff --git a/lib/notmuch.h b/lib/notmuch.h index 693e5076..0491e8f8 100644 --- a/lib/notmuch.h +++ b/lib/notmuch.h @@ -58,7 +58,7 @@ NOTMUCH_BEGIN_DECLS * version in Makefile.local. */ #define LIBNOTMUCH_MAJOR_VERSION 5 -#define LIBNOTMUCH_MINOR_VERSION 2 +#define LIBNOTMUCH_MINOR_VERSION 3 #define LIBNOTMUCH_MICRO_VERSION 0 @@ -1486,11 +1486,36 @@ typedef enum _notmuch_message_flag { /** * Get a value of a flag for the email corresponding to 'message'. + * + * returns FALSE in case of errors. + * + * @deprecated Deprecated as of libnotmuch 5.3 (notmuch 0.31). Please + * use notmuch_message_get_flag_st instead. */ +NOTMUCH_DEPRECATED(5,3) notmuch_bool_t notmuch_message_get_flag (notmuch_message_t *message, notmuch_message_flag_t flag); +/** + * Get a value of a flag for the email corresponding to 'message'. + * + * @param message a message object + * @param flag flag to check + * @param is_set pointer to boolean to store flag value. + * + * @retval #NOTMUCH_STATUS_SUCCESS + * @retval #NOTMUCH_STATUS_NULL_POINTER is_set is NULL + * @retval #NOTMUCH_STATUS_XAPIAN_EXCEPTION Accessing the database + * triggered an exception. + * + * @since libnotmuch 5.3 (notmuch 0.31) + */ +notmuch_status_t +notmuch_message_get_flag_st (notmuch_message_t *message, + notmuch_message_flag_t flag, + notmuch_bool_t *is_set); + /** * Set a value of a flag for the email corresponding to 'message'. */ diff --git a/test/T560-lib-error.sh b/test/T560-lib-error.sh index ebace888..36c4c1ce 100755 --- a/test/T560-lib-error.sh +++ b/test/T560-lib-error.sh @@ -438,7 +438,6 @@ EOF test_expect_equal_file EXPECTED OUTPUT test_begin_subtest "Handle getting ghost flag from closed database" -test_subtest_known_broken cat c_head2 - c_tail <<'EOF' | test_C ${MAIL_DIR} { notmuch_bool_t result; From c76832a2037d375ab99dbeba3b37d1aca2103a36 Mon Sep 17 00:00:00 2001 From: David Bremner Date: Sat, 11 Jul 2020 15:30:04 -0300 Subject: [PATCH 247/427] cli/search: replace deprecated notmuch_message_get_flag Our handling of errors is all or nothing here, but it's an improvement on the status quo, and it avoids rippling internal API changes. --- notmuch-search.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/notmuch-search.c b/notmuch-search.c index fd0b58c5..2805d960 100644 --- a/notmuch-search.c +++ b/notmuch-search.c @@ -90,9 +90,13 @@ get_thread_query (notmuch_thread_t *thread, notmuch_messages_move_to_next (messages)) { notmuch_message_t *message = notmuch_messages_get (messages); const char *mid = notmuch_message_get_message_id (message); + notmuch_bool_t is_set; + char **buf; + + if (notmuch_message_get_flag_st (message, NOTMUCH_MESSAGE_FLAG_MATCH, &is_set)) + return -1; /* Determine which query buffer to extend */ - char **buf = notmuch_message_get_flag ( - message, NOTMUCH_MESSAGE_FLAG_MATCH) ? matched_out : unmatched_out; + buf = is_set ? matched_out : unmatched_out; /* Add this message's id: query. Since "id" is an exclusive * prefix, it is implicitly 'or'd together, so we only need to * join queries with a space. */ From ef27194a93736910070320be5615a0257342c543 Mon Sep 17 00:00:00 2001 From: David Bremner Date: Sat, 11 Jul 2020 15:30:05 -0300 Subject: [PATCH 248/427] cli/show: replace deprecated notmuch_message_get_flag This can be seen as moving an abort out of the library, into the CLI where we can both print to stderr and shut the process down without ill effect. --- notmuch-show.c | 26 +++++++++++++++++++------- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/notmuch-show.c b/notmuch-show.c index 36265043..dd836add 100644 --- a/notmuch-show.c +++ b/notmuch-show.c @@ -80,6 +80,18 @@ _get_disposition (GMimeObject *meta) return g_mime_content_disposition_get_disposition (disposition); } +static bool _get_message_flag (notmuch_message_t *message, notmuch_message_flag_t flag) { + notmuch_bool_t is_set; + notmuch_status_t status; + + status = notmuch_message_get_flag_st (message, flag, &is_set); + + if (print_status_message ("notmuch show", message, status)) + INTERNAL_ERROR("unexpected error getting message flag\n"); + + return is_set; +} + /* Emit a sequence of key/value pairs for the metadata of message. * The caller should begin a map before calling this. */ static void @@ -97,10 +109,10 @@ format_message_sprinter (sprinter_t *sp, notmuch_message_t *message) sp->string (sp, notmuch_message_get_message_id (message)); sp->map_key (sp, "match"); - sp->boolean (sp, notmuch_message_get_flag (message, NOTMUCH_MESSAGE_FLAG_MATCH)); + sp->boolean (sp, _get_message_flag (message, NOTMUCH_MESSAGE_FLAG_MATCH)); sp->map_key (sp, "excluded"); - sp->boolean (sp, notmuch_message_get_flag (message, NOTMUCH_MESSAGE_FLAG_EXCLUDED)); + sp->boolean (sp, _get_message_flag (message, NOTMUCH_MESSAGE_FLAG_EXCLUDED)); sp->map_key (sp, "filename"); if (notmuch_format_version >= 3) { @@ -507,8 +519,8 @@ format_part_text (const void *ctx, sprinter_t *sp, mime_node_t *node, part_type, notmuch_message_get_message_id (message), indent, - notmuch_message_get_flag (message, NOTMUCH_MESSAGE_FLAG_MATCH) ? 1 : 0, - notmuch_message_get_flag (message, NOTMUCH_MESSAGE_FLAG_EXCLUDED) ? 1 : 0, + _get_message_flag (message, NOTMUCH_MESSAGE_FLAG_MATCH) ? 1 : 0, + _get_message_flag (message, NOTMUCH_MESSAGE_FLAG_EXCLUDED) ? 1 : 0, notmuch_message_get_filename (message)); } else { char *content_string; @@ -1002,8 +1014,8 @@ show_messages (void *ctx, message = notmuch_messages_get (messages); - match = notmuch_message_get_flag (message, NOTMUCH_MESSAGE_FLAG_MATCH); - excluded = notmuch_message_get_flag (message, NOTMUCH_MESSAGE_FLAG_EXCLUDED); + match = _get_message_flag (message, NOTMUCH_MESSAGE_FLAG_MATCH); + excluded = _get_message_flag (message, NOTMUCH_MESSAGE_FLAG_EXCLUDED); next_indent = indent; @@ -1143,7 +1155,7 @@ do_show_unthreaded (void *ctx, message = notmuch_messages_get (messages); notmuch_message_set_flag (message, NOTMUCH_MESSAGE_FLAG_MATCH, TRUE); - excluded = notmuch_message_get_flag (message, NOTMUCH_MESSAGE_FLAG_EXCLUDED); + excluded = _get_message_flag (message, NOTMUCH_MESSAGE_FLAG_EXCLUDED); if (!excluded || !params->omit_excluded) { status = show_message (ctx, format, sp, message, 0, params); From a4776faa9b85159d9551617f0a61505cf5d16a5a Mon Sep 17 00:00:00 2001 From: David Bremner Date: Sat, 11 Jul 2020 15:30:06 -0300 Subject: [PATCH 249/427] lib/add-message: drop use of deprecated notmuch_message_get_flag. As a side effect, we revert the switch from notmuch_bool_t to bool here. This is because those two types are not actually compatible when passing by reference. --- lib/add-message.cc | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/add-message.cc b/lib/add-message.cc index 8c92689b..9dd4b697 100644 --- a/lib/add-message.cc +++ b/lib/add-message.cc @@ -477,7 +477,7 @@ notmuch_database_index_file (notmuch_database_t *notmuch, notmuch_message_t *message = NULL; notmuch_status_t ret = NOTMUCH_STATUS_SUCCESS, ret2; notmuch_private_status_t private_status; - bool is_ghost = false, is_new = false; + notmuch_bool_t is_ghost = false, is_new = false; notmuch_indexopts_t *def_indexopts = NULL; const char *date; @@ -525,7 +525,9 @@ notmuch_database_index_file (notmuch_database_t *notmuch, is_new = true; break; case NOTMUCH_PRIVATE_STATUS_SUCCESS: - is_ghost = notmuch_message_get_flag (message, NOTMUCH_MESSAGE_FLAG_GHOST); + ret = notmuch_message_get_flag_st (message, NOTMUCH_MESSAGE_FLAG_GHOST, &is_ghost); + if (ret) + goto DONE; is_new = false; break; default: From c8539d5e9248f08f417165e765814a1ed3d777e1 Mon Sep 17 00:00:00 2001 From: David Bremner Date: Sat, 11 Jul 2020 15:30:07 -0300 Subject: [PATCH 250/427] lib/thread: replace use of deprecated notmuch_message_get_flag This adds one more reason why _notmuch_thread_create might return NULL, but those were not previously enumerated, so no promises are broken. --- lib/thread.cc | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/lib/thread.cc b/lib/thread.cc index 6073e45c..17346008 100644 --- a/lib/thread.cc +++ b/lib/thread.cc @@ -351,14 +351,16 @@ _thread_set_subject_from_message (notmuch_thread_t *thread, /* Add a message to this thread which is known to match the original * search specification. The 'sort' parameter controls whether the * oldest or newest matching subject is applied to the thread as a - * whole. */ -static void + * whole. Returns 0 on success. + */ +static int _thread_add_matched_message (notmuch_thread_t *thread, notmuch_message_t *message, notmuch_sort_t sort) { time_t date; notmuch_message_t *hashed_message; + notmuch_bool_t is_set; date = notmuch_message_get_date (message); @@ -375,7 +377,9 @@ _thread_add_matched_message (notmuch_thread_t *thread, _thread_set_subject_from_message (thread, message); } - if (! notmuch_message_get_flag (message, NOTMUCH_MESSAGE_FLAG_EXCLUDED)) + if (notmuch_message_get_flag_st (message, NOTMUCH_MESSAGE_FLAG_EXCLUDED, &is_set)) + return -1; + if (! is_set) thread->matched_messages++; if (g_hash_table_lookup_extended (thread->message_hash, @@ -386,6 +390,7 @@ _thread_add_matched_message (notmuch_thread_t *thread, } _thread_add_matched_author (thread, _notmuch_message_get_author (hashed_message)); + return 0; } static bool @@ -625,7 +630,10 @@ _notmuch_thread_create (void *ctx, if ( _notmuch_doc_id_set_contains (match_set, doc_id)) { _notmuch_doc_id_set_remove (match_set, doc_id); - _thread_add_matched_message (thread, message, sort); + if (_thread_add_matched_message (thread, message, sort)) { + thread = NULL; + goto DONE; + } } _notmuch_message_close (message); From 49d630d0f389e0bfb08a83178582f4a38f1ec664 Mon Sep 17 00:00:00 2001 From: David Bremner Date: Sat, 11 Jul 2020 15:30:08 -0300 Subject: [PATCH 251/427] bindings/ruby: replacy use of deprecated notmuch_message_get_flag Depending on the flag, this actually can return an errror, so raise a ruby exception if so. --- bindings/ruby/message.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/bindings/ruby/message.c b/bindings/ruby/message.c index c55cf6e2..6ea82afa 100644 --- a/bindings/ruby/message.c +++ b/bindings/ruby/message.c @@ -137,13 +137,18 @@ VALUE notmuch_rb_message_get_flag (VALUE self, VALUE flagv) { notmuch_message_t *message; + notmuch_bool_t is_set; + notmuch_status_t status; Data_Get_Notmuch_Message (self, message); if (!FIXNUM_P (flagv)) rb_raise (rb_eTypeError, "Flag not a Fixnum"); - return notmuch_message_get_flag (message, FIX2INT (flagv)) ? Qtrue : Qfalse; + status = notmuch_message_get_flag_st (message, FIX2INT (flagv), &is_set); + notmuch_rb_status_raise (status); + + return is_set ? Qtrue : Qfalse; } /* From 7aaf6cbe9ac20d99046d1bdf260fe0fa72519760 Mon Sep 17 00:00:00 2001 From: David Bremner Date: Wed, 15 Jul 2020 09:36:27 -0300 Subject: [PATCH 252/427] configure: replace multiple redirects with redirected block This belatedly implements Tomi's suggestion from id:m25zaukv6u.fsf@guru.guru-group.fi --- configure | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/configure b/configure index 1986e666..98f5f1ea 100755 --- a/configure +++ b/configure @@ -1551,16 +1551,16 @@ NOTMUCH_HAVE_PYTHON3_PYTEST=${have_python3_pytest} PLATFORM=${platform} EOF -cat > sphinx.config <> sphinx.config -fi -if [ $WITH_PYTHON_DOCS = "1" ]; then - printf "tags.add('WITH_PYTHON')\n" >> sphinx.config -fi -printf "rsti_dir = '%s'\n" $(realpath emacs) >> sphinx.config +{ + echo "# Generated by configure, run from doc/conf.py" + if [ $WITH_EMACS = "1" ]; then + echo "tags.add('WITH_EMACS')" + fi + if [ $WITH_PYTHON_DOCS = "1" ]; then + echo "tags.add('WITH_PYTHON')" + fi + printf "rsti_dir = '%s'\n" $(realpath emacs) +} > sphinx.config # Finally, after everything configured, inform the user how to continue. cat < Date: Wed, 8 Jul 2020 21:17:00 -0300 Subject: [PATCH 253/427] test: add regression test for notmuch_message_has_maildir_flag This passes the NULL return inside _ensure_maildir_flags does not break anything. Probably this should be handled more explicitely. --- lib/message.cc | 3 ++- test/T560-lib-error.sh | 16 ++++++++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/lib/message.cc b/lib/message.cc index 4e1be986..bb4b2fa1 100644 --- a/lib/message.cc +++ b/lib/message.cc @@ -1732,7 +1732,8 @@ _ensure_maildir_flags (notmuch_message_t *message, bool force) message->maildir_flags = NULL; } } - + /* n_m_get_filenames returns NULL for errors, which terminates the + * loop */ for (filenames = notmuch_message_get_filenames (message); notmuch_filenames_valid (filenames); notmuch_filenames_move_to_next (filenames)) { diff --git a/test/T560-lib-error.sh b/test/T560-lib-error.sh index 36c4c1ce..02478e92 100755 --- a/test/T560-lib-error.sh +++ b/test/T560-lib-error.sh @@ -533,4 +533,20 @@ cat < EXPECTED EOF test_expect_equal_file EXPECTED OUTPUT +test_begin_subtest "Handle read maildir flag with closed database" +cat c_head2 - c_tail <<'EOF' | test_C ${MAIL_DIR} + { + notmuch_bool_t is_set = -1; + is_set = notmuch_message_has_maildir_flag (message, 'S'); + printf("%d\n%d\n", message != NULL, is_set == FALSE || is_set == TRUE); + } +EOF +cat < EXPECTED +== stdout == +1 +1 +== stderr == +EOF +test_expect_equal_file EXPECTED OUTPUT + test_done From b7572ceb143773a988a4852ac3b7c85254009d0e Mon Sep 17 00:00:00 2001 From: David Bremner Date: Wed, 8 Jul 2020 21:17:01 -0300 Subject: [PATCH 254/427] lib: add notmuch_message_has_maildir_flag_st Initially the new function is mainly tested indirectly via the wrapper. --- lib/message.cc | 41 ++++++++++++++++++++++++++++++++++------- lib/notmuch.h | 22 ++++++++++++++++++++++ test/T560-lib-error.sh | 17 +++++++++++++++++ 3 files changed, 73 insertions(+), 7 deletions(-) diff --git a/lib/message.cc b/lib/message.cc index bb4b2fa1..8e090aa3 100644 --- a/lib/message.cc +++ b/lib/message.cc @@ -1717,7 +1717,7 @@ _filename_is_in_maildir (const char *filename) return NULL; } -static void +static notmuch_status_t _ensure_maildir_flags (notmuch_message_t *message, bool force) { const char *flags; @@ -1732,9 +1732,10 @@ _ensure_maildir_flags (notmuch_message_t *message, bool force) message->maildir_flags = NULL; } } - /* n_m_get_filenames returns NULL for errors, which terminates the - * loop */ - for (filenames = notmuch_message_get_filenames (message); + filenames = notmuch_message_get_filenames (message); + if (! filenames) + return NOTMUCH_STATUS_XAPIAN_EXCEPTION; + for (; notmuch_filenames_valid (filenames); notmuch_filenames_move_to_next (filenames)) { filename = notmuch_filenames_get (filenames); @@ -1760,13 +1761,37 @@ _ensure_maildir_flags (notmuch_message_t *message, bool force) } if (seen_maildir_info) message->maildir_flags = combined_flags; + return NOTMUCH_STATUS_SUCCESS; } notmuch_bool_t notmuch_message_has_maildir_flag (notmuch_message_t *message, char flag) { - _ensure_maildir_flags (message, false); - return message->maildir_flags && (strchr (message->maildir_flags, flag) != NULL); + notmuch_status_t status; + notmuch_bool_t ret; + status = notmuch_message_has_maildir_flag_st (message, flag, &ret); + if (status) + return FALSE; + + return ret; +} + +notmuch_status_t +notmuch_message_has_maildir_flag_st (notmuch_message_t *message, + char flag, + notmuch_bool_t *is_set) +{ + notmuch_status_t status; + + if (! is_set) + return NOTMUCH_STATUS_NULL_POINTER; + + status = _ensure_maildir_flags (message, false); + if (status) + return status; + + *is_set = message->maildir_flags && (strchr (message->maildir_flags, flag) != NULL); + return NOTMUCH_STATUS_SUCCESS; } notmuch_status_t @@ -1775,7 +1800,9 @@ notmuch_message_maildir_flags_to_tags (notmuch_message_t *message) notmuch_status_t status; unsigned i; - _ensure_maildir_flags (message, true); + status = _ensure_maildir_flags (message, true); + if (status) + return status; /* If none of the filenames have any maildir info field (not even * an empty info with no flags set) then there's no information to * go on, so do nothing. */ diff --git a/lib/notmuch.h b/lib/notmuch.h index 0491e8f8..edad3d8a 100644 --- a/lib/notmuch.h +++ b/lib/notmuch.h @@ -1680,10 +1680,32 @@ notmuch_message_maildir_flags_to_tags (notmuch_message_t *message); * return TRUE if any filename of 'message' has maildir flag 'flag', * FALSE otherwise. * + * Deprecated wrapper for notmuch_message_has_maildir_flag_st + * + * @returns FALSE in case of error + * @deprecated libnotmuch 5.3 (notmuch 0.31) */ +NOTMUCH_DEPRECATED(5, 3) notmuch_bool_t notmuch_message_has_maildir_flag (notmuch_message_t *message, char flag); +/** + * check message for maildir flag + * + * @param [in,out] message message to check + * @param [in] flag flag to check for + * @param [out] is_set pointer to boolean + * + * @retval #NOTMUCH_STATUS_SUCCESS + * @retval #NOTMUCH_STATUS_NULL_POINTER is_set is NULL + * @retval #NOTMUCH_STATUS_XAPIAN_EXCEPTION Accessing the database + * triggered an exception. + */ +notmuch_status_t +notmuch_message_has_maildir_flag_st (notmuch_message_t *message, + char flag, + notmuch_bool_t *is_set); + /** * Rename message filename(s) to encode tags as maildir flags. * diff --git a/test/T560-lib-error.sh b/test/T560-lib-error.sh index 02478e92..84ea6ccf 100755 --- a/test/T560-lib-error.sh +++ b/test/T560-lib-error.sh @@ -549,4 +549,21 @@ cat < EXPECTED EOF test_expect_equal_file EXPECTED OUTPUT +test_begin_subtest "Handle checking maildir flag with closed db (new API)" +cat c_head2 - c_tail <<'EOF' | test_C ${MAIL_DIR} + { + notmuch_status_t status; + notmuch_bool_t out; + status = notmuch_message_has_maildir_flag_st (message, 'S', &out); + printf("%d\n%d\n", message != NULL, status == NOTMUCH_STATUS_XAPIAN_EXCEPTION); + } +EOF +cat < EXPECTED +== stdout == +1 +1 +== stderr == +EOF +test_expect_equal_file EXPECTED OUTPUT + test_done From 485c32b1f30ef74baccaf40777891f6ef9974634 Mon Sep 17 00:00:00 2001 From: David Bremner Date: Mon, 13 Jul 2020 20:51:07 -0300 Subject: [PATCH 255/427] cli/new: replace newly deprecated n_m_has_maildir_flag Boolean return values have no out-of-band-values to signal errors. The change here is that a (somewhat unlikely) fatal error after indexing will now be caught. --- notmuch-new.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/notmuch-new.c b/notmuch-new.c index f079f62a..4075d395 100644 --- a/notmuch-new.c +++ b/notmuch-new.c @@ -388,8 +388,11 @@ add_file (notmuch_database_t *notmuch, const char *filename, notmuch_message_maildir_flags_to_tags (message); for (tag = state->new_tags; *tag != NULL; tag++) { - if (strcmp ("unread", *tag) != 0 || - ! notmuch_message_has_maildir_flag (message, 'S')) { + notmuch_bool_t is_set; + /* Currently all errors from has_maildir_flag are fatal */ + if ((status = notmuch_message_has_maildir_flag_st (message, 'S', &is_set))) + goto DONE; + if (strcmp ("unread", *tag) != 0 || ! is_set) { notmuch_message_add_tag (message, *tag); } } From e32f66d0e30b6bd0ddb225f06785f7689349fcb9 Mon Sep 17 00:00:00 2001 From: David Bremner Date: Wed, 8 Jul 2020 21:17:02 -0300 Subject: [PATCH 256/427] test: add regression test for n_m_maildir_flags_to_tags This function currently catches at least the obvious Xapian exceptions and we want to keep it that way. --- test/T560-lib-error.sh | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/test/T560-lib-error.sh b/test/T560-lib-error.sh index 84ea6ccf..2aee132b 100755 --- a/test/T560-lib-error.sh +++ b/test/T560-lib-error.sh @@ -566,4 +566,20 @@ cat < EXPECTED EOF test_expect_equal_file EXPECTED OUTPUT +test_begin_subtest "Handle converting maildir flags to tags with closed db" +cat c_head2 - c_tail <<'EOF' | test_C ${MAIL_DIR} + { + notmuch_status_t status; + status = notmuch_message_maildir_flags_to_tags (message); + printf("%d\n%d\n", message != NULL, status == NOTMUCH_STATUS_XAPIAN_EXCEPTION); + } +EOF +cat < EXPECTED +== stdout == +1 +1 +== stderr == +EOF +test_expect_equal_file EXPECTED OUTPUT + test_done From a4ac9941a8f3afde7d2ffa17a6862e63444affa9 Mon Sep 17 00:00:00 2001 From: David Bremner Date: Wed, 8 Jul 2020 21:17:03 -0300 Subject: [PATCH 257/427] test: add broken test for n_m_remove_all_tags The Xapian exception is actually caught here, but the NULL return is not dealt with properly. --- test/T560-lib-error.sh | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/test/T560-lib-error.sh b/test/T560-lib-error.sh index 2aee132b..52057c25 100755 --- a/test/T560-lib-error.sh +++ b/test/T560-lib-error.sh @@ -582,4 +582,21 @@ cat < EXPECTED EOF test_expect_equal_file EXPECTED OUTPUT +test_begin_subtest "Handle removing all tags with closed db" +test_subtest_known_broken +cat c_head2 - c_tail <<'EOF' | test_C ${MAIL_DIR} + { + notmuch_status_t status; + status = notmuch_message_remove_all_tags (message); + printf("%d\n%d\n", message != NULL, status == NOTMUCH_STATUS_XAPIAN_EXCEPTION); + } +EOF +cat < EXPECTED +== stdout == +1 +1 +== stderr == +EOF +test_expect_equal_file EXPECTED OUTPUT + test_done From a2b90dc084c0224e56c1e63773048545788c7ad4 Mon Sep 17 00:00:00 2001 From: David Bremner Date: Wed, 8 Jul 2020 21:17:04 -0300 Subject: [PATCH 258/427] lib: handle xapian exception in n_m_remove_all_tags At least the exception we already catch should be reported properly. --- lib/message.cc | 10 +++++++--- lib/notmuch.h | 6 ++++-- test/T560-lib-error.sh | 1 - 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/lib/message.cc b/lib/message.cc index 8e090aa3..09708ed9 100644 --- a/lib/message.cc +++ b/lib/message.cc @@ -2071,16 +2071,20 @@ notmuch_message_remove_all_tags (notmuch_message_t *message) status = _notmuch_database_ensure_writable (message->notmuch); if (status) return status; + tags = notmuch_message_get_tags (message); + if (! tags) + return NOTMUCH_STATUS_XAPIAN_EXCEPTION; - for (tags = notmuch_message_get_tags (message); + for (; notmuch_tags_valid (tags); notmuch_tags_move_to_next (tags)) { tag = notmuch_tags_get (tags); private_status = _notmuch_message_remove_term (message, "tag", tag); if (private_status) { - INTERNAL_ERROR ("_notmuch_message_remove_term return unexpected value: %d\n", - private_status); + return COERCE_STATUS (private_status, + "_notmuch_message_remove_term return unexpected value: %d\n", + private_status); } } diff --git a/lib/notmuch.h b/lib/notmuch.h index edad3d8a..82d6c10f 100644 --- a/lib/notmuch.h +++ b/lib/notmuch.h @@ -1635,8 +1635,10 @@ notmuch_message_remove_tag (notmuch_message_t *message, const char *tag); * See notmuch_message_freeze for an example showing how to safely * replace tag values. * - * NOTMUCH_STATUS_READ_ONLY_DATABASE: Database was opened in read-only - * mode so message cannot be modified. + * @retval #NOTMUCH_STATUS_READ_ONLY_DATABASE: Database was opened in + * read-only mode so message cannot be modified. + * @retval #NOTMUCH_STATUS_XAPIAN_EXCEPTION: an execption was thrown + * accessing the database. */ notmuch_status_t notmuch_message_remove_all_tags (notmuch_message_t *message); diff --git a/test/T560-lib-error.sh b/test/T560-lib-error.sh index 52057c25..5b5ad765 100755 --- a/test/T560-lib-error.sh +++ b/test/T560-lib-error.sh @@ -583,7 +583,6 @@ EOF test_expect_equal_file EXPECTED OUTPUT test_begin_subtest "Handle removing all tags with closed db" -test_subtest_known_broken cat c_head2 - c_tail <<'EOF' | test_C ${MAIL_DIR} { notmuch_status_t status; From 9244f588608ccedeab5e82e965a86c04a22be4b7 Mon Sep 17 00:00:00 2001 From: David Bremner Date: Wed, 8 Jul 2020 21:17:05 -0300 Subject: [PATCH 259/427] test: regression tests of n_m_freeze and n_m_thaw on closed db Neither of these accesses the database, so should be safe. Add the tests to catch any changes in exception throwing. --- test/T560-lib-error.sh | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/test/T560-lib-error.sh b/test/T560-lib-error.sh index 5b5ad765..1ee9bd06 100755 --- a/test/T560-lib-error.sh +++ b/test/T560-lib-error.sh @@ -598,4 +598,36 @@ cat < EXPECTED EOF test_expect_equal_file EXPECTED OUTPUT +test_begin_subtest "Handle freezing message with closed db" +cat c_head2 - c_tail <<'EOF' | test_C ${MAIL_DIR} + { + notmuch_status_t status; + status = notmuch_message_freeze (message); + printf("%d\n%d\n", message != NULL, status == NOTMUCH_STATUS_SUCCESS); + } +EOF +cat < EXPECTED +== stdout == +1 +1 +== stderr == +EOF +test_expect_equal_file EXPECTED OUTPUT + +test_begin_subtest "Handle thawing message with closed db" +cat c_head2 - c_tail <<'EOF' | test_C ${MAIL_DIR} + { + notmuch_status_t status; + status = notmuch_message_thaw (message); + printf("%d\n%d\n", message != NULL, status == NOTMUCH_STATUS_UNBALANCED_FREEZE_THAW); + } +EOF +cat < EXPECTED +== stdout == +1 +1 +== stderr == +EOF +test_expect_equal_file EXPECTED OUTPUT + test_done From 1578e2af6a412a72346d5264d2d6707972a95b33 Mon Sep 17 00:00:00 2001 From: David Bremner Date: Wed, 8 Jul 2020 21:17:06 -0300 Subject: [PATCH 260/427] test: regression test for destroying message with closed db This should be fine because the message belongs to the database (talloc context wise). --- test/T560-lib-error.sh | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/test/T560-lib-error.sh b/test/T560-lib-error.sh index 1ee9bd06..492c1fb5 100755 --- a/test/T560-lib-error.sh +++ b/test/T560-lib-error.sh @@ -630,4 +630,19 @@ cat < EXPECTED EOF test_expect_equal_file EXPECTED OUTPUT +test_begin_subtest "Handle destroying message with closed db" +cat c_head2 - c_tail <<'EOF' | test_C ${MAIL_DIR} + { + notmuch_message_destroy (message); + printf("%d\n%d\n", message != NULL, 1); + } +EOF +cat < EXPECTED +== stdout == +1 +1 +== stderr == +EOF +test_expect_equal_file EXPECTED OUTPUT + test_done From a2279c322e65ff26a136a2fb801a1d0670a45c69 Mon Sep 17 00:00:00 2001 From: David Bremner Date: Wed, 8 Jul 2020 21:17:07 -0300 Subject: [PATCH 261/427] test: regression for retrieving closed db from message This is actually one of the few potentially useful things you can do with a message belonging to a closed database, since in principle you could re-open the database. --- test/T560-lib-error.sh | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/test/T560-lib-error.sh b/test/T560-lib-error.sh index 492c1fb5..4805368a 100755 --- a/test/T560-lib-error.sh +++ b/test/T560-lib-error.sh @@ -645,4 +645,20 @@ cat < EXPECTED EOF test_expect_equal_file EXPECTED OUTPUT +test_begin_subtest "Handle retrieving closed db from message" +cat c_head2 - c_tail <<'EOF' | test_C ${MAIL_DIR} + { + notmuch_database_t *db2; + db2 = notmuch_message_get_database (message); + printf("%d\n%d\n", message != NULL, db == db2); + } +EOF +cat < EXPECTED +== stdout == +1 +1 +== stderr == +EOF +test_expect_equal_file EXPECTED OUTPUT + test_done From a0c83e1fac88588ff6564dd82fbca29c2f5c78a5 Mon Sep 17 00:00:00 2001 From: David Bremner Date: Wed, 8 Jul 2020 21:17:08 -0300 Subject: [PATCH 262/427] test: add known broken test for n_m_reindex on closed db This is another case where the code should not call INTERNAL_ERROR. --- test/T560-lib-error.sh | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/test/T560-lib-error.sh b/test/T560-lib-error.sh index 4805368a..8543b5dc 100755 --- a/test/T560-lib-error.sh +++ b/test/T560-lib-error.sh @@ -661,4 +661,21 @@ cat < EXPECTED EOF test_expect_equal_file EXPECTED OUTPUT +test_begin_subtest "Handle reindexing message with closed db" +test_subtest_known_broken +cat c_head2 - c_tail <<'EOF' | test_C ${MAIL_DIR} + { + notmuch_status_t status; + status = notmuch_message_reindex (message, NULL); + printf("%d\n%d\n", message != NULL, status == NOTMUCH_STATUS_XAPIAN_EXCEPTION); + } +EOF +cat < EXPECTED +== stdout == +1 +1 +== stderr == +EOF +test_expect_equal_file EXPECTED OUTPUT + test_done From 765ca7bc08f83a3c0f9ebffe58dab03634e45f37 Mon Sep 17 00:00:00 2001 From: David Bremner Date: Wed, 8 Jul 2020 21:17:09 -0300 Subject: [PATCH 263/427] lib: fix return value for n_m_reindex Also update the documentation for the behaviour of n_m_get_thread_id that this fix relies on. --- lib/message.cc | 6 ++++-- lib/notmuch.h | 4 ++-- test/T560-lib-error.sh | 1 - 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/lib/message.cc b/lib/message.cc index 09708ed9..87448101 100644 --- a/lib/message.cc +++ b/lib/message.cc @@ -2205,8 +2205,10 @@ notmuch_message_reindex (notmuch_message_t *message, /* Save in case we need to delete message */ orig_thread_id = notmuch_message_get_thread_id (message); if (! orig_thread_id) { - /* XXX TODO: make up new error return? */ - INTERNAL_ERROR ("message without thread-id"); + /* the following is correct as long as there is only one reason + n_m_get_thread_id returns NULL + */ + return NOTMUCH_STATUS_XAPIAN_EXCEPTION; } /* strdup it because the metadata may be invalidated */ diff --git a/lib/notmuch.h b/lib/notmuch.h index 82d6c10f..0dd442d4 100644 --- a/lib/notmuch.h +++ b/lib/notmuch.h @@ -1378,8 +1378,8 @@ notmuch_message_get_message_id (notmuch_message_t *message); * notmuch_message_destroy on 'message' or until a query from which it * derived is destroyed). * - * This function will not return NULL since Notmuch ensures that every - * message belongs to a single thread. + * This function will return NULL if triggers an unhandled Xapian + * exception. */ const char * notmuch_message_get_thread_id (notmuch_message_t *message); diff --git a/test/T560-lib-error.sh b/test/T560-lib-error.sh index 8543b5dc..fda1f170 100755 --- a/test/T560-lib-error.sh +++ b/test/T560-lib-error.sh @@ -662,7 +662,6 @@ EOF test_expect_equal_file EXPECTED OUTPUT test_begin_subtest "Handle reindexing message with closed db" -test_subtest_known_broken cat c_head2 - c_tail <<'EOF' | test_C ${MAIL_DIR} { notmuch_status_t status; From f9fbd1ee3bfd679175d88af403752d87a730349f Mon Sep 17 00:00:00 2001 From: Sean Whitton Date: Wed, 22 Jul 2020 08:11:32 -0700 Subject: [PATCH 264/427] emacs: notmuch-search: avoid wiping out buffer-local variables --- emacs/notmuch.el | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/emacs/notmuch.el b/emacs/notmuch.el index dd18f2e1..c97997fe 100644 --- a/emacs/notmuch.el +++ b/emacs/notmuch.el @@ -987,7 +987,11 @@ the configured default sort order." (if no-display (set-buffer buffer) (switch-to-buffer buffer)) - (notmuch-search-mode) + ;; avoid wiping out third party buffer-local variables in the case + ;; where we're just refreshing or changing the sort order of an + ;; existing search results buffer + (unless (eq major-mode 'notmuch-search-mode) + (notmuch-search-mode)) ;; Don't track undo information for this buffer (set 'buffer-undo-list t) (set 'notmuch-search-query-string query) From 1a8060b81f2fbba324d4156f4733a7f1deb14d60 Mon Sep 17 00:00:00 2001 From: David Bremner Date: Wed, 22 Jul 2020 08:35:42 -0300 Subject: [PATCH 265/427] test: sanitize line numbers in exception reports Prevent test suite churn when reported line numbers change. --- test/test-lib.sh | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/test/test-lib.sh b/test/test-lib.sh index 7424881e..8c331b88 100644 --- a/test/test-lib.sh +++ b/test/test-lib.sh @@ -619,6 +619,11 @@ print(msg.as_string(False)) ' "$@" } +notmuch_exception_sanitize () +{ + perl -pe 's/(A Xapian exception occurred at .*[.]cc?):([0-9]*)/\1:XXX/' +} + notmuch_search_sanitize () { perl -pe 's/("?thread"?: ?)("?)................("?)/\1\2XXX\3/' @@ -1093,7 +1098,7 @@ test_C () { echo "== stdout ==" > OUTPUT.stdout echo "== stderr ==" > OUTPUT.stderr ./${exec_file} "$@" 1>>OUTPUT.stdout 2>>OUTPUT.stderr - notmuch_dir_sanitize OUTPUT.stdout OUTPUT.stderr > OUTPUT + notmuch_dir_sanitize OUTPUT.stdout OUTPUT.stderr | notmuch_exception_sanitize > OUTPUT } make_shim () { From e9867b818b75216427c7190f67354959dfb914db Mon Sep 17 00:00:00 2001 From: David Bremner Date: Wed, 22 Jul 2020 08:19:28 -0300 Subject: [PATCH 266/427] lib: fix exception messages for n_m_message_* The original generic handler had an extra '%s' in the format string. Update tests that failed to catch this because the template to print status strings checked 'stat', which was not set. --- lib/message.cc | 2 +- test/T560-lib-error.sh | 67 +++++++++++++++++++++++++++--------------- 2 files changed, 44 insertions(+), 25 deletions(-) diff --git a/lib/message.cc b/lib/message.cc index 87448101..64798413 100644 --- a/lib/message.cc +++ b/lib/message.cc @@ -96,7 +96,7 @@ static void _log_xapian_exception (const char *where, notmuch_message_t *message, const Xapian::Error error) { notmuch_database_t *notmuch = notmuch_message_get_database (message); _notmuch_database_log (notmuch, - "A Xapian exception occurred %s retrieving %s : %s\n", + "A Xapian exception occurred at %s: %s\n", where, error.get_msg ().c_str ()); notmuch->exception_reported = true; diff --git a/test/T560-lib-error.sh b/test/T560-lib-error.sh index fda1f170..8a608341 100755 --- a/test/T560-lib-error.sh +++ b/test/T560-lib-error.sh @@ -345,6 +345,7 @@ cat c_head2 - c_tail <<'EOF' | test_C ${MAIL_DIR} const char *id2; id2=notmuch_message_get_message_id (message); printf("%d\n%d\n", message != NULL, id2==NULL); + stat = NOTMUCH_STATUS_XAPIAN_EXCEPTION; } EOF cat < EXPECTED @@ -352,6 +353,7 @@ cat < EXPECTED 1 1 == stderr == +A Xapian exception occurred at lib/message.cc:XXX: Database has been closed EOF test_expect_equal_file EXPECTED OUTPUT @@ -361,6 +363,7 @@ cat c_head2 - c_tail <<'EOF' | test_C ${MAIL_DIR} const char *id2; id2=notmuch_message_get_thread_id (message); printf("%d\n%d\n", message != NULL, id2==NULL); + stat = NOTMUCH_STATUS_XAPIAN_EXCEPTION; } EOF cat < EXPECTED @@ -368,6 +371,7 @@ cat < EXPECTED 1 1 == stderr == +A Xapian exception occurred at lib/message.cc:XXX: Database has been closed EOF test_expect_equal_file EXPECTED OUTPUT @@ -377,6 +381,7 @@ cat c_head2 - c_tail <<'EOF' | test_C ${MAIL_DIR} const char *from; from=notmuch_message_get_header (message, "from"); printf("%s\n%d\n", id, from == NULL); + stat = NOTMUCH_STATUS_XAPIAN_EXCEPTION; } EOF cat < EXPECTED @@ -384,6 +389,7 @@ cat < EXPECTED 1258471718-6781-1-git-send-email-dottedmag@dottedmag.net 1 == stderr == +A Xapian exception occurred at lib/message.cc:XXX: Database has been closed EOF test_expect_equal_file EXPECTED OUTPUT @@ -395,6 +401,7 @@ cat c_head2 - c_tail <<'EOF' | test_C ${MAIL_DIR} notmuch_messages_t *replies; replies = notmuch_message_get_replies (message); printf("%d\n%d\n", message != NULL, replies==NULL); + stat = NOTMUCH_STATUS_XAPIAN_EXCEPTION; } EOF cat < EXPECTED @@ -411,6 +418,7 @@ cat c_head2 - c_tail <<'EOF' | test_C ${MAIL_DIR} const char *filename; filename = notmuch_message_get_filename (message); printf("%d\n%d\n", message != NULL, filename == NULL); + stat = NOTMUCH_STATUS_XAPIAN_EXCEPTION; } EOF cat < EXPECTED @@ -418,6 +426,7 @@ cat < EXPECTED 1 1 == stderr == +A Xapian exception occurred at lib/message.cc:XXX: Database has been closed EOF test_expect_equal_file EXPECTED OUTPUT @@ -427,6 +436,7 @@ cat c_head2 - c_tail <<'EOF' | test_C ${MAIL_DIR} notmuch_filenames_t *filenames; filenames = notmuch_message_get_filenames (message); printf("%d\n%d\n", message != NULL, filenames == NULL); + stat = NOTMUCH_STATUS_XAPIAN_EXCEPTION; } EOF cat < EXPECTED @@ -434,6 +444,7 @@ cat < EXPECTED 1 1 == stderr == +A Xapian exception occurred at lib/message.cc:XXX: Database has been closed EOF test_expect_equal_file EXPECTED OUTPUT @@ -443,6 +454,7 @@ cat c_head2 - c_tail <<'EOF' | test_C ${MAIL_DIR} notmuch_bool_t result; result = notmuch_message_get_flag (message, NOTMUCH_MESSAGE_FLAG_GHOST); printf("%d\n%d\n", message != NULL, result == FALSE); + stat = NOTMUCH_STATUS_XAPIAN_EXCEPTION; } EOF cat < EXPECTED @@ -450,6 +462,7 @@ cat < EXPECTED 1 1 == stderr == +A Xapian exception occurred at lib/message.cc:XXX: Database has been closed EOF test_expect_equal_file EXPECTED OUTPUT @@ -459,6 +472,7 @@ cat c_head2 - c_tail <<'EOF' | test_C ${MAIL_DIR} time_t result; result = notmuch_message_get_date (message); printf("%d\n%d\n", message != NULL, result == 0); + stat = NOTMUCH_STATUS_XAPIAN_EXCEPTION; } EOF cat < EXPECTED @@ -466,6 +480,7 @@ cat < EXPECTED 1 1 == stderr == +A Xapian exception occurred at lib/message.cc:XXX: Database has been closed EOF test_expect_equal_file EXPECTED OUTPUT @@ -475,6 +490,7 @@ cat c_head2 - c_tail <<'EOF' | test_C ${MAIL_DIR} notmuch_tags_t *result; result = notmuch_message_get_tags (message); printf("%d\n%d\n", message != NULL, result == NULL); + stat = NOTMUCH_STATUS_XAPIAN_EXCEPTION; } EOF cat < EXPECTED @@ -482,6 +498,7 @@ cat < EXPECTED 1 1 == stderr == +A Xapian exception occurred at lib/message.cc:XXX: Database has been closed EOF test_expect_equal_file EXPECTED OUTPUT @@ -491,6 +508,7 @@ cat c_head2 - c_tail <<'EOF' | test_C ${MAIL_DIR} int result; result = notmuch_message_count_files (message); printf("%d\n%d\n", message != NULL, result < 0); + stat = NOTMUCH_STATUS_XAPIAN_EXCEPTION; } EOF cat < EXPECTED @@ -498,15 +516,15 @@ cat < EXPECTED 1 1 == stderr == +A Xapian exception occurred at lib/message.cc:XXX: Database has been closed EOF test_expect_equal_file EXPECTED OUTPUT test_begin_subtest "Handle adding tag with closed database" cat c_head2 - c_tail <<'EOF' | test_C ${MAIL_DIR} { - notmuch_status_t status; - status = notmuch_message_add_tag (message, "boom"); - printf("%d\n%d\n", message != NULL, status == NOTMUCH_STATUS_XAPIAN_EXCEPTION); + stat = notmuch_message_add_tag (message, "boom"); + printf("%d\n%d\n", message != NULL, stat == NOTMUCH_STATUS_XAPIAN_EXCEPTION); } EOF cat < EXPECTED @@ -514,15 +532,15 @@ cat < EXPECTED 1 1 == stderr == +A Xapian exception occurred at lib/message.cc:XXX: Database has been closed EOF test_expect_equal_file EXPECTED OUTPUT test_begin_subtest "Handle removing tag with closed database" cat c_head2 - c_tail <<'EOF' | test_C ${MAIL_DIR} { - notmuch_status_t status; - status = notmuch_message_remove_tag (message, "boom"); - printf("%d\n%d\n", message != NULL, status == NOTMUCH_STATUS_XAPIAN_EXCEPTION); + stat = notmuch_message_remove_tag (message, "boom"); + printf("%d\n%d\n", message != NULL, stat == NOTMUCH_STATUS_XAPIAN_EXCEPTION); } EOF cat < EXPECTED @@ -530,6 +548,7 @@ cat < EXPECTED 1 1 == stderr == +A Xapian exception occurred at lib/message.cc:XXX: Database has been closed EOF test_expect_equal_file EXPECTED OUTPUT @@ -539,6 +558,7 @@ cat c_head2 - c_tail <<'EOF' | test_C ${MAIL_DIR} notmuch_bool_t is_set = -1; is_set = notmuch_message_has_maildir_flag (message, 'S'); printf("%d\n%d\n", message != NULL, is_set == FALSE || is_set == TRUE); + stat = NOTMUCH_STATUS_XAPIAN_EXCEPTION; } EOF cat < EXPECTED @@ -546,16 +566,16 @@ cat < EXPECTED 1 1 == stderr == +A Xapian exception occurred at lib/message.cc:XXX: Database has been closed EOF test_expect_equal_file EXPECTED OUTPUT test_begin_subtest "Handle checking maildir flag with closed db (new API)" cat c_head2 - c_tail <<'EOF' | test_C ${MAIL_DIR} { - notmuch_status_t status; notmuch_bool_t out; - status = notmuch_message_has_maildir_flag_st (message, 'S', &out); - printf("%d\n%d\n", message != NULL, status == NOTMUCH_STATUS_XAPIAN_EXCEPTION); + stat = notmuch_message_has_maildir_flag_st (message, 'S', &out); + printf("%d\n%d\n", message != NULL, stat == NOTMUCH_STATUS_XAPIAN_EXCEPTION); } EOF cat < EXPECTED @@ -563,15 +583,15 @@ cat < EXPECTED 1 1 == stderr == +A Xapian exception occurred at lib/message.cc:XXX: Database has been closed EOF test_expect_equal_file EXPECTED OUTPUT test_begin_subtest "Handle converting maildir flags to tags with closed db" cat c_head2 - c_tail <<'EOF' | test_C ${MAIL_DIR} { - notmuch_status_t status; - status = notmuch_message_maildir_flags_to_tags (message); - printf("%d\n%d\n", message != NULL, status == NOTMUCH_STATUS_XAPIAN_EXCEPTION); + stat = notmuch_message_maildir_flags_to_tags (message); + printf("%d\n%d\n", message != NULL, stat == NOTMUCH_STATUS_XAPIAN_EXCEPTION); } EOF cat < EXPECTED @@ -579,15 +599,15 @@ cat < EXPECTED 1 1 == stderr == +A Xapian exception occurred at lib/message.cc:XXX: Database has been closed EOF test_expect_equal_file EXPECTED OUTPUT test_begin_subtest "Handle removing all tags with closed db" cat c_head2 - c_tail <<'EOF' | test_C ${MAIL_DIR} { - notmuch_status_t status; - status = notmuch_message_remove_all_tags (message); - printf("%d\n%d\n", message != NULL, status == NOTMUCH_STATUS_XAPIAN_EXCEPTION); + stat = notmuch_message_remove_all_tags (message); + printf("%d\n%d\n", message != NULL, stat == NOTMUCH_STATUS_XAPIAN_EXCEPTION); } EOF cat < EXPECTED @@ -595,15 +615,15 @@ cat < EXPECTED 1 1 == stderr == +A Xapian exception occurred at lib/message.cc:XXX: Database has been closed EOF test_expect_equal_file EXPECTED OUTPUT test_begin_subtest "Handle freezing message with closed db" cat c_head2 - c_tail <<'EOF' | test_C ${MAIL_DIR} { - notmuch_status_t status; - status = notmuch_message_freeze (message); - printf("%d\n%d\n", message != NULL, status == NOTMUCH_STATUS_SUCCESS); + stat = notmuch_message_freeze (message); + printf("%d\n%d\n", message != NULL, stat == NOTMUCH_STATUS_SUCCESS); } EOF cat < EXPECTED @@ -617,9 +637,8 @@ test_expect_equal_file EXPECTED OUTPUT test_begin_subtest "Handle thawing message with closed db" cat c_head2 - c_tail <<'EOF' | test_C ${MAIL_DIR} { - notmuch_status_t status; - status = notmuch_message_thaw (message); - printf("%d\n%d\n", message != NULL, status == NOTMUCH_STATUS_UNBALANCED_FREEZE_THAW); + stat = notmuch_message_thaw (message); + printf("%d\n%d\n", message != NULL, stat == NOTMUCH_STATUS_UNBALANCED_FREEZE_THAW); } EOF cat < EXPECTED @@ -664,9 +683,8 @@ test_expect_equal_file EXPECTED OUTPUT test_begin_subtest "Handle reindexing message with closed db" cat c_head2 - c_tail <<'EOF' | test_C ${MAIL_DIR} { - notmuch_status_t status; - status = notmuch_message_reindex (message, NULL); - printf("%d\n%d\n", message != NULL, status == NOTMUCH_STATUS_XAPIAN_EXCEPTION); + stat = notmuch_message_reindex (message, NULL); + printf("%d\n%d\n", message != NULL, stat == NOTMUCH_STATUS_XAPIAN_EXCEPTION); } EOF cat < EXPECTED @@ -674,6 +692,7 @@ cat < EXPECTED 1 1 == stderr == +A Xapian exception occurred at lib/message.cc:XXX: Database has been closed EOF test_expect_equal_file EXPECTED OUTPUT From f3a56b5f6d02d72c53ae22be9a4c65c86c2faffc Mon Sep 17 00:00:00 2001 From: David Bremner Date: Mon, 13 Jul 2020 21:21:03 -0300 Subject: [PATCH 267/427] test: regression tests for n_d_status_string and n_d_get_path These do not crash on a closed database, and we want to keep it that way. Start a new file of tests as T560-lib-error was starting to get unwieldy. --- test/T562-lib-database.sh | 70 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 70 insertions(+) create mode 100755 test/T562-lib-database.sh diff --git a/test/T562-lib-database.sh b/test/T562-lib-database.sh new file mode 100755 index 00000000..c869341a --- /dev/null +++ b/test/T562-lib-database.sh @@ -0,0 +1,70 @@ +#!/usr/bin/env bash +test_description="error reporting for library" + +. $(dirname "$0")/test-lib.sh || exit 1 + +add_email_corpus + +test_begin_subtest "building database" +test_expect_success "NOTMUCH_NEW" + +cat < c_head +#include +#include +#include +int main (int argc, char** argv) +{ + notmuch_database_t *db; + notmuch_status_t stat; + char *msg = NULL; + + stat = notmuch_database_open_verbose (argv[1], NOTMUCH_DATABASE_MODE_READ_WRITE, &db, &msg); + if (stat != NOTMUCH_STATUS_SUCCESS) { + fprintf (stderr, "error opening database: %d %s\n", stat, msg ? msg : ""); + exit (1); + } +EOF + +cat <<'EOF' > c_tail + if (stat) { + const char *stat_str = notmuch_database_status_string (db); + if (stat_str) + fputs (stat_str, stderr); + } + +} +EOF + +test_begin_subtest "get status_string with closed db" +cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR} + { + const char *str; + EXPECT0(notmuch_database_close (db)); + str = notmuch_database_status_string (db); + printf("%d\n", str == NULL); + } +EOF +cat < EXPECTED +== stdout == +1 +== stderr == +EOF +test_expect_equal_file EXPECTED OUTPUT + +test_begin_subtest "get path with closed db" +cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR} + { + const char *path; + EXPECT0(notmuch_database_close (db)); + path = notmuch_database_get_path (db); + printf("%s\n", path); + } +EOF +cat < EXPECTED +== stdout == +MAIL_DIR +== stderr == +EOF +test_expect_equal_file EXPECTED OUTPUT + +test_done From 920dc56e605405bb7413d958ac80b8b84d7a8a0f Mon Sep 17 00:00:00 2001 From: David Bremner Date: Mon, 13 Jul 2020 21:30:52 -0300 Subject: [PATCH 268/427] test: add known broken test for n_d_get_version on closed db This should not crash, but it does currently. --- test/T562-lib-database.sh | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/test/T562-lib-database.sh b/test/T562-lib-database.sh index c869341a..b8fba7d6 100755 --- a/test/T562-lib-database.sh +++ b/test/T562-lib-database.sh @@ -1,5 +1,5 @@ #!/usr/bin/env bash -test_description="error reporting for library" +test_description="notmuch_database_* API" . $(dirname "$0")/test-lib.sh || exit 1 @@ -67,4 +67,21 @@ MAIL_DIR EOF test_expect_equal_file EXPECTED OUTPUT +test_begin_subtest "get version with closed db" +test_subtest_known_broken +cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR} + { + unsigned int version; + EXPECT0(notmuch_database_close (db)); + version = notmuch_database_get_version (db); + printf ("%u\n", version); + } +EOF +cat < EXPECTED +== stdout == +0 +== stderr == +EOF +test_expect_equal_file EXPECTED OUTPUT + test_done From 095d3d7134f5668b96cf1d70997d3f110b03c898 Mon Sep 17 00:00:00 2001 From: David Bremner Date: Tue, 14 Jul 2020 08:25:28 -0300 Subject: [PATCH 269/427] lib: move deallocation of memory from n_d_close to n_d_destroy In order to mimic the "best effort" API of Xapian to provide information from a closed database when possible, do not destroy the Xapian database object too early. Because the pointer to a Xapian database is no longer nulled on close, introduce a flag to track whether the notmuch database is open or not. --- lib/database-private.h | 2 +- lib/database.cc | 35 ++++++++++++++++++++--------------- 2 files changed, 21 insertions(+), 16 deletions(-) diff --git a/lib/database-private.h b/lib/database-private.h index 76359007..d2c25313 100644 --- a/lib/database-private.h +++ b/lib/database-private.h @@ -194,7 +194,7 @@ struct _notmuch_database { /* true if changes have been made in this atomic section */ bool atomic_dirty; Xapian::Database *xapian_db; - + bool open; /* Bit mask of features used by this database. This is a * bitwise-OR of NOTMUCH_FEATURE_* values (above). */ enum _notmuch_features features; diff --git a/lib/database.cc b/lib/database.cc index 2f794164..cfb19ccb 100644 --- a/lib/database.cc +++ b/lib/database.cc @@ -1076,6 +1076,10 @@ notmuch_database_open_verbose (const char *path, *database = notmuch; else talloc_free (notmuch); + + if (notmuch) + notmuch->open = true; + return status; } @@ -1087,7 +1091,7 @@ notmuch_database_close (notmuch_database_t *notmuch) /* Many Xapian objects (and thus notmuch objects) hold references to * the database, so merely deleting the database may not suffice to * close it. Thus, we explicitly close it here. */ - if (notmuch->xapian_db != NULL) { + if (notmuch->open) { try { /* If there's an outstanding transaction, it's unclear if * closing the Xapian database commits everything up to @@ -1110,20 +1114,7 @@ notmuch_database_close (notmuch_database_t *notmuch) } } } - - delete notmuch->term_gen; - notmuch->term_gen = NULL; - delete notmuch->query_parser; - notmuch->query_parser = NULL; - delete notmuch->xapian_db; - notmuch->xapian_db = NULL; - delete notmuch->value_range_processor; - notmuch->value_range_processor = NULL; - delete notmuch->date_range_processor; - notmuch->date_range_processor = NULL; - delete notmuch->last_mod_range_processor; - notmuch->last_mod_range_processor = NULL; - + notmuch->open = false; return status; } @@ -1336,6 +1327,20 @@ notmuch_database_destroy (notmuch_database_t *notmuch) notmuch_status_t status; status = notmuch_database_close (notmuch); + + delete notmuch->term_gen; + notmuch->term_gen = NULL; + delete notmuch->query_parser; + notmuch->query_parser = NULL; + delete notmuch->xapian_db; + notmuch->xapian_db = NULL; + delete notmuch->value_range_processor; + notmuch->value_range_processor = NULL; + delete notmuch->date_range_processor; + notmuch->date_range_processor = NULL; + delete notmuch->last_mod_range_processor; + notmuch->last_mod_range_processor = NULL; + talloc_free (notmuch); return status; From ab456541924dcb1e1c6d6c7ba1781011e3453f27 Mon Sep 17 00:00:00 2001 From: David Bremner Date: Tue, 14 Jul 2020 19:31:10 -0300 Subject: [PATCH 270/427] lib/n_d_get_version: catch exceptions and clarify the API notmuch_database_get_version previously returned 0 on some errors, but did not document this. Luckily 0 is not a valid database version. --- lib/database.cc | 19 ++++++++++++++++++- lib/notmuch.h | 3 +++ test/T562-lib-database.sh | 5 +++-- 3 files changed, 24 insertions(+), 3 deletions(-) diff --git a/lib/database.cc b/lib/database.cc index cfb19ccb..8ec91987 100644 --- a/lib/database.cc +++ b/lib/database.cc @@ -58,6 +58,17 @@ typedef struct { #define DB_ACTION Xapian::DB_CREATE_OR_OPEN #endif +#define LOG_XAPIAN_EXCEPTION(message, error) _log_xapian_exception (__location__, message, error) + +static void +_log_xapian_exception (const char *where, notmuch_database_t *notmuch, const Xapian::Error error) { + _notmuch_database_log (notmuch, + "A Xapian exception occurred at %s: %s\n", + where, + error.get_msg ().c_str ()); + notmuch->exception_reported = true; +} + /* Here's the current schema for our database (for NOTMUCH_DATABASE_VERSION): * * We currently have three different types of documents (mail, ghost, @@ -1360,7 +1371,13 @@ notmuch_database_get_version (notmuch_database_t *notmuch) const char *str; char *end; - version_string = notmuch->xapian_db->get_metadata ("version"); + try { + version_string = notmuch->xapian_db->get_metadata ("version"); + } catch (const Xapian::Error &error) { + LOG_XAPIAN_EXCEPTION (notmuch, error); + return 0; + } + if (version_string.empty ()) return 0; diff --git a/lib/notmuch.h b/lib/notmuch.h index 0dd442d4..d9f3003f 100644 --- a/lib/notmuch.h +++ b/lib/notmuch.h @@ -431,6 +431,8 @@ notmuch_database_get_path (notmuch_database_t *database); /** * Return the database format version of the given database. + * + * @retval 0 on error */ unsigned int notmuch_database_get_version (notmuch_database_t *database); @@ -444,6 +446,7 @@ notmuch_database_get_version (notmuch_database_t *database); * fail with NOTMUCH_STATUS_UPGRADE_REQUIRED. This always returns * FALSE for a read-only database because there's no way to upgrade a * read-only database. + * */ notmuch_bool_t notmuch_database_needs_upgrade (notmuch_database_t *database); diff --git a/test/T562-lib-database.sh b/test/T562-lib-database.sh index b8fba7d6..20bed8b4 100755 --- a/test/T562-lib-database.sh +++ b/test/T562-lib-database.sh @@ -15,7 +15,7 @@ cat < c_head int main (int argc, char** argv) { notmuch_database_t *db; - notmuch_status_t stat; + notmuch_status_t stat = NOTMUCH_STATUS_SUCCESS; char *msg = NULL; stat = notmuch_database_open_verbose (argv[1], NOTMUCH_DATABASE_MODE_READ_WRITE, &db, &msg); @@ -68,19 +68,20 @@ EOF test_expect_equal_file EXPECTED OUTPUT test_begin_subtest "get version with closed db" -test_subtest_known_broken cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR} { unsigned int version; EXPECT0(notmuch_database_close (db)); version = notmuch_database_get_version (db); printf ("%u\n", version); + stat = NOTMUCH_STATUS_XAPIAN_EXCEPTION; } EOF cat < EXPECTED == stdout == 0 == stderr == +A Xapian exception occurred at lib/database.cc:XXX: Database has been closed EOF test_expect_equal_file EXPECTED OUTPUT From 964e783fd42ae870d2aa24a4e4ff14bc5c7a1945 Mon Sep 17 00:00:00 2001 From: David Bremner Date: Wed, 15 Jul 2020 22:59:58 -0300 Subject: [PATCH 271/427] test: regression test for closing a closed database This does not currently throw an error, and it should stay that way. --- test/T562-lib-database.sh | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/test/T562-lib-database.sh b/test/T562-lib-database.sh index 20bed8b4..fe5555ec 100755 --- a/test/T562-lib-database.sh +++ b/test/T562-lib-database.sh @@ -85,4 +85,19 @@ A Xapian exception occurred at lib/database.cc:XXX: Database has been closed EOF test_expect_equal_file EXPECTED OUTPUT +test_begin_subtest "re-close a closed db" +cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR} + { + EXPECT0(notmuch_database_close (db)); + stat = notmuch_database_close (db); + printf ("%d\n", stat); + } +EOF +cat < EXPECTED +== stdout == +0 +== stderr == +EOF +test_expect_equal_file EXPECTED OUTPUT + test_done From 37dc6045e492884ef81c73049a9ed884ebd2c2db Mon Sep 17 00:00:00 2001 From: David Bremner Date: Wed, 15 Jul 2020 23:05:54 -0300 Subject: [PATCH 272/427] test: add regression tests for notmuch database destroy Either an open or closed database should be ok to destroy --- test/T562-lib-database.sh | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/test/T562-lib-database.sh b/test/T562-lib-database.sh index fe5555ec..83499d4a 100755 --- a/test/T562-lib-database.sh +++ b/test/T562-lib-database.sh @@ -100,4 +100,35 @@ cat < EXPECTED EOF test_expect_equal_file EXPECTED OUTPUT +test_begin_subtest "destroy a closed db" +cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR} + { + unsigned int version; + EXPECT0(notmuch_database_close (db)); + stat = notmuch_database_destroy (db); + printf ("%d\n", stat); + } +EOF +cat < EXPECTED +== stdout == +0 +== stderr == +EOF +test_expect_equal_file EXPECTED OUTPUT + +test_begin_subtest "destroy an open db" +cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR} + { + unsigned int version; + stat = notmuch_database_destroy (db); + printf ("%d\n", stat); + } +EOF +cat < EXPECTED +== stdout == +0 +== stderr == +EOF +test_expect_equal_file EXPECTED OUTPUT + test_done From cf90431cbf2899d675bf09e945c7cbf8f1b4728f Mon Sep 17 00:00:00 2001 From: David Bremner Date: Thu, 16 Jul 2020 19:22:05 -0300 Subject: [PATCH 273/427] test: add known broken test for n_d_needs_upgrade It's a bit arbitrary which value to return for errors, but the same argument as for read only databases applies for errors. --- test/T562-lib-database.sh | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/test/T562-lib-database.sh b/test/T562-lib-database.sh index 83499d4a..d7f70415 100755 --- a/test/T562-lib-database.sh +++ b/test/T562-lib-database.sh @@ -131,4 +131,24 @@ cat < EXPECTED EOF test_expect_equal_file EXPECTED OUTPUT +test_begin_subtest "check a closed db for upgrade" +test_subtest_known_broken +cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR} + { + notmuch_bool_t ret; + + EXPECT0(notmuch_database_close (db)); + ret = notmuch_database_needs_upgrade (db); + printf ("%d\n", ret == FALSE); + stat = NOTMUCH_STATUS_XAPIAN_EXCEPTION; + } +EOF +cat < EXPECTED +== stdout == +1 +== stderr == +A Xapian exception occurred at lib/database.cc:XXX: Database has been closed +EOF +test_expect_equal_file EXPECTED OUTPUT + test_done From f25fc8e2115fc0fa6fa2a6d1cf3ed2747e163886 Mon Sep 17 00:00:00 2001 From: David Bremner Date: Thu, 16 Jul 2020 19:28:22 -0300 Subject: [PATCH 274/427] lib/n_d_needs_upgrade: handle error return from n_d_get_version Also clarify documentation of error return from n_d_needs_upgrade. --- lib/database.cc | 14 +++++++++++--- lib/notmuch.h | 2 ++ test/T562-lib-database.sh | 1 - 3 files changed, 13 insertions(+), 4 deletions(-) diff --git a/lib/database.cc b/lib/database.cc index 8ec91987..4ff748f6 100644 --- a/lib/database.cc +++ b/lib/database.cc @@ -1395,9 +1395,17 @@ notmuch_database_get_version (notmuch_database_t *notmuch) notmuch_bool_t notmuch_database_needs_upgrade (notmuch_database_t *notmuch) { - return notmuch->mode == NOTMUCH_DATABASE_MODE_READ_WRITE && - ((NOTMUCH_FEATURES_CURRENT & ~notmuch->features) || - (notmuch_database_get_version (notmuch) < NOTMUCH_DATABASE_VERSION)); + unsigned int version; + + if (notmuch->mode != NOTMUCH_DATABASE_MODE_READ_WRITE) + return FALSE; + + if (NOTMUCH_FEATURES_CURRENT & ~notmuch->features) + return TRUE; + + version = notmuch_database_get_version (notmuch); + + return (version > 0 && version < NOTMUCH_DATABASE_VERSION); } static volatile sig_atomic_t do_progress_notify = 0; diff --git a/lib/notmuch.h b/lib/notmuch.h index d9f3003f..f9e9cc41 100644 --- a/lib/notmuch.h +++ b/lib/notmuch.h @@ -447,6 +447,8 @@ notmuch_database_get_version (notmuch_database_t *database); * FALSE for a read-only database because there's no way to upgrade a * read-only database. * + * Also returns FALSE if an error occurs accessing the database. + * */ notmuch_bool_t notmuch_database_needs_upgrade (notmuch_database_t *database); diff --git a/test/T562-lib-database.sh b/test/T562-lib-database.sh index d7f70415..cd202c6e 100755 --- a/test/T562-lib-database.sh +++ b/test/T562-lib-database.sh @@ -132,7 +132,6 @@ EOF test_expect_equal_file EXPECTED OUTPUT test_begin_subtest "check a closed db for upgrade" -test_subtest_known_broken cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR} { notmuch_bool_t ret; From 3f121d636e7f67e12e19d0dc386ad9360d900ab4 Mon Sep 17 00:00:00 2001 From: David Bremner Date: Thu, 16 Jul 2020 19:34:02 -0300 Subject: [PATCH 275/427] test: regression test for n_d_upgrade The logic is that if it's acceptable to return SUCCESS for read only database, it's acceptable for a closed one. --- test/T562-lib-database.sh | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/test/T562-lib-database.sh b/test/T562-lib-database.sh index cd202c6e..73ea0d2a 100755 --- a/test/T562-lib-database.sh +++ b/test/T562-lib-database.sh @@ -150,4 +150,21 @@ A Xapian exception occurred at lib/database.cc:XXX: Database has been closed EOF test_expect_equal_file EXPECTED OUTPUT +test_begin_subtest "upgrade a closed db" +cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR} + { + notmuch_bool_t ret; + + EXPECT0(notmuch_database_close (db)); + stat = notmuch_database_upgrade (db, NULL, NULL); + printf ("%d\n", ret == NOTMUCH_STATUS_SUCCESS); + } +EOF +cat < EXPECTED +== stdout == +1 +== stderr == +EOF +test_expect_equal_file EXPECTED OUTPUT + test_done From bcb02dd8fc176729525869a969c0bc8de2acfaa9 Mon Sep 17 00:00:00 2001 From: David Bremner Date: Thu, 16 Jul 2020 19:48:19 -0300 Subject: [PATCH 276/427] test: add regression test for n_d_{begin,end}_atomic Xapian currently succeeds to begin/end a transaction on a closed database, or at least does not throw an exception. Make the test robust against this changing. --- test/T562-lib-database.sh | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/test/T562-lib-database.sh b/test/T562-lib-database.sh index 73ea0d2a..857bdd12 100755 --- a/test/T562-lib-database.sh +++ b/test/T562-lib-database.sh @@ -167,4 +167,39 @@ cat < EXPECTED EOF test_expect_equal_file EXPECTED OUTPUT +test_begin_subtest "begin atomic section for a closed db" +cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR} + { + EXPECT0(notmuch_database_close (db)); + stat = notmuch_database_begin_atomic (db); + printf ("%d\n", stat == NOTMUCH_STATUS_SUCCESS || + stat == NOTMUCH_STATUS_XAPIAN_EXCEPTION); + stat = NOTMUCH_STATUS_SUCCESS; + } +EOF +cat < EXPECTED +== stdout == +1 +== stderr == +EOF +test_expect_equal_file EXPECTED OUTPUT + +test_begin_subtest "end atomic section for a closed db" +cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR} + { + EXPECT0(notmuch_database_close (db)); + EXPECT0(notmuch_database_begin_atomic (db)); + stat = notmuch_database_end_atomic (db); + printf ("%d\n", stat == NOTMUCH_STATUS_SUCCESS || + stat == NOTMUCH_STATUS_XAPIAN_EXCEPTION); + stat = NOTMUCH_STATUS_SUCCESS; + } +EOF +cat < EXPECTED +== stdout == +1 +== stderr == +EOF +test_expect_equal_file EXPECTED OUTPUT + test_done From 959cb4b7a7d2a723a297468c7a0a2c12e9ec95bd Mon Sep 17 00:00:00 2001 From: David Bremner Date: Thu, 16 Jul 2020 20:04:12 -0300 Subject: [PATCH 277/427] test: regression test for n_d_get_revision This function only accesses data cached by notmuch, so being closed is not a problem. --- test/T562-lib-database.sh | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/test/T562-lib-database.sh b/test/T562-lib-database.sh index 857bdd12..620a9ca3 100755 --- a/test/T562-lib-database.sh +++ b/test/T562-lib-database.sh @@ -202,4 +202,22 @@ cat < EXPECTED EOF test_expect_equal_file EXPECTED OUTPUT +test_begin_subtest "get revision for a closed db" +cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR} + { + const char *uuid; + unsigned long rev; + + EXPECT0(notmuch_database_close (db)); + rev = notmuch_database_get_revision (db, &uuid); + printf ("%d\n", rev, uuid); + } +EOF +cat < EXPECTED +== stdout == +53 +== stderr == +EOF +test_expect_equal_file EXPECTED OUTPUT + test_done From 1ca805a9620c7e09cb6c4022e6ffe9b95a3fc04f Mon Sep 17 00:00:00 2001 From: David Bremner Date: Fri, 17 Jul 2020 07:38:09 -0300 Subject: [PATCH 278/427] test: add regression test for n_d_get_directory At least this exception is already handled correctly. --- test/T562-lib-database.sh | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/test/T562-lib-database.sh b/test/T562-lib-database.sh index 620a9ca3..eed88f46 100755 --- a/test/T562-lib-database.sh +++ b/test/T562-lib-database.sh @@ -220,4 +220,21 @@ cat < EXPECTED EOF test_expect_equal_file EXPECTED OUTPUT +test_begin_subtest "get directory for a closed db" +cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR} + { + notmuch_directory_t *dir; + EXPECT0(notmuch_database_close (db)); + stat = notmuch_database_get_directory (db, "/nonexistent", &dir); + printf ("%d\n", stat == NOTMUCH_STATUS_XAPIAN_EXCEPTION); + } +EOF +cat < EXPECTED +== stdout == +1 +== stderr == +A Xapian exception occurred creating a directory: Database has been closed. +EOF +test_expect_equal_file EXPECTED OUTPUT + test_done From 9ab3e2e77d2881004cd2fdc1035dad7052ae735c Mon Sep 17 00:00:00 2001 From: David Bremner Date: Fri, 17 Jul 2020 07:51:24 -0300 Subject: [PATCH 279/427] test: regression test for n_d_index_file closed db Current exception handling seems OK, at least for this case. --- test/T562-lib-database.sh | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/test/T562-lib-database.sh b/test/T562-lib-database.sh index eed88f46..4e5655e8 100755 --- a/test/T562-lib-database.sh +++ b/test/T562-lib-database.sh @@ -12,6 +12,7 @@ cat < c_head #include #include #include +#include int main (int argc, char** argv) { notmuch_database_t *db; @@ -237,4 +238,23 @@ A Xapian exception occurred creating a directory: Database has been closed. EOF test_expect_equal_file EXPECTED OUTPUT +# XXX TODO: test with relative path +test_begin_subtest "index file with a closed db" +cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR} + { + notmuch_message_t *msg; + const char *path = talloc_asprintf(db, "%s/01:2,", argv[1]); + EXPECT0(notmuch_database_close (db)); + stat = notmuch_database_index_file (db, path, NULL, &msg); + printf ("%d\n", stat == NOTMUCH_STATUS_XAPIAN_EXCEPTION); + } +EOF +cat < EXPECTED +== stdout == +1 +== stderr == +A Xapian exception occurred finding message: Database has been closed. +EOF +test_expect_equal_file EXPECTED OUTPUT + test_done From be3f4aec3f9006d066fe092e7fe0810c5a0f86e0 Mon Sep 17 00:00:00 2001 From: David Bremner Date: Fri, 17 Jul 2020 08:02:14 -0300 Subject: [PATCH 280/427] test: add known broken test for indexing relative path The current code seems to look for the path relative to the current working directory, rather than the mail store root. --- test/T562-lib-database.sh | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/test/T562-lib-database.sh b/test/T562-lib-database.sh index 4e5655e8..e8263a14 100755 --- a/test/T562-lib-database.sh +++ b/test/T562-lib-database.sh @@ -238,7 +238,6 @@ A Xapian exception occurred creating a directory: Database has been closed. EOF test_expect_equal_file EXPECTED OUTPUT -# XXX TODO: test with relative path test_begin_subtest "index file with a closed db" cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR} { @@ -257,4 +256,21 @@ A Xapian exception occurred finding message: Database has been closed. EOF test_expect_equal_file EXPECTED OUTPUT +generate_message '[filename]=relative_path' +test_begin_subtest "index file (relative path)" +test_subtest_known_broken +cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR} + { + notmuch_message_t *msg; + stat = notmuch_database_index_file (db, "relative_path", NULL, &msg); + printf ("%d\n", stat == NOTMUCH_STATUS_SUCCESS); + } +EOF +cat < EXPECTED +== stdout == +1 +== stderr == +EOF +test_expect_equal_file EXPECTED OUTPUT + test_done From c477d7ce311335fda16a15e624ca3931d79144cf Mon Sep 17 00:00:00 2001 From: David Bremner Date: Sat, 18 Jul 2020 21:11:28 -0300 Subject: [PATCH 281/427] lib: convert relative filenames to absolute in n_d_index_file The API docs promise to handle relative filenames, but the code did not do it. Also check for files outside the mail root, as implied by the API description. This fixes the bug reported at id:87sgdqo0rz.fsf@tethera.net --- lib/message-file.c | 24 ++++++++++++++++++++---- test/T562-lib-database.sh | 18 +++++++++++++++++- 2 files changed, 37 insertions(+), 5 deletions(-) diff --git a/lib/message-file.c b/lib/message-file.c index e1db26fb..311bd478 100644 --- a/lib/message-file.c +++ b/lib/message-file.c @@ -64,21 +64,37 @@ _notmuch_message_file_open_ctx (notmuch_database_t *notmuch, if (unlikely (message == NULL)) return NULL; - message->filename = talloc_strdup (message, filename); + const char *prefix = notmuch_database_get_path (notmuch); + if (prefix == NULL) + goto FAIL; + + if (*filename == '/') { + if (strncmp (filename, prefix, strlen(prefix)) != 0) { + _notmuch_database_log (notmuch, "Error opening %s: path outside mail root\n", + filename); + errno = 0; + goto FAIL; + } + message->filename = talloc_strdup (message, filename); + } else { + message->filename = talloc_asprintf(message, "%s/%s", prefix, filename); + } + if (message->filename == NULL) goto FAIL; talloc_set_destructor (message, _notmuch_message_file_destructor); - message->stream = g_mime_stream_gzfile_open (filename); + message->stream = g_mime_stream_gzfile_open (message->filename); if (message->stream == NULL) goto FAIL; return message; FAIL: - _notmuch_database_log (notmuch, "Error opening %s: %s\n", - filename, strerror (errno)); + if (errno) + _notmuch_database_log (notmuch, "Error opening %s: %s\n", + filename, strerror (errno)); _notmuch_message_file_close (message); return NULL; diff --git a/test/T562-lib-database.sh b/test/T562-lib-database.sh index e8263a14..e64f0f12 100755 --- a/test/T562-lib-database.sh +++ b/test/T562-lib-database.sh @@ -258,7 +258,6 @@ test_expect_equal_file EXPECTED OUTPUT generate_message '[filename]=relative_path' test_begin_subtest "index file (relative path)" -test_subtest_known_broken cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR} { notmuch_message_t *msg; @@ -273,4 +272,21 @@ cat < EXPECTED EOF test_expect_equal_file EXPECTED OUTPUT +test_begin_subtest "index file (absolute path outside mail root)" +cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR} + { + notmuch_message_t *msg; + stat = notmuch_database_index_file (db, "/dev/zero", NULL, &msg); + printf ("%d\n", stat == NOTMUCH_STATUS_FILE_ERROR); + } +EOF +cat < EXPECTED +== stdout == +1 +== stderr == +Error opening /dev/zero: path outside mail root +EOF +test_expect_equal_file EXPECTED OUTPUT + + test_done From 97c887282a6688323822137a21ea0bfc2344b5c9 Mon Sep 17 00:00:00 2001 From: David Bremner Date: Sun, 19 Jul 2020 07:13:06 -0300 Subject: [PATCH 282/427] test: add known broken test for notmuch_database_remove_message The current error message is a bit confusing; fix in next commit. --- test/T562-lib-database.sh | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/test/T562-lib-database.sh b/test/T562-lib-database.sh index e64f0f12..75a31091 100755 --- a/test/T562-lib-database.sh +++ b/test/T562-lib-database.sh @@ -288,5 +288,21 @@ Error opening /dev/zero: path outside mail root EOF test_expect_equal_file EXPECTED OUTPUT +test_begin_subtest "remove message file with a closed db" +test_subtest_known_broken +cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR} + { + EXPECT0(notmuch_database_close (db)); + stat = notmuch_database_remove_message (db, "01:2,"); + printf ("%d\n", stat == NOTMUCH_STATUS_XAPIAN_EXCEPTION); + } +EOF +cat < EXPECTED +== stdout == +1 +== stderr == +A Xapian exception occurred finding/creating a directory: Database has been closed. +EOF +test_expect_equal_file EXPECTED OUTPUT test_done From ce360ce4c99597946949317aa40892536c9ebc05 Mon Sep 17 00:00:00 2001 From: David Bremner Date: Sun, 19 Jul 2020 07:36:47 -0300 Subject: [PATCH 283/427] lib: rename _n_d_create to _n_d_find_or_create The error message and name were confusing when called in some "read only" context. --- lib/database.cc | 10 +++++----- lib/directory.cc | 10 +++++----- lib/notmuch-private.h | 8 ++++---- test/T560-lib-error.sh | 2 +- test/T562-lib-database.sh | 3 +-- 5 files changed, 16 insertions(+), 17 deletions(-) diff --git a/lib/database.cc b/lib/database.cc index 4ff748f6..2aff56be 100644 --- a/lib/database.cc +++ b/lib/database.cc @@ -1605,8 +1605,8 @@ notmuch_database_upgrade (notmuch_database_t *notmuch, mtime = Xapian::sortable_unserialise ( document.get_value (NOTMUCH_VALUE_TIMESTAMP)); - directory = _notmuch_directory_create (notmuch, term.c_str () + 10, - NOTMUCH_FIND_CREATE, &status); + directory = _notmuch_directory_find_or_create (notmuch, term.c_str () + 10, + NOTMUCH_FIND_CREATE, &status); notmuch_directory_set_mtime (directory, mtime); notmuch_directory_destroy (directory); @@ -1878,7 +1878,7 @@ _notmuch_database_find_directory_id (notmuch_database_t *notmuch, return NOTMUCH_STATUS_SUCCESS; } - directory = _notmuch_directory_create (notmuch, path, flags, &status); + directory = _notmuch_directory_find_or_create (notmuch, path, flags, &status); if (status || ! directory) { *directory_id = -1; return status; @@ -1988,8 +1988,8 @@ notmuch_database_get_directory (notmuch_database_t *notmuch, *directory = NULL; try { - *directory = _notmuch_directory_create (notmuch, path, - NOTMUCH_FIND_LOOKUP, &status); + *directory = _notmuch_directory_find_or_create (notmuch, path, + NOTMUCH_FIND_LOOKUP, &status); } catch (const Xapian::Error &error) { _notmuch_database_log (notmuch, "A Xapian exception occurred getting directory: %s.\n", error.get_msg ().c_str ()); diff --git a/lib/directory.cc b/lib/directory.cc index af71f402..09b49245 100644 --- a/lib/directory.cc +++ b/lib/directory.cc @@ -94,10 +94,10 @@ find_directory_document (notmuch_database_t *notmuch, * NOTMUCH_STATUS_SUCCESS and this returns NULL. */ notmuch_directory_t * -_notmuch_directory_create (notmuch_database_t *notmuch, - const char *path, - notmuch_find_flags_t flags, - notmuch_status_t *status_ret) +_notmuch_directory_find_or_create (notmuch_database_t *notmuch, + const char *path, + notmuch_find_flags_t flags, + notmuch_status_t *status_ret) { Xapian::WritableDatabase *db; notmuch_directory_t *directory; @@ -187,7 +187,7 @@ _notmuch_directory_create (notmuch_database_t *notmuch, directory->doc.get_value (NOTMUCH_VALUE_TIMESTAMP)); } catch (const Xapian::Error &error) { _notmuch_database_log (notmuch, - "A Xapian exception occurred creating a directory: %s.\n", + "A Xapian exception occurred finding/creating a directory: %s.\n", error.get_msg ().c_str ()); notmuch->exception_reported = true; notmuch_directory_destroy (directory); diff --git a/lib/notmuch-private.h b/lib/notmuch-private.h index 28ced3a2..2bbbb293 100644 --- a/lib/notmuch-private.h +++ b/lib/notmuch-private.h @@ -251,10 +251,10 @@ _notmuch_database_filename_to_direntry (void *ctx, /* directory.cc */ notmuch_directory_t * -_notmuch_directory_create (notmuch_database_t *notmuch, - const char *path, - notmuch_find_flags_t flags, - notmuch_status_t *status_ret); +_notmuch_directory_find_or_create (notmuch_database_t *notmuch, + const char *path, + notmuch_find_flags_t flags, + notmuch_status_t *status_ret); unsigned int _notmuch_directory_get_document_id (notmuch_directory_t *directory); diff --git a/test/T560-lib-error.sh b/test/T560-lib-error.sh index 8a608341..68180e41 100755 --- a/test/T560-lib-error.sh +++ b/test/T560-lib-error.sh @@ -275,7 +275,7 @@ sed 's/^\(A Xapian exception [^:]*\):.*$/\1/' < OUTPUT > OUTPUT.clean cat <<'EOF' >EXPECTED == stdout == == stderr == -A Xapian exception occurred creating a directory +A Xapian exception occurred finding/creating a directory EOF test_expect_equal_file EXPECTED OUTPUT.clean restore_database diff --git a/test/T562-lib-database.sh b/test/T562-lib-database.sh index 75a31091..f7391d3f 100755 --- a/test/T562-lib-database.sh +++ b/test/T562-lib-database.sh @@ -234,7 +234,7 @@ cat < EXPECTED == stdout == 1 == stderr == -A Xapian exception occurred creating a directory: Database has been closed. +A Xapian exception occurred finding/creating a directory: Database has been closed. EOF test_expect_equal_file EXPECTED OUTPUT @@ -289,7 +289,6 @@ EOF test_expect_equal_file EXPECTED OUTPUT test_begin_subtest "remove message file with a closed db" -test_subtest_known_broken cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR} { EXPECT0(notmuch_database_close (db)); From 64aa8961512b47166954d196fd2ca04b2b22cd42 Mon Sep 17 00:00:00 2001 From: David Bremner Date: Sun, 19 Jul 2020 07:43:36 -0300 Subject: [PATCH 284/427] lib: add regression test for n_d_find_message_by_filename At least this Xapian exception is caught. Make sure it stays that way. --- test/T562-lib-database.sh | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/test/T562-lib-database.sh b/test/T562-lib-database.sh index f7391d3f..745e91b3 100755 --- a/test/T562-lib-database.sh +++ b/test/T562-lib-database.sh @@ -304,4 +304,21 @@ A Xapian exception occurred finding/creating a directory: Database has been clos EOF test_expect_equal_file EXPECTED OUTPUT +test_begin_subtest "find message by filename with a closed db" +cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR} + { + notmuch_message_t *msg; + EXPECT0(notmuch_database_close (db)); + stat = notmuch_database_find_message_by_filename (db, "01:2,", &msg); + printf ("%d\n", stat == NOTMUCH_STATUS_XAPIAN_EXCEPTION); + } +EOF +cat < EXPECTED +== stdout == +1 +== stderr == +A Xapian exception occurred finding/creating a directory: Database has been closed. +EOF +test_expect_equal_file EXPECTED OUTPUT + test_done From dfab190a4089b3d04addd0989053461d43c2bdeb Mon Sep 17 00:00:00 2001 From: David Bremner Date: Sun, 19 Jul 2020 07:57:39 -0300 Subject: [PATCH 285/427] lib: add regresion test for n_d_get_all_tags Existing error handling seems adequate, if not ideal. --- test/T562-lib-database.sh | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/test/T562-lib-database.sh b/test/T562-lib-database.sh index 745e91b3..60e944fc 100755 --- a/test/T562-lib-database.sh +++ b/test/T562-lib-database.sh @@ -321,4 +321,22 @@ A Xapian exception occurred finding/creating a directory: Database has been clos EOF test_expect_equal_file EXPECTED OUTPUT +test_begin_subtest "Handle getting tags from closed database" +cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR} + { + notmuch_tags_t *result; + EXPECT0(notmuch_database_close (db)); + result = notmuch_database_get_all_tags (db); + printf("%d\n", result == NULL); + stat = NOTMUCH_STATUS_XAPIAN_EXCEPTION; + } +EOF +cat < EXPECTED +== stdout == +1 +== stderr == +A Xapian exception occurred getting tags: Database has been closed. +EOF +test_expect_equal_file EXPECTED OUTPUT + test_done From a5e13d52187f9863269a88cc83042c50f1838d47 Mon Sep 17 00:00:00 2001 From: David Bremner Date: Sun, 19 Jul 2020 09:39:16 -0300 Subject: [PATCH 286/427] test: add regression test for n_d_get_config Xapian exceptions seem to handled OK, at least for this case. --- test/T562-lib-database.sh | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/test/T562-lib-database.sh b/test/T562-lib-database.sh index 60e944fc..09988a45 100755 --- a/test/T562-lib-database.sh +++ b/test/T562-lib-database.sh @@ -339,4 +339,20 @@ A Xapian exception occurred getting tags: Database has been closed. EOF test_expect_equal_file EXPECTED OUTPUT +test_begin_subtest "get config from closed database" +cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR} + { + const char *result; + EXPECT0(notmuch_database_close (db)); + stat = notmuch_database_get_config (db, "foo", &result); + printf("%d\n", stat == NOTMUCH_STATUS_XAPIAN_EXCEPTION); + } +EOF +cat < EXPECTED +== stdout == +1 +== stderr == +Error: A Xapian exception occurred getting metadata: Database has been closed +EOF +test_expect_equal_file EXPECTED OUTPUT test_done From f6d74be84071b31e1e8d466bea76dd8d44d54377 Mon Sep 17 00:00:00 2001 From: David Bremner Date: Sun, 19 Jul 2020 09:44:05 -0300 Subject: [PATCH 287/427] test: add known broken test for n_d_set_config Error status is currently lost. --- test/T562-lib-database.sh | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/test/T562-lib-database.sh b/test/T562-lib-database.sh index 09988a45..cdc6fc12 100755 --- a/test/T562-lib-database.sh +++ b/test/T562-lib-database.sh @@ -355,4 +355,22 @@ cat < EXPECTED Error: A Xapian exception occurred getting metadata: Database has been closed EOF test_expect_equal_file EXPECTED OUTPUT + +test_begin_subtest "set config in closed database" +test_subtest_known_broken +cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR} + { + EXPECT0(notmuch_database_close (db)); + stat = notmuch_database_set_config (db, "foo", "bar"); + printf("%d\n", stat == NOTMUCH_STATUS_XAPIAN_EXCEPTION); + } +EOF +cat < EXPECTED +== stdout == +1 +== stderr == +Error: A Xapian exception occurred setting metadata: Database has been closed +EOF +test_expect_equal_file EXPECTED OUTPUT + test_done From b268c8c0711a12b26a3abd3c8dde38a3679999b1 Mon Sep 17 00:00:00 2001 From: David Bremner Date: Sun, 19 Jul 2020 09:51:03 -0300 Subject: [PATCH 288/427] lib: fix error return bug with n_d_set_config. The catch block either needs to return, or the function needs to return "status". Choose the latter for consistency with n_d_get_config. --- lib/config.cc | 2 +- test/T562-lib-database.sh | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/config.cc b/lib/config.cc index 292f0288..20036471 100644 --- a/lib/config.cc +++ b/lib/config.cc @@ -60,7 +60,7 @@ notmuch_database_set_config (notmuch_database_t *notmuch, _notmuch_database_log (notmuch, "Error: A Xapian exception occurred setting metadata: %s\n", error.get_msg ().c_str ()); } - return NOTMUCH_STATUS_SUCCESS; + return status; } static notmuch_status_t diff --git a/test/T562-lib-database.sh b/test/T562-lib-database.sh index cdc6fc12..f767fddf 100755 --- a/test/T562-lib-database.sh +++ b/test/T562-lib-database.sh @@ -357,7 +357,6 @@ EOF test_expect_equal_file EXPECTED OUTPUT test_begin_subtest "set config in closed database" -test_subtest_known_broken cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR} { EXPECT0(notmuch_database_close (db)); From 24cf4381b8e29e62c3a636082414df2d91ec0f22 Mon Sep 17 00:00:00 2001 From: David Bremner Date: Sun, 19 Jul 2020 10:11:13 -0300 Subject: [PATCH 289/427] test: add known broken test for n_d_get_default_indexopts Xapian exceptions are swallowed and turned into default return value. --- test/T562-lib-database.sh | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/test/T562-lib-database.sh b/test/T562-lib-database.sh index f767fddf..32fda72e 100755 --- a/test/T562-lib-database.sh +++ b/test/T562-lib-database.sh @@ -372,4 +372,21 @@ Error: A Xapian exception occurred setting metadata: Database has been closed EOF test_expect_equal_file EXPECTED OUTPUT +test_begin_subtest "get indexopts from closed database" +test_subtest_known_broken +cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR} + { + notmuch_indexopts_t *result; + EXPECT0(notmuch_database_close (db)); + result = notmuch_database_get_default_indexopts (db); + printf("%d\n", result == NULL); + } +EOF +cat < EXPECTED +== stdout == +1 +== stderr == +EOF +test_expect_equal_file EXPECTED OUTPUT + test_done From a2e9eaadaf2728bb09064b274744f930581e443e Mon Sep 17 00:00:00 2001 From: Jonas Bernoulli Date: Mon, 27 Jul 2020 17:25:00 +0200 Subject: [PATCH 290/427] gitignore: Ignore generated sphinx.config --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 1c8705ec..8f3ebec0 100644 --- a/.gitignore +++ b/.gitignore @@ -17,3 +17,4 @@ tags /.stamps *.stamp /bindings/python-cffi/build/ +/sphinx.config From d5d8846c3286ec681c3d46b3fe30d50254224208 Mon Sep 17 00:00:00 2001 From: Jonas Bernoulli Date: Mon, 27 Jul 2020 17:25:01 +0200 Subject: [PATCH 291/427] test: Deal with Emacs 27 switching to lexical scope by default Starting with Emacs 27 undeclared variables in evaluated interactive code uses lexical scope. This includes code passed with '--eval' as we do in the Emacs tests, which also happen to assume dynamic scope. - This can affect variables defined by libraries that we use. We let-bind such variables to change the behavior of functions which we then call with these bindings in effect. If these libraries are not loaded beforehand, then the bindings are lexical and fail to have the effect we intended. At this time only 'smtpmail' has to be loaded explicitly (for the variables let-bound in emacs_deliver_message and emacs_fcc_message). 'message' doesn't have to be loaded explicitly, because loading 'notmuch' (in 'run_emacs') already takes care of that, indirectly. - Our own testing-only variables also have to be declared explicitly. We should have done that anyway, but because of how and where these variables are used it was very easy to overlook that (i.e. it isn't something the byte-compiler ever looks at). Not so in Emacs 27 anymore; here this oversight caused four tests to fail. The numeric values of these variables get incremented by functions that we add to hooks that are run by many tests, not just the tests where we actually inspect the value and therefore take care to let- bind the values to 0 before we begin. The global values therefore have to be numeric values as well. I have chosen -100 instead of 0 as the default in case someone writes a test that inspects the value but forgets to let-bind the value. I hope that the unusual negative value that one is going to see in such a case will help debugging the issue. --- test/test-lib.el | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/test/test-lib.el b/test/test-lib.el index b47b388e..15271b02 100644 --- a/test/test-lib.el +++ b/test/test-lib.el @@ -22,6 +22,12 @@ (require 'cl-lib) +;; Ensure that the dynamic variables that are defined by this library +;; are defined by the time that we let-bind them. This is needed +;; because starting with Emacs 27 undeclared variables in evaluated +;; interactive code (such as our tests) use lexical scope. +(require 'smtpmail) + ;; `read-file-name' by default uses `completing-read' function to read ;; user input. It does not respect `standard-input' variable which we ;; use in tests to provide user input. So replace it with a plain @@ -113,6 +119,12 @@ nothing." (add-hook-counter 'notmuch-hello-mode-hook) (add-hook-counter 'notmuch-hello-refresh-hook) +(defvar notmuch-hello-mode-hook-counter -100 + "Tests that care about this counter must let-bind it to 0.") + +(defvar notmuch-hello-refresh-hook-counter -100 + "Tests that care about this counter must let-bind it to 0.") + (defadvice notmuch-search-process-filter (around pessimal activate disable) "Feed notmuch-search-process-filter one character at a time." (let ((string (ad-get-arg 1))) From 7b752e9eb479426fdf87ab10ca64fd057ee88252 Mon Sep 17 00:00:00 2001 From: Jonas Bernoulli Date: Mon, 27 Jul 2020 17:25:02 +0200 Subject: [PATCH 292/427] test: Remove misguided emacs testing utilities The goal of this abstraction was to save space. But that failed as the result actually was that four trivial lines got replace with 15 fairly complicated lines. The opposite of what it was supposed to do. Also it made it harder to come up with the fix in the previous commit; simply grepping for the relevant symbols did not work because they get constructed at run-time instead of appearing in the source file. --- test/test-lib.el | 19 ++++--------------- 1 file changed, 4 insertions(+), 15 deletions(-) diff --git a/test/test-lib.el b/test/test-lib.el index 15271b02..aae9e833 100644 --- a/test/test-lib.el +++ b/test/test-lib.el @@ -104,26 +104,15 @@ running, quit if it terminated." "Initiate orphan watchdog check." (run-at-time 60 60 'orphan-watchdog-check pid)) -(defun hook-counter (hook) - "Count how many times a hook is called. Increments -`hook'-counter variable value if it is bound, otherwise does -nothing." - (let ((counter (intern (concat (symbol-name hook) "-counter")))) - (if (boundp counter) - (set counter (1+ (symbol-value counter)))))) - -(defun add-hook-counter (hook) - "Add hook to count how many times `hook' is called." - (add-hook hook (apply-partially 'hook-counter hook))) - -(add-hook-counter 'notmuch-hello-mode-hook) -(add-hook-counter 'notmuch-hello-refresh-hook) - (defvar notmuch-hello-mode-hook-counter -100 "Tests that care about this counter must let-bind it to 0.") +(add-hook 'notmuch-hello-mode-hook + (lambda () (cl-incf notmuch-hello-mode-hook-counter))) (defvar notmuch-hello-refresh-hook-counter -100 "Tests that care about this counter must let-bind it to 0.") +(add-hook 'notmuch-hello-refresh-hook + (lambda () (cl-incf notmuch-hello-refresh-hook-counter))) (defadvice notmuch-search-process-filter (around pessimal activate disable) "Feed notmuch-search-process-filter one character at a time." From 8b162b40f84e891afa340b72c50b98f62ae57106 Mon Sep 17 00:00:00 2001 From: Jonas Bernoulli Date: Mon, 27 Jul 2020 17:25:03 +0200 Subject: [PATCH 293/427] test: Explicitly state that we want to sign with sender Since Emacs 27 'mml-secure-epg-sign' errors out if we don't opt-in to signing as the sender using 'mml-secure-openpgp-sign-with-sender'. --- test/test-lib.sh | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/test/test-lib.sh b/test/test-lib.sh index 8c331b88..31c858d1 100644 --- a/test/test-lib.sh +++ b/test/test-lib.sh @@ -414,7 +414,9 @@ emacs_fcc_message () (message-goto-body) (insert \"${body}\") $* - (notmuch-mua-send-and-exit))" || return 1 + (let ((mml-secure-smime-sign-with-sender t) + (mml-secure-openpgp-sign-with-sender t)) + (notmuch-mua-send-and-exit)))" || return 1 notmuch new $nmn_args >/dev/null } From c73d510f965cddf4cf45f9895c45fb818519e200 Mon Sep 17 00:00:00 2001 From: David Bremner Date: Sun, 26 Jul 2020 20:31:34 -0300 Subject: [PATCH 294/427] lib: drop two gratuitous assignments to database mode I'm not sure what the point of modifying that right before destroying the object is. In a future commit I want to remove that element of the object, so simplify that task. --- lib/database.cc | 2 -- 1 file changed, 2 deletions(-) diff --git a/lib/database.cc b/lib/database.cc index 2aff56be..b987cb42 100644 --- a/lib/database.cc +++ b/lib/database.cc @@ -994,7 +994,6 @@ notmuch_database_open_verbose (const char *path, " has a newer database format version (%u) than supported by this\n" " version of notmuch (%u).\n", notmuch_path, version, NOTMUCH_DATABASE_VERSION)); - notmuch->mode = NOTMUCH_DATABASE_MODE_READ_ONLY; notmuch_database_destroy (notmuch); notmuch = NULL; status = NOTMUCH_STATUS_FILE_ERROR; @@ -1013,7 +1012,6 @@ notmuch_database_open_verbose (const char *path, " requires features (%s)\n" " not supported by this version of notmuch.\n", notmuch_path, incompat_features)); - notmuch->mode = NOTMUCH_DATABASE_MODE_READ_ONLY; notmuch_database_destroy (notmuch); notmuch = NULL; status = NOTMUCH_STATUS_FILE_ERROR; From d7d4c729ab3b74eaaebe64cb8c7383b0ea06cec0 Mon Sep 17 00:00:00 2001 From: David Bremner Date: Sun, 26 Jul 2020 20:31:35 -0300 Subject: [PATCH 295/427] lib: encapsulate the use of notmuch_database_t field 'mode' The plan is to change the underlying representation. --- lib/database.cc | 18 ++++++++++++------ lib/directory.cc | 2 +- lib/message.cc | 4 ++-- lib/notmuch-private.h | 3 +++ 4 files changed, 18 insertions(+), 9 deletions(-) diff --git a/lib/database.cc b/lib/database.cc index b987cb42..08278235 100644 --- a/lib/database.cc +++ b/lib/database.cc @@ -69,6 +69,12 @@ _log_xapian_exception (const char *where, notmuch_database_t *notmuch, const Xa notmuch->exception_reported = true; } +notmuch_database_mode_t +_notmuch_database_mode (notmuch_database_t *notmuch) +{ + return notmuch->mode; +} + /* Here's the current schema for our database (for NOTMUCH_DATABASE_VERSION): * * We currently have three different types of documents (mail, ghost, @@ -783,7 +789,7 @@ notmuch_database_create_verbose (const char *path, notmuch_status_t _notmuch_database_ensure_writable (notmuch_database_t *notmuch) { - if (notmuch->mode == NOTMUCH_DATABASE_MODE_READ_ONLY) { + if (_notmuch_database_mode (notmuch) == NOTMUCH_DATABASE_MODE_READ_ONLY) { _notmuch_database_log (notmuch, "Cannot write to a read-only database.\n"); return NOTMUCH_STATUS_READ_ONLY_DATABASE; } @@ -1107,7 +1113,7 @@ notmuch_database_close (notmuch_database_t *notmuch) * that transaction, or may discard committed (but * unflushed) transactions. To be certain, explicitly * cancel any outstanding transaction before closing. */ - if (notmuch->mode == NOTMUCH_DATABASE_MODE_READ_WRITE && + if (_notmuch_database_mode (notmuch) == NOTMUCH_DATABASE_MODE_READ_WRITE && notmuch->atomic_nesting) (static_cast (notmuch->xapian_db)) ->cancel_transaction (); @@ -1130,7 +1136,7 @@ notmuch_database_close (notmuch_database_t *notmuch) notmuch_status_t _notmuch_database_reopen (notmuch_database_t *notmuch) { - if (notmuch->mode != NOTMUCH_DATABASE_MODE_READ_ONLY) + if (_notmuch_database_mode (notmuch) != NOTMUCH_DATABASE_MODE_READ_ONLY) return NOTMUCH_STATUS_UNSUPPORTED_OPERATION; try { @@ -1395,7 +1401,7 @@ notmuch_database_needs_upgrade (notmuch_database_t *notmuch) { unsigned int version; - if (notmuch->mode != NOTMUCH_DATABASE_MODE_READ_WRITE) + if (_notmuch_database_mode (notmuch) != NOTMUCH_DATABASE_MODE_READ_WRITE) return FALSE; if (NOTMUCH_FEATURES_CURRENT & ~notmuch->features) @@ -1697,7 +1703,7 @@ notmuch_database_upgrade (notmuch_database_t *notmuch, notmuch_status_t notmuch_database_begin_atomic (notmuch_database_t *notmuch) { - if (notmuch->mode == NOTMUCH_DATABASE_MODE_READ_ONLY || + if (_notmuch_database_mode (notmuch) == NOTMUCH_DATABASE_MODE_READ_ONLY || notmuch->atomic_nesting > 0) goto DONE; @@ -1726,7 +1732,7 @@ notmuch_database_end_atomic (notmuch_database_t *notmuch) if (notmuch->atomic_nesting == 0) return NOTMUCH_STATUS_UNBALANCED_ATOMIC; - if (notmuch->mode == NOTMUCH_DATABASE_MODE_READ_ONLY || + if (_notmuch_database_mode (notmuch) == NOTMUCH_DATABASE_MODE_READ_ONLY || notmuch->atomic_nesting > 1) goto DONE; diff --git a/lib/directory.cc b/lib/directory.cc index 09b49245..eaba65ab 100644 --- a/lib/directory.cc +++ b/lib/directory.cc @@ -114,7 +114,7 @@ _notmuch_directory_find_or_create (notmuch_database_t *notmuch, path = _notmuch_database_relative_path (notmuch, path); - if (create && notmuch->mode == NOTMUCH_DATABASE_MODE_READ_ONLY) + if (create && _notmuch_database_mode (notmuch) == NOTMUCH_DATABASE_MODE_READ_ONLY) INTERNAL_ERROR ("Failure to ensure database is writable"); directory = talloc (notmuch, notmuch_directory_t); diff --git a/lib/message.cc b/lib/message.cc index 64798413..d23e64ab 100644 --- a/lib/message.cc +++ b/lib/message.cc @@ -275,7 +275,7 @@ _notmuch_message_create_for_message_id (notmuch_database_t *notmuch, return NULL; } - if (notmuch->mode == NOTMUCH_DATABASE_MODE_READ_ONLY) + if (_notmuch_database_mode (notmuch) == NOTMUCH_DATABASE_MODE_READ_ONLY) INTERNAL_ERROR ("Failure to ensure database is writable."); try { @@ -1324,7 +1324,7 @@ _notmuch_message_sync (notmuch_message_t *message) { Xapian::WritableDatabase *db; - if (message->notmuch->mode == NOTMUCH_DATABASE_MODE_READ_ONLY) + if (_notmuch_database_mode (message->notmuch) == NOTMUCH_DATABASE_MODE_READ_ONLY) return; if (! message->modified) diff --git a/lib/notmuch-private.h b/lib/notmuch-private.h index 2bbbb293..57ec7f72 100644 --- a/lib/notmuch-private.h +++ b/lib/notmuch-private.h @@ -259,6 +259,9 @@ _notmuch_directory_find_or_create (notmuch_database_t *notmuch, unsigned int _notmuch_directory_get_document_id (notmuch_directory_t *directory); +notmuch_database_mode_t +_notmuch_database_mode (notmuch_database_t *notmuch); + /* message.cc */ notmuch_message_t * From a09293793f43b93b7008dd361b192199ad528fca Mon Sep 17 00:00:00 2001 From: David Bremner Date: Sun, 26 Jul 2020 20:31:36 -0300 Subject: [PATCH 296/427] lib: replace use of static_cast for writable databases static_cast is a bit tricky to understand and error prone, so add a second pointer to (potentially the same) Xapian database object that we know has the right subclass. --- lib/add-message.cc | 13 +++---------- lib/config.cc | 4 +--- lib/database-private.h | 2 +- lib/database.cc | 21 ++++++++++++--------- lib/directory.cc | 18 +++++++----------- lib/message.cc | 10 +++------- 6 files changed, 27 insertions(+), 41 deletions(-) diff --git a/lib/add-message.cc b/lib/add-message.cc index 9dd4b697..485debad 100644 --- a/lib/add-message.cc +++ b/lib/add-message.cc @@ -43,15 +43,12 @@ _notmuch_database_generate_thread_id (notmuch_database_t *notmuch) /* 16 bytes (+ terminator) for hexadecimal representation of * a 64-bit integer. */ static char thread_id[17]; - Xapian::WritableDatabase *db; - - db = static_cast (notmuch->xapian_db); notmuch->last_thread_id++; sprintf (thread_id, "%016" PRIx64, notmuch->last_thread_id); - db->set_metadata ("last_thread_id", thread_id); + notmuch->writable_xapian_db->set_metadata ("last_thread_id", thread_id); return thread_id; } @@ -161,7 +158,7 @@ _resolve_message_id_to_thread_id_old (notmuch_database_t *notmuch, * can return the thread ID stored in the metadata. Otherwise, we * generate a new thread ID and store it there. */ - db = static_cast (notmuch->xapian_db); + db = notmuch->writable_xapian_db; metadata_key = _get_metadata_thread_id_key (ctx, message_id); thread_id_string = notmuch->xapian_db->get_metadata (metadata_key); @@ -370,13 +367,9 @@ _consume_metadata_thread_id (void *ctx, notmuch_database_t *notmuch, if (stored_id.empty ()) { return NULL; } else { - Xapian::WritableDatabase *db; - - db = static_cast (notmuch->xapian_db); - /* Clear the metadata for this message ID. We don't need it * anymore. */ - db->set_metadata (metadata_key, ""); + notmuch->writable_xapian_db->set_metadata (metadata_key, ""); return talloc_strdup (ctx, stored_id.c_str ()); } diff --git a/lib/config.cc b/lib/config.cc index 20036471..dae0ff0e 100644 --- a/lib/config.cc +++ b/lib/config.cc @@ -45,15 +45,13 @@ notmuch_database_set_config (notmuch_database_t *notmuch, const char *value) { notmuch_status_t status; - Xapian::WritableDatabase *db; status = _notmuch_database_ensure_writable (notmuch); if (status) return status; try { - db = static_cast (notmuch->xapian_db); - db->set_metadata (CONFIG_PREFIX + key, value); + notmuch->writable_xapian_db->set_metadata (CONFIG_PREFIX + key, value); } catch (const Xapian::Error &error) { status = NOTMUCH_STATUS_XAPIAN_EXCEPTION; notmuch->exception_reported = true; diff --git a/lib/database-private.h b/lib/database-private.h index d2c25313..041602cd 100644 --- a/lib/database-private.h +++ b/lib/database-private.h @@ -189,11 +189,11 @@ struct _notmuch_database { char *path; - notmuch_database_mode_t mode; int atomic_nesting; /* true if changes have been made in this atomic section */ bool atomic_dirty; Xapian::Database *xapian_db; + Xapian::WritableDatabase *writable_xapian_db; bool open; /* Bit mask of features used by this database. This is a * bitwise-OR of NOTMUCH_FEATURE_* values (above). */ diff --git a/lib/database.cc b/lib/database.cc index 08278235..75189685 100644 --- a/lib/database.cc +++ b/lib/database.cc @@ -72,7 +72,10 @@ _log_xapian_exception (const char *where, notmuch_database_t *notmuch, const Xa notmuch_database_mode_t _notmuch_database_mode (notmuch_database_t *notmuch) { - return notmuch->mode; + if (notmuch->writable_xapian_db) + return NOTMUCH_DATABASE_MODE_READ_WRITE; + else + return NOTMUCH_DATABASE_MODE_READ_ONLY; } /* Here's the current schema for our database (for NOTMUCH_DATABASE_VERSION): @@ -976,7 +979,7 @@ notmuch_database_open_verbose (const char *path, strip_trailing (notmuch->path, '/'); - notmuch->mode = mode; + notmuch->writable_xapian_db = NULL; notmuch->atomic_nesting = 0; notmuch->view = 1; try { @@ -984,8 +987,9 @@ notmuch_database_open_verbose (const char *path, string last_mod; if (mode == NOTMUCH_DATABASE_MODE_READ_WRITE) { - notmuch->xapian_db = new Xapian::WritableDatabase (xapian_path, - DB_ACTION); + notmuch->writable_xapian_db = new Xapian::WritableDatabase (xapian_path, + DB_ACTION); + notmuch->xapian_db = notmuch->writable_xapian_db; } else { notmuch->xapian_db = new Xapian::Database (xapian_path); } @@ -1115,8 +1119,7 @@ notmuch_database_close (notmuch_database_t *notmuch) * cancel any outstanding transaction before closing. */ if (_notmuch_database_mode (notmuch) == NOTMUCH_DATABASE_MODE_READ_WRITE && notmuch->atomic_nesting) - (static_cast (notmuch->xapian_db)) - ->cancel_transaction (); + notmuch->writable_xapian_db->cancel_transaction (); /* Close the database. This implicitly flushes * outstanding changes. */ @@ -1454,7 +1457,7 @@ notmuch_database_upgrade (notmuch_database_t *notmuch, if (status) return status; - db = static_cast (notmuch->xapian_db); + db = notmuch->writable_xapian_db; target_features = notmuch->features | NOTMUCH_FEATURES_CURRENT; new_features = NOTMUCH_FEATURES_CURRENT & ~notmuch->features; @@ -1711,7 +1714,7 @@ notmuch_database_begin_atomic (notmuch_database_t *notmuch) return NOTMUCH_STATUS_UPGRADE_REQUIRED; try { - (static_cast (notmuch->xapian_db))->begin_transaction (false); + notmuch->writable_xapian_db->begin_transaction (false); } catch (const Xapian::Error &error) { _notmuch_database_log (notmuch, "A Xapian exception occurred beginning transaction: %s.\n", error.get_msg ().c_str ()); @@ -1736,7 +1739,7 @@ notmuch_database_end_atomic (notmuch_database_t *notmuch) notmuch->atomic_nesting > 1) goto DONE; - db = static_cast (notmuch->xapian_db); + db = notmuch->writable_xapian_db; try { db->commit_transaction (); diff --git a/lib/directory.cc b/lib/directory.cc index eaba65ab..b9c3d77f 100644 --- a/lib/directory.cc +++ b/lib/directory.cc @@ -99,7 +99,6 @@ _notmuch_directory_find_or_create (notmuch_database_t *notmuch, notmuch_find_flags_t flags, notmuch_status_t *status_ret) { - Xapian::WritableDatabase *db; notmuch_directory_t *directory; notmuch_private_status_t private_status; const char *db_path; @@ -176,10 +175,10 @@ _notmuch_directory_find_or_create (notmuch_database_t *notmuch, directory->doc.add_value (NOTMUCH_VALUE_TIMESTAMP, Xapian::sortable_serialise (0)); - db = static_cast (notmuch->xapian_db); - directory->document_id = _notmuch_database_generate_doc_id (notmuch); - db->replace_document (directory->document_id, directory->doc); + directory->notmuch-> + writable_xapian_db + -> replace_document (directory->document_id, directory->doc); talloc_free (local); } @@ -213,20 +212,18 @@ notmuch_directory_set_mtime (notmuch_directory_t *directory, time_t mtime) { notmuch_database_t *notmuch = directory->notmuch; - Xapian::WritableDatabase *db; notmuch_status_t status; status = _notmuch_database_ensure_writable (notmuch); if (status) return status; - db = static_cast (notmuch->xapian_db); - try { directory->doc.add_value (NOTMUCH_VALUE_TIMESTAMP, Xapian::sortable_serialise (mtime)); - db->replace_document (directory->document_id, directory->doc); + directory->notmuch + ->writable_xapian_db->replace_document (directory->document_id, directory->doc); directory->mtime = mtime; @@ -288,15 +285,14 @@ notmuch_status_t notmuch_directory_delete (notmuch_directory_t *directory) { notmuch_status_t status; - Xapian::WritableDatabase *db; status = _notmuch_database_ensure_writable (directory->notmuch); if (status) return status; try { - db = static_cast (directory->notmuch->xapian_db); - db->delete_document (directory->document_id); + directory->notmuch-> + writable_xapian_db->delete_document (directory->document_id); } catch (const Xapian::Error &error) { _notmuch_database_log (directory->notmuch, "A Xapian exception occurred deleting directory entry: %s.\n", diff --git a/lib/message.cc b/lib/message.cc index d23e64ab..fca99082 100644 --- a/lib/message.cc +++ b/lib/message.cc @@ -1322,8 +1322,6 @@ _notmuch_message_upgrade_last_mod (notmuch_message_t *message) void _notmuch_message_sync (notmuch_message_t *message) { - Xapian::WritableDatabase *db; - if (_notmuch_database_mode (message->notmuch) == NOTMUCH_DATABASE_MODE_READ_ONLY) return; @@ -1342,8 +1340,8 @@ _notmuch_message_sync (notmuch_message_t *message) _notmuch_database_new_revision ( message->notmuch))); - db = static_cast (message->notmuch->xapian_db); - db->replace_document (message->doc_id, message->doc); + message->notmuch->writable_xapian_db-> + replace_document (message->doc_id, message->doc); message->modified = false; } @@ -1353,7 +1351,6 @@ notmuch_status_t _notmuch_message_delete (notmuch_message_t *message) { notmuch_status_t status; - Xapian::WritableDatabase *db; const char *mid, *tid, *query_string; notmuch_message_t *ghost; notmuch_private_status_t private_status; @@ -1370,8 +1367,7 @@ _notmuch_message_delete (notmuch_message_t *message) if (status) return status; - db = static_cast (notmuch->xapian_db); - db->delete_document (message->doc_id); + message->notmuch->writable_xapian_db->delete_document (message->doc_id); /* if this was a ghost to begin with, we are done */ private_status = _notmuch_message_has_term (message, "type", "ghost", &is_ghost); From 85da95e26699a404b3bb3e1c01ea7998a1284abd Mon Sep 17 00:00:00 2001 From: David Bremner Date: Wed, 22 Jul 2020 07:51:18 -0300 Subject: [PATCH 297/427] test: move notmuch_message_* tests to their own file This is for consistency with the recently added tests for notmuch_database_*. --- test/T560-lib-error.sh | 378 -------------------------------------- test/T566-lib-message.sh | 380 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 380 insertions(+), 378 deletions(-) create mode 100755 test/T566-lib-message.sh diff --git a/test/T560-lib-error.sh b/test/T560-lib-error.sh index 68180e41..70df292a 100755 --- a/test/T560-lib-error.sh +++ b/test/T560-lib-error.sh @@ -318,382 +318,4 @@ EOF test_expect_equal_file EXPECTED OUTPUT.clean restore_database -cat < c_head2 -#include -#include -#include -int main (int argc, char** argv) -{ - notmuch_database_t *db; - notmuch_status_t stat; - char *msg = NULL; - notmuch_message_t *message = NULL; - const char *id = "1258471718-6781-1-git-send-email-dottedmag@dottedmag.net"; - - stat = notmuch_database_open_verbose (argv[1], NOTMUCH_DATABASE_MODE_READ_WRITE, &db, &msg); - if (stat != NOTMUCH_STATUS_SUCCESS) { - fprintf (stderr, "error opening database: %d %s\n", stat, msg ? msg : ""); - exit (1); - } - EXPECT0(notmuch_database_find_message (db, id, &message)); - EXPECT0(notmuch_database_close (db)); -EOF - -test_begin_subtest "Handle getting message-id from closed database" -cat c_head2 - c_tail <<'EOF' | test_C ${MAIL_DIR} - { - const char *id2; - id2=notmuch_message_get_message_id (message); - printf("%d\n%d\n", message != NULL, id2==NULL); - stat = NOTMUCH_STATUS_XAPIAN_EXCEPTION; - } -EOF -cat < EXPECTED -== stdout == -1 -1 -== stderr == -A Xapian exception occurred at lib/message.cc:XXX: Database has been closed -EOF -test_expect_equal_file EXPECTED OUTPUT - -test_begin_subtest "Handle getting thread-id from closed database" -cat c_head2 - c_tail <<'EOF' | test_C ${MAIL_DIR} - { - const char *id2; - id2=notmuch_message_get_thread_id (message); - printf("%d\n%d\n", message != NULL, id2==NULL); - stat = NOTMUCH_STATUS_XAPIAN_EXCEPTION; - } -EOF -cat < EXPECTED -== stdout == -1 -1 -== stderr == -A Xapian exception occurred at lib/message.cc:XXX: Database has been closed -EOF -test_expect_equal_file EXPECTED OUTPUT - -test_begin_subtest "Handle getting header from closed database" -cat c_head2 - c_tail <<'EOF' | test_C ${MAIL_DIR} - { - const char *from; - from=notmuch_message_get_header (message, "from"); - printf("%s\n%d\n", id, from == NULL); - stat = NOTMUCH_STATUS_XAPIAN_EXCEPTION; - } -EOF -cat < EXPECTED -== stdout == -1258471718-6781-1-git-send-email-dottedmag@dottedmag.net -1 -== stderr == -A Xapian exception occurred at lib/message.cc:XXX: Database has been closed -EOF -test_expect_equal_file EXPECTED OUTPUT - -# XXX TODO: test on a message from notmuch_thread_get_toplevel_messages -# XXX this test only tests the trivial code path -test_begin_subtest "Handle getting replies from closed database" -cat c_head2 - c_tail <<'EOF' | test_C ${MAIL_DIR} - { - notmuch_messages_t *replies; - replies = notmuch_message_get_replies (message); - printf("%d\n%d\n", message != NULL, replies==NULL); - stat = NOTMUCH_STATUS_XAPIAN_EXCEPTION; - } -EOF -cat < EXPECTED -== stdout == -1 -1 -== stderr == -EOF -test_expect_equal_file EXPECTED OUTPUT - -test_begin_subtest "Handle getting message filename from closed database" -cat c_head2 - c_tail <<'EOF' | test_C ${MAIL_DIR} - { - const char *filename; - filename = notmuch_message_get_filename (message); - printf("%d\n%d\n", message != NULL, filename == NULL); - stat = NOTMUCH_STATUS_XAPIAN_EXCEPTION; - } -EOF -cat < EXPECTED -== stdout == -1 -1 -== stderr == -A Xapian exception occurred at lib/message.cc:XXX: Database has been closed -EOF -test_expect_equal_file EXPECTED OUTPUT - -test_begin_subtest "Handle getting all message filenames from closed database" -cat c_head2 - c_tail <<'EOF' | test_C ${MAIL_DIR} - { - notmuch_filenames_t *filenames; - filenames = notmuch_message_get_filenames (message); - printf("%d\n%d\n", message != NULL, filenames == NULL); - stat = NOTMUCH_STATUS_XAPIAN_EXCEPTION; - } -EOF -cat < EXPECTED -== stdout == -1 -1 -== stderr == -A Xapian exception occurred at lib/message.cc:XXX: Database has been closed -EOF -test_expect_equal_file EXPECTED OUTPUT - -test_begin_subtest "Handle getting ghost flag from closed database" -cat c_head2 - c_tail <<'EOF' | test_C ${MAIL_DIR} - { - notmuch_bool_t result; - result = notmuch_message_get_flag (message, NOTMUCH_MESSAGE_FLAG_GHOST); - printf("%d\n%d\n", message != NULL, result == FALSE); - stat = NOTMUCH_STATUS_XAPIAN_EXCEPTION; - } -EOF -cat < EXPECTED -== stdout == -1 -1 -== stderr == -A Xapian exception occurred at lib/message.cc:XXX: Database has been closed -EOF -test_expect_equal_file EXPECTED OUTPUT - -test_begin_subtest "Handle getting date from closed database" -cat c_head2 - c_tail <<'EOF' | test_C ${MAIL_DIR} - { - time_t result; - result = notmuch_message_get_date (message); - printf("%d\n%d\n", message != NULL, result == 0); - stat = NOTMUCH_STATUS_XAPIAN_EXCEPTION; - } -EOF -cat < EXPECTED -== stdout == -1 -1 -== stderr == -A Xapian exception occurred at lib/message.cc:XXX: Database has been closed -EOF -test_expect_equal_file EXPECTED OUTPUT - -test_begin_subtest "Handle getting tags from closed database" -cat c_head2 - c_tail <<'EOF' | test_C ${MAIL_DIR} - { - notmuch_tags_t *result; - result = notmuch_message_get_tags (message); - printf("%d\n%d\n", message != NULL, result == NULL); - stat = NOTMUCH_STATUS_XAPIAN_EXCEPTION; - } -EOF -cat < EXPECTED -== stdout == -1 -1 -== stderr == -A Xapian exception occurred at lib/message.cc:XXX: Database has been closed -EOF -test_expect_equal_file EXPECTED OUTPUT - -test_begin_subtest "Handle counting files from closed database" -cat c_head2 - c_tail <<'EOF' | test_C ${MAIL_DIR} - { - int result; - result = notmuch_message_count_files (message); - printf("%d\n%d\n", message != NULL, result < 0); - stat = NOTMUCH_STATUS_XAPIAN_EXCEPTION; - } -EOF -cat < EXPECTED -== stdout == -1 -1 -== stderr == -A Xapian exception occurred at lib/message.cc:XXX: Database has been closed -EOF -test_expect_equal_file EXPECTED OUTPUT - -test_begin_subtest "Handle adding tag with closed database" -cat c_head2 - c_tail <<'EOF' | test_C ${MAIL_DIR} - { - stat = notmuch_message_add_tag (message, "boom"); - printf("%d\n%d\n", message != NULL, stat == NOTMUCH_STATUS_XAPIAN_EXCEPTION); - } -EOF -cat < EXPECTED -== stdout == -1 -1 -== stderr == -A Xapian exception occurred at lib/message.cc:XXX: Database has been closed -EOF -test_expect_equal_file EXPECTED OUTPUT - -test_begin_subtest "Handle removing tag with closed database" -cat c_head2 - c_tail <<'EOF' | test_C ${MAIL_DIR} - { - stat = notmuch_message_remove_tag (message, "boom"); - printf("%d\n%d\n", message != NULL, stat == NOTMUCH_STATUS_XAPIAN_EXCEPTION); - } -EOF -cat < EXPECTED -== stdout == -1 -1 -== stderr == -A Xapian exception occurred at lib/message.cc:XXX: Database has been closed -EOF -test_expect_equal_file EXPECTED OUTPUT - -test_begin_subtest "Handle read maildir flag with closed database" -cat c_head2 - c_tail <<'EOF' | test_C ${MAIL_DIR} - { - notmuch_bool_t is_set = -1; - is_set = notmuch_message_has_maildir_flag (message, 'S'); - printf("%d\n%d\n", message != NULL, is_set == FALSE || is_set == TRUE); - stat = NOTMUCH_STATUS_XAPIAN_EXCEPTION; - } -EOF -cat < EXPECTED -== stdout == -1 -1 -== stderr == -A Xapian exception occurred at lib/message.cc:XXX: Database has been closed -EOF -test_expect_equal_file EXPECTED OUTPUT - -test_begin_subtest "Handle checking maildir flag with closed db (new API)" -cat c_head2 - c_tail <<'EOF' | test_C ${MAIL_DIR} - { - notmuch_bool_t out; - stat = notmuch_message_has_maildir_flag_st (message, 'S', &out); - printf("%d\n%d\n", message != NULL, stat == NOTMUCH_STATUS_XAPIAN_EXCEPTION); - } -EOF -cat < EXPECTED -== stdout == -1 -1 -== stderr == -A Xapian exception occurred at lib/message.cc:XXX: Database has been closed -EOF -test_expect_equal_file EXPECTED OUTPUT - -test_begin_subtest "Handle converting maildir flags to tags with closed db" -cat c_head2 - c_tail <<'EOF' | test_C ${MAIL_DIR} - { - stat = notmuch_message_maildir_flags_to_tags (message); - printf("%d\n%d\n", message != NULL, stat == NOTMUCH_STATUS_XAPIAN_EXCEPTION); - } -EOF -cat < EXPECTED -== stdout == -1 -1 -== stderr == -A Xapian exception occurred at lib/message.cc:XXX: Database has been closed -EOF -test_expect_equal_file EXPECTED OUTPUT - -test_begin_subtest "Handle removing all tags with closed db" -cat c_head2 - c_tail <<'EOF' | test_C ${MAIL_DIR} - { - stat = notmuch_message_remove_all_tags (message); - printf("%d\n%d\n", message != NULL, stat == NOTMUCH_STATUS_XAPIAN_EXCEPTION); - } -EOF -cat < EXPECTED -== stdout == -1 -1 -== stderr == -A Xapian exception occurred at lib/message.cc:XXX: Database has been closed -EOF -test_expect_equal_file EXPECTED OUTPUT - -test_begin_subtest "Handle freezing message with closed db" -cat c_head2 - c_tail <<'EOF' | test_C ${MAIL_DIR} - { - stat = notmuch_message_freeze (message); - printf("%d\n%d\n", message != NULL, stat == NOTMUCH_STATUS_SUCCESS); - } -EOF -cat < EXPECTED -== stdout == -1 -1 -== stderr == -EOF -test_expect_equal_file EXPECTED OUTPUT - -test_begin_subtest "Handle thawing message with closed db" -cat c_head2 - c_tail <<'EOF' | test_C ${MAIL_DIR} - { - stat = notmuch_message_thaw (message); - printf("%d\n%d\n", message != NULL, stat == NOTMUCH_STATUS_UNBALANCED_FREEZE_THAW); - } -EOF -cat < EXPECTED -== stdout == -1 -1 -== stderr == -EOF -test_expect_equal_file EXPECTED OUTPUT - -test_begin_subtest "Handle destroying message with closed db" -cat c_head2 - c_tail <<'EOF' | test_C ${MAIL_DIR} - { - notmuch_message_destroy (message); - printf("%d\n%d\n", message != NULL, 1); - } -EOF -cat < EXPECTED -== stdout == -1 -1 -== stderr == -EOF -test_expect_equal_file EXPECTED OUTPUT - -test_begin_subtest "Handle retrieving closed db from message" -cat c_head2 - c_tail <<'EOF' | test_C ${MAIL_DIR} - { - notmuch_database_t *db2; - db2 = notmuch_message_get_database (message); - printf("%d\n%d\n", message != NULL, db == db2); - } -EOF -cat < EXPECTED -== stdout == -1 -1 -== stderr == -EOF -test_expect_equal_file EXPECTED OUTPUT - -test_begin_subtest "Handle reindexing message with closed db" -cat c_head2 - c_tail <<'EOF' | test_C ${MAIL_DIR} - { - stat = notmuch_message_reindex (message, NULL); - printf("%d\n%d\n", message != NULL, stat == NOTMUCH_STATUS_XAPIAN_EXCEPTION); - } -EOF -cat < EXPECTED -== stdout == -1 -1 -== stderr == -A Xapian exception occurred at lib/message.cc:XXX: Database has been closed -EOF -test_expect_equal_file EXPECTED OUTPUT - test_done diff --git a/test/T566-lib-message.sh b/test/T566-lib-message.sh new file mode 100755 index 00000000..0c5575a9 --- /dev/null +++ b/test/T566-lib-message.sh @@ -0,0 +1,380 @@ +#!/usr/bin/env bash +test_description="API tests for notmuch_message_*" + +. $(dirname "$0")/test-lib.sh || exit 1 + +add_email_corpus + +test_begin_subtest "building database" +test_expect_success "NOTMUCH_NEW" + +cat <<'EOF' > c_tail + if (stat) { + const char *stat_str = notmuch_database_status_string (db); + if (stat_str) + fputs (stat_str, stderr); + } + +} +EOF + +cat < c_head +#include +#include +#include +int main (int argc, char** argv) +{ + notmuch_database_t *db; + notmuch_status_t stat; + char *msg = NULL; + notmuch_message_t *message = NULL; + const char *id = "1258471718-6781-1-git-send-email-dottedmag@dottedmag.net"; + + stat = notmuch_database_open_verbose (argv[1], NOTMUCH_DATABASE_MODE_READ_WRITE, &db, &msg); + if (stat != NOTMUCH_STATUS_SUCCESS) { + fprintf (stderr, "error opening database: %d %s\n", stat, msg ? msg : ""); + exit (1); + } + EXPECT0(notmuch_database_find_message (db, id, &message)); + EXPECT0(notmuch_database_close (db)); +EOF + +test_begin_subtest "Handle getting message-id from closed database" +cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR} + { + const char *id2; + id2=notmuch_message_get_message_id (message); + printf("%d\n%d\n", message != NULL, id2==NULL); + } +EOF +cat < EXPECTED +== stdout == +1 +1 +== stderr == +EOF +test_expect_equal_file EXPECTED OUTPUT + +test_begin_subtest "Handle getting thread-id from closed database" +cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR} + { + const char *id2; + id2=notmuch_message_get_thread_id (message); + printf("%d\n%d\n", message != NULL, id2==NULL); + } +EOF +cat < EXPECTED +== stdout == +1 +1 +== stderr == +EOF +test_expect_equal_file EXPECTED OUTPUT + +test_begin_subtest "Handle getting header from closed database" +cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR} + { + const char *from; + from=notmuch_message_get_header (message, "from"); + printf("%s\n%d\n", id, from == NULL); + } +EOF +cat < EXPECTED +== stdout == +1258471718-6781-1-git-send-email-dottedmag@dottedmag.net +1 +== stderr == +EOF +test_expect_equal_file EXPECTED OUTPUT + +# XXX TODO: test on a message from notmuch_thread_get_toplevel_messages +# XXX this test only tests the trivial code path +test_begin_subtest "Handle getting replies from closed database" +cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR} + { + notmuch_messages_t *replies; + replies = notmuch_message_get_replies (message); + printf("%d\n%d\n", message != NULL, replies==NULL); + } +EOF +cat < EXPECTED +== stdout == +1 +1 +== stderr == +EOF +test_expect_equal_file EXPECTED OUTPUT + +test_begin_subtest "Handle getting message filename from closed database" +cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR} + { + const char *filename; + filename = notmuch_message_get_filename (message); + printf("%d\n%d\n", message != NULL, filename == NULL); + } +EOF +cat < EXPECTED +== stdout == +1 +1 +== stderr == +EOF +test_expect_equal_file EXPECTED OUTPUT + +test_begin_subtest "Handle getting all message filenames from closed database" +cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR} + { + notmuch_filenames_t *filenames; + filenames = notmuch_message_get_filenames (message); + printf("%d\n%d\n", message != NULL, filenames == NULL); + } +EOF +cat < EXPECTED +== stdout == +1 +1 +== stderr == +EOF +test_expect_equal_file EXPECTED OUTPUT + +test_begin_subtest "Handle getting ghost flag from closed database" +cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR} + { + notmuch_bool_t result; + result = notmuch_message_get_flag (message, NOTMUCH_MESSAGE_FLAG_GHOST); + printf("%d\n%d\n", message != NULL, result == FALSE); + } +EOF +cat < EXPECTED +== stdout == +1 +1 +== stderr == +EOF +test_expect_equal_file EXPECTED OUTPUT + +test_begin_subtest "Handle getting date from closed database" +cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR} + { + time_t result; + result = notmuch_message_get_date (message); + printf("%d\n%d\n", message != NULL, result == 0); + } +EOF +cat < EXPECTED +== stdout == +1 +1 +== stderr == +EOF +test_expect_equal_file EXPECTED OUTPUT + +test_begin_subtest "Handle getting tags from closed database" +cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR} + { + notmuch_tags_t *result; + result = notmuch_message_get_tags (message); + printf("%d\n%d\n", message != NULL, result == NULL); + } +EOF +cat < EXPECTED +== stdout == +1 +1 +== stderr == +EOF +test_expect_equal_file EXPECTED OUTPUT + +test_begin_subtest "Handle counting files from closed database" +cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR} + { + int result; + result = notmuch_message_count_files (message); + printf("%d\n%d\n", message != NULL, result < 0); + } +EOF +cat < EXPECTED +== stdout == +1 +1 +== stderr == +EOF +test_expect_equal_file EXPECTED OUTPUT + +test_begin_subtest "Handle adding tag with closed database" +cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR} + { + notmuch_status_t status; + status = notmuch_message_add_tag (message, "boom"); + printf("%d\n%d\n", message != NULL, status == NOTMUCH_STATUS_XAPIAN_EXCEPTION); + } +EOF +cat < EXPECTED +== stdout == +1 +1 +== stderr == +EOF +test_expect_equal_file EXPECTED OUTPUT + +test_begin_subtest "Handle removing tag with closed database" +cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR} + { + notmuch_status_t status; + status = notmuch_message_remove_tag (message, "boom"); + printf("%d\n%d\n", message != NULL, status == NOTMUCH_STATUS_XAPIAN_EXCEPTION); + } +EOF +cat < EXPECTED +== stdout == +1 +1 +== stderr == +EOF +test_expect_equal_file EXPECTED OUTPUT + +test_begin_subtest "Handle read maildir flag with closed database" +cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR} + { + notmuch_bool_t is_set = -1; + is_set = notmuch_message_has_maildir_flag (message, 'S'); + printf("%d\n%d\n", message != NULL, is_set == FALSE || is_set == TRUE); + } +EOF +cat < EXPECTED +== stdout == +1 +1 +== stderr == +EOF +test_expect_equal_file EXPECTED OUTPUT + +test_begin_subtest "Handle checking maildir flag with closed db (new API)" +cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR} + { + notmuch_status_t status; + notmuch_bool_t out; + status = notmuch_message_has_maildir_flag_st (message, 'S', &out); + printf("%d\n%d\n", message != NULL, status == NOTMUCH_STATUS_XAPIAN_EXCEPTION); + } +EOF +cat < EXPECTED +== stdout == +1 +1 +== stderr == +EOF +test_expect_equal_file EXPECTED OUTPUT + +test_begin_subtest "Handle converting maildir flags to tags with closed db" +cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR} + { + notmuch_status_t status; + status = notmuch_message_maildir_flags_to_tags (message); + printf("%d\n%d\n", message != NULL, status == NOTMUCH_STATUS_XAPIAN_EXCEPTION); + } +EOF +cat < EXPECTED +== stdout == +1 +1 +== stderr == +EOF +test_expect_equal_file EXPECTED OUTPUT + +test_begin_subtest "Handle removing all tags with closed db" +cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR} + { + notmuch_status_t status; + status = notmuch_message_remove_all_tags (message); + printf("%d\n%d\n", message != NULL, status == NOTMUCH_STATUS_XAPIAN_EXCEPTION); + } +EOF +cat < EXPECTED +== stdout == +1 +1 +== stderr == +EOF +test_expect_equal_file EXPECTED OUTPUT + +test_begin_subtest "Handle freezing message with closed db" +cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR} + { + notmuch_status_t status; + status = notmuch_message_freeze (message); + printf("%d\n%d\n", message != NULL, status == NOTMUCH_STATUS_SUCCESS); + } +EOF +cat < EXPECTED +== stdout == +1 +1 +== stderr == +EOF +test_expect_equal_file EXPECTED OUTPUT + +test_begin_subtest "Handle thawing message with closed db" +cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR} + { + notmuch_status_t status; + status = notmuch_message_thaw (message); + printf("%d\n%d\n", message != NULL, status == NOTMUCH_STATUS_UNBALANCED_FREEZE_THAW); + } +EOF +cat < EXPECTED +== stdout == +1 +1 +== stderr == +EOF +test_expect_equal_file EXPECTED OUTPUT + +test_begin_subtest "Handle destroying message with closed db" +cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR} + { + notmuch_message_destroy (message); + printf("%d\n%d\n", message != NULL, 1); + } +EOF +cat < EXPECTED +== stdout == +1 +1 +== stderr == +EOF +test_expect_equal_file EXPECTED OUTPUT + +test_begin_subtest "Handle retrieving closed db from message" +cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR} + { + notmuch_database_t *db2; + db2 = notmuch_message_get_database (message); + printf("%d\n%d\n", message != NULL, db == db2); + } +EOF +cat < EXPECTED +== stdout == +1 +1 +== stderr == +EOF +test_expect_equal_file EXPECTED OUTPUT + +test_begin_subtest "Handle reindexing message with closed db" +cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR} + { + notmuch_status_t status; + status = notmuch_message_reindex (message, NULL); + printf("%d\n%d\n", message != NULL, status == NOTMUCH_STATUS_XAPIAN_EXCEPTION); + } +EOF +cat < EXPECTED +== stdout == +1 +1 +== stderr == +EOF +test_expect_equal_file EXPECTED OUTPUT + +test_done From cbf9f890aab6a9fe6ce4fbe7f1a7903582ff5ff4 Mon Sep 17 00:00:00 2001 From: David Bremner Date: Wed, 22 Jul 2020 07:51:19 -0300 Subject: [PATCH 298/427] test: add regression test for n_q_{create,_get_query_string} Start a new file of tests, to keep to the (emerging) scheme of one notmuch_foo group per file --- test/T564-lib-query.sh | 61 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100755 test/T564-lib-query.sh diff --git a/test/T564-lib-query.sh b/test/T564-lib-query.sh new file mode 100755 index 00000000..103870a6 --- /dev/null +++ b/test/T564-lib-query.sh @@ -0,0 +1,61 @@ +#!/usr/bin/env bash +test_description="notmuch_database_* API" + +. $(dirname "$0")/test-lib.sh || exit 1 + +add_email_corpus + +test_begin_subtest "building database" +test_expect_success "NOTMUCH_NEW" + +cat < c_head +#include +#include +#include +#include +int main (int argc, char** argv) +{ + notmuch_database_t *db; + notmuch_status_t stat; + char *msg = NULL; + + stat = notmuch_database_open_verbose (argv[1], NOTMUCH_DATABASE_MODE_READ_WRITE, &db, &msg); + if (stat != NOTMUCH_STATUS_SUCCESS) { + fprintf (stderr, "error opening database: %d %s\n", stat, msg ? msg : ""); + exit (1); + } +EOF + +cat <<'EOF' > c_tail + if (stat) { + const char *stat_str = notmuch_database_status_string (db); + if (stat_str) + fputs (stat_str, stderr); + } + +} +EOF + +test_begin_subtest "roundtrip query string with closed db" +cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR} + { + notmuch_query_t *query; + const char *str = "id:1258471718-6781-1-git-send-email-dottedmag@dottedmag.net"; + const char *ret; + + EXPECT0(notmuch_database_close (db)); + query = notmuch_query_create (db, str); + ret = notmuch_query_get_query_string (query); + + printf("%s\n%s\n", str, ret); + } +EOF +cat < EXPECTED +== stdout == +id:1258471718-6781-1-git-send-email-dottedmag@dottedmag.net +id:1258471718-6781-1-git-send-email-dottedmag@dottedmag.net +== stderr == +EOF +test_expect_equal_file EXPECTED OUTPUT + +test_done From 768efe88d0523f11e7c0e732e55a3c25e9cecbc8 Mon Sep 17 00:00:00 2001 From: David Bremner Date: Wed, 22 Jul 2020 07:51:20 -0300 Subject: [PATCH 299/427] test: regression test for notmuch_query_get_database Current functionality is too trivial to really justify a test, but it's simpler just to test the complete API. --- test/T564-lib-query.sh | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/test/T564-lib-query.sh b/test/T564-lib-query.sh index 103870a6..ac08344f 100755 --- a/test/T564-lib-query.sh +++ b/test/T564-lib-query.sh @@ -1,5 +1,5 @@ #!/usr/bin/env bash -test_description="notmuch_database_* API" +test_description="notmuch_query_* API" . $(dirname "$0")/test-lib.sh || exit 1 @@ -58,4 +58,25 @@ id:1258471718-6781-1-git-send-email-dottedmag@dottedmag.net EOF test_expect_equal_file EXPECTED OUTPUT +test_begin_subtest "retrieve closed db from query" +cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR} + { + notmuch_query_t *query; + const char *str = "id:1258471718-6781-1-git-send-email-dottedmag@dottedmag.net"; + notmuch_database_t *db2; + + query = notmuch_query_create (db, str); + EXPECT0(notmuch_database_close (db)); + db2 = notmuch_query_get_database (query); + + printf("%d\n", db == db2); + } +EOF +cat < EXPECTED +== stdout == +1 +== stderr == +EOF +test_expect_equal_file EXPECTED OUTPUT + test_done From a292461e65c2150b02fc6f9b168dc871b46d44a1 Mon Sep 17 00:00:00 2001 From: David Bremner Date: Wed, 22 Jul 2020 07:51:21 -0300 Subject: [PATCH 300/427] test: regression test for set_omit_excluded --- test/T564-lib-query.sh | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/test/T564-lib-query.sh b/test/T564-lib-query.sh index ac08344f..ea176191 100755 --- a/test/T564-lib-query.sh +++ b/test/T564-lib-query.sh @@ -79,4 +79,24 @@ cat < EXPECTED EOF test_expect_equal_file EXPECTED OUTPUT +test_begin_subtest "set omit_excluded on closed db" +cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR} + { + notmuch_query_t *query; + const char *str = "id:1258471718-6781-1-git-send-email-dottedmag@dottedmag.net"; + + query = notmuch_query_create (db, str); + EXPECT0(notmuch_database_close (db)); + notmuch_query_set_omit_excluded (query, NOTMUCH_EXCLUDE_ALL); + + printf("SUCCESS\n"); + } +EOF +cat < EXPECTED +== stdout == +SUCCESS +== stderr == +EOF +test_expect_equal_file EXPECTED OUTPUT + test_done From d5c84c8289ff57e5ea5745f126f9c0377cb8f413 Mon Sep 17 00:00:00 2001 From: David Bremner Date: Wed, 22 Jul 2020 07:51:22 -0300 Subject: [PATCH 301/427] test: regression test for n_q_{set, get}_sort We just want to make sure accessing the query owned by a closed db does not cause a crash. --- test/T564-lib-query.sh | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/test/T564-lib-query.sh b/test/T564-lib-query.sh index ea176191..14e47cfb 100755 --- a/test/T564-lib-query.sh +++ b/test/T564-lib-query.sh @@ -99,4 +99,25 @@ SUCCESS EOF test_expect_equal_file EXPECTED OUTPUT +test_begin_subtest "roundtrip sort on closed db" +cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR} + { + notmuch_query_t *query; + const char *str = "id:1258471718-6781-1-git-send-email-dottedmag@dottedmag.net"; + notmuch_sort_t sort; + + query = notmuch_query_create (db, str); + EXPECT0(notmuch_database_close (db)); + notmuch_query_set_sort (query, NOTMUCH_SORT_UNSORTED); + sort = notmuch_query_get_sort (query); + printf("%d\n", sort == NOTMUCH_SORT_UNSORTED); + } +EOF +cat < EXPECTED +== stdout == +1 +== stderr == +EOF +test_expect_equal_file EXPECTED OUTPUT + test_done From 4130b4e514e9ad16b420db2736f9cbb79d829fa9 Mon Sep 17 00:00:00 2001 From: David Bremner Date: Wed, 22 Jul 2020 07:51:23 -0300 Subject: [PATCH 302/427] test: add regression test for n_q_add_tag_exclude This relies on the change to not tear down the auxilary Xapian objects, in particular the query parser, when the database is closed. --- test/T564-lib-query.sh | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/test/T564-lib-query.sh b/test/T564-lib-query.sh index 14e47cfb..a2f5d731 100755 --- a/test/T564-lib-query.sh +++ b/test/T564-lib-query.sh @@ -120,4 +120,23 @@ cat < EXPECTED EOF test_expect_equal_file EXPECTED OUTPUT +test_begin_subtest "add tag_exclude on closed db" +cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR} + { + notmuch_query_t *query; + const char *str = "id:1258471718-6781-1-git-send-email-dottedmag@dottedmag.net"; + + query = notmuch_query_create (db, str); + EXPECT0(notmuch_database_close (db)); + stat = notmuch_query_add_tag_exclude (query, "spam"); + printf("%d\n", stat == NOTMUCH_STATUS_SUCCESS); + } +EOF +cat < EXPECTED +== stdout == +1 +== stderr == +EOF +test_expect_equal_file EXPECTED OUTPUT + test_done From 25fc8662edf94a29314bfc01f2a335eb47c142c0 Mon Sep 17 00:00:00 2001 From: David Bremner Date: Wed, 22 Jul 2020 07:51:24 -0300 Subject: [PATCH 303/427] test: regression test for n_q_search_threads At least this exception is caught. --- test/T564-lib-query.sh | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/test/T564-lib-query.sh b/test/T564-lib-query.sh index a2f5d731..97729f9a 100755 --- a/test/T564-lib-query.sh +++ b/test/T564-lib-query.sh @@ -139,4 +139,27 @@ cat < EXPECTED EOF test_expect_equal_file EXPECTED OUTPUT +test_begin_subtest "search threads on closed db" +cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR} + { + notmuch_query_t *query; + const char *str = "id:1258471718-6781-1-git-send-email-dottedmag@dottedmag.net"; + notmuch_threads_t *threads; + + query = notmuch_query_create (db, str); + EXPECT0(notmuch_database_close (db)); + stat = notmuch_query_search_threads (query, &threads); + + printf("%d\n", stat == NOTMUCH_STATUS_XAPIAN_EXCEPTION); + } +EOF +cat < EXPECTED +== stdout == +1 +== stderr == +A Xapian exception occurred performing query: Database has been closed +Query string was: id:1258471718-6781-1-git-send-email-dottedmag@dottedmag.net +EOF +test_expect_equal_file EXPECTED OUTPUT + test_done From 0a4c6cdeb118e68f2fa73a4b90b2fa74a06e88ab Mon Sep 17 00:00:00 2001 From: David Bremner Date: Wed, 22 Jul 2020 07:51:25 -0300 Subject: [PATCH 304/427] test: regression test for n_q_search_messages Exception handling matches notmuch_query_search_threads, at least for this case. --- test/T564-lib-query.sh | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/test/T564-lib-query.sh b/test/T564-lib-query.sh index 97729f9a..dda32be7 100755 --- a/test/T564-lib-query.sh +++ b/test/T564-lib-query.sh @@ -162,4 +162,27 @@ Query string was: id:1258471718-6781-1-git-send-email-dottedmag@dottedmag.net EOF test_expect_equal_file EXPECTED OUTPUT +test_begin_subtest "search messages on closed db" +cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR} + { + notmuch_query_t *query; + const char *str = "id:1258471718-6781-1-git-send-email-dottedmag@dottedmag.net"; + notmuch_messages_t *messages; + + query = notmuch_query_create (db, str); + EXPECT0(notmuch_database_close (db)); + stat = notmuch_query_search_messages (query, &messages); + + printf("%d\n", stat == NOTMUCH_STATUS_XAPIAN_EXCEPTION); + } +EOF +cat < EXPECTED +== stdout == +1 +== stderr == +A Xapian exception occurred performing query: Database has been closed +Query string was: id:1258471718-6781-1-git-send-email-dottedmag@dottedmag.net +EOF +test_expect_equal_file EXPECTED OUTPUT + test_done From 451c0ce3fce6141ee658153a074b78a2b0e24955 Mon Sep 17 00:00:00 2001 From: David Bremner Date: Wed, 22 Jul 2020 07:51:26 -0300 Subject: [PATCH 305/427] test: regression tests for n_q_count_{messages, threads} At least these exceptions are caught. --- test/T564-lib-query.sh | 46 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/test/T564-lib-query.sh b/test/T564-lib-query.sh index dda32be7..745d28a4 100755 --- a/test/T564-lib-query.sh +++ b/test/T564-lib-query.sh @@ -185,4 +185,50 @@ Query string was: id:1258471718-6781-1-git-send-email-dottedmag@dottedmag.net EOF test_expect_equal_file EXPECTED OUTPUT +test_begin_subtest "count messages on closed db" +cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR} + { + notmuch_query_t *query; + const char *str = "id:1258471718-6781-1-git-send-email-dottedmag@dottedmag.net"; + unsigned int count; + + query = notmuch_query_create (db, str); + EXPECT0(notmuch_database_close (db)); + stat = notmuch_query_count_messages (query, &count); + + printf("%d\n", stat == NOTMUCH_STATUS_XAPIAN_EXCEPTION); + } +EOF +cat < EXPECTED +== stdout == +1 +== stderr == +A Xapian exception occurred performing query: Database has been closed +Query string was: id:1258471718-6781-1-git-send-email-dottedmag@dottedmag.net +EOF +test_expect_equal_file EXPECTED OUTPUT + +test_begin_subtest "count threads on closed db" +cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR} + { + notmuch_query_t *query; + const char *str = "id:1258471718-6781-1-git-send-email-dottedmag@dottedmag.net"; + unsigned int count; + + query = notmuch_query_create (db, str); + EXPECT0(notmuch_database_close (db)); + stat = notmuch_query_count_threads (query, &count); + + printf("%d\n", stat == NOTMUCH_STATUS_XAPIAN_EXCEPTION); + } +EOF +cat < EXPECTED +== stdout == +1 +== stderr == +A Xapian exception occurred performing query: Database has been closed +Query string was: id:1258471718-6781-1-git-send-email-dottedmag@dottedmag.net +EOF +test_expect_equal_file EXPECTED OUTPUT + test_done From 46468baa5a877b16bf0bb769fc00dd1c2c4b70b0 Mon Sep 17 00:00:00 2001 From: David Bremner Date: Wed, 22 Jul 2020 07:51:27 -0300 Subject: [PATCH 306/427] test: regression test for notmuch_query_destroy As with other void API entries, not crashing counts as success. --- test/T564-lib-query.sh | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/test/T564-lib-query.sh b/test/T564-lib-query.sh index 745d28a4..50b0a88e 100755 --- a/test/T564-lib-query.sh +++ b/test/T564-lib-query.sh @@ -231,4 +231,24 @@ Query string was: id:1258471718-6781-1-git-send-email-dottedmag@dottedmag.net EOF test_expect_equal_file EXPECTED OUTPUT +test_begin_subtest "destroy query with closed db" +cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR} + { + notmuch_query_t *query; + const char *str = "id:1258471718-6781-1-git-send-email-dottedmag@dottedmag.net"; + + query = notmuch_query_create (db, str); + EXPECT0(notmuch_database_close (db)); + notmuch_query_destroy (query); + + printf("SUCCESS\n"); + } +EOF +cat < EXPECTED +== stdout == +SUCCESS +== stderr == +EOF +test_expect_equal_file EXPECTED OUTPUT + test_done From 0baa581e6ee71ab76ced001ebebd7b96d8180ec2 Mon Sep 17 00:00:00 2001 From: David Bremner Date: Fri, 24 Jul 2020 08:13:59 -0300 Subject: [PATCH 307/427] test: regression test for n_t_get_thread_id This is just cached data, so as long as we don't prematurely free anything, it should be fine. --- test/T568-lib-thread.sh | 63 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100755 test/T568-lib-thread.sh diff --git a/test/T568-lib-thread.sh b/test/T568-lib-thread.sh new file mode 100755 index 00000000..00755834 --- /dev/null +++ b/test/T568-lib-thread.sh @@ -0,0 +1,63 @@ +#!/usr/bin/env bash +test_description="API tests for notmuch_thread_*" + +. $(dirname "$0")/test-lib.sh || exit 1 + +add_email_corpus + +test_begin_subtest "building database" +test_expect_success "NOTMUCH_NEW" + +cat <<'EOF' > c_tail + if (stat) { + const char *stat_str = notmuch_database_status_string (db); + if (stat_str) + fputs (stat_str, stderr); + } + +} +EOF + +cat < c_head +#include +#include +#include +int main (int argc, char** argv) +{ + notmuch_database_t *db; + notmuch_status_t stat; + char *msg = NULL; + notmuch_thread_t *thread = NULL; + notmuch_threads_t *threads = NULL; + notmuch_query_t *query = NULL; + const char *id = "thread:0000000000000009"; + + stat = notmuch_database_open_verbose (argv[1], NOTMUCH_DATABASE_MODE_READ_WRITE, &db, &msg); + if (stat != NOTMUCH_STATUS_SUCCESS) { + fprintf (stderr, "error opening database: %d %s\n", stat, msg ? msg : ""); + exit (1); + } + + query = notmuch_query_create (db, id); + EXPECT0(notmuch_query_search_threads (query, &threads)); + thread = notmuch_threads_get (threads); + EXPECT0(notmuch_database_close (db)); +EOF + +test_begin_subtest "get thread-id from closed database" +cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR} + { + const char *id2; + id2 = notmuch_thread_get_thread_id (thread); + printf("%d\n%s\n", thread != NULL, id2); + } +EOF +cat < EXPECTED +== stdout == +1 +0000000000000009 +== stderr == +EOF +test_expect_equal_file EXPECTED OUTPUT + +test_done From 02ab473115f064c8f53fc46cc06a33e40588906d Mon Sep 17 00:00:00 2001 From: David Bremner Date: Fri, 24 Jul 2020 08:14:00 -0300 Subject: [PATCH 308/427] test: add regression test for n_thread_get_total_{messages,files} This is returning cached info, so does not need to access the (closed) database. --- test/T568-lib-thread.sh | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/test/T568-lib-thread.sh b/test/T568-lib-thread.sh index 00755834..5816cfbc 100755 --- a/test/T568-lib-thread.sh +++ b/test/T568-lib-thread.sh @@ -60,4 +60,36 @@ cat < EXPECTED EOF test_expect_equal_file EXPECTED OUTPUT +test_begin_subtest "get total messages with closed database" +cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR} + { + int count; + count = notmuch_thread_get_total_messages (thread); + printf("%d\n%d\n", thread != NULL, count); + } +EOF +cat < EXPECTED +== stdout == +1 +7 +== stderr == +EOF +test_expect_equal_file EXPECTED OUTPUT + +test_begin_subtest "get total files with closed database" +cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR} + { + int count; + count = notmuch_thread_get_total_files (thread); + printf("%d\n%d\n", thread != NULL, count); + } +EOF +cat < EXPECTED +== stdout == +1 +7 +== stderr == +EOF +test_expect_equal_file EXPECTED OUTPUT + test_done From 6ccc4338a45dc61b531850f212cbc0e3ec7eae30 Mon Sep 17 00:00:00 2001 From: David Bremner Date: Fri, 24 Jul 2020 08:14:01 -0300 Subject: [PATCH 309/427] test: add regression tests for n_thread_get_toplevel_messages Include a test for the previously omitted non-trivial code path for notmuch_thread_get_replies. --- test/T566-lib-message.sh | 1 - test/T568-lib-thread.sh | 80 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 80 insertions(+), 1 deletion(-) diff --git a/test/T566-lib-message.sh b/test/T566-lib-message.sh index 0c5575a9..4cf35810 100755 --- a/test/T566-lib-message.sh +++ b/test/T566-lib-message.sh @@ -87,7 +87,6 @@ cat < EXPECTED EOF test_expect_equal_file EXPECTED OUTPUT -# XXX TODO: test on a message from notmuch_thread_get_toplevel_messages # XXX this test only tests the trivial code path test_begin_subtest "Handle getting replies from closed database" cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR} diff --git a/test/T568-lib-thread.sh b/test/T568-lib-thread.sh index 5816cfbc..4703950b 100755 --- a/test/T568-lib-thread.sh +++ b/test/T568-lib-thread.sh @@ -92,4 +92,84 @@ cat < EXPECTED EOF test_expect_equal_file EXPECTED OUTPUT +test_begin_subtest "get top level messages with closed database" +cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR} + { + notmuch_messages_t *messages; + messages = notmuch_thread_get_toplevel_messages (thread); + printf("%d\n%d\n", thread != NULL, messages != NULL); + } +EOF +cat < EXPECTED +== stdout == +1 +1 +== stderr == +EOF +test_expect_equal_file EXPECTED OUTPUT + +test_begin_subtest "iterate over level messages with closed database" +cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR} + { + notmuch_messages_t *messages; + for (messages = notmuch_thread_get_toplevel_messages (thread); + notmuch_messages_valid (messages); + notmuch_messages_move_to_next (messages)) { + notmuch_message_t *message = notmuch_messages_get (messages); + const char *mid = notmuch_message_get_message_id (message); + printf("%s\n", mid); + } + } +EOF +cat < EXPECTED +== stdout == +20091117190054.GU3165@dottiness.seas.harvard.edu +== stderr == +EOF +test_expect_equal_file EXPECTED OUTPUT + +test_begin_subtest "iterate over level messages with closed database" +cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR} + { + notmuch_messages_t *messages; + for (messages = notmuch_thread_get_toplevel_messages (thread); + notmuch_messages_valid (messages); + notmuch_messages_move_to_next (messages)) { + notmuch_message_t *message = notmuch_messages_get (messages); + const char *mid = notmuch_message_get_message_id (message); + printf("%s\n", mid); + } + } +EOF +cat < EXPECTED +== stdout == +20091117190054.GU3165@dottiness.seas.harvard.edu +== stderr == +EOF +test_expect_equal_file EXPECTED OUTPUT + +test_begin_subtest "iterate over replies with closed database" +cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR} + { + notmuch_messages_t *messages = notmuch_thread_get_toplevel_messages (thread); + notmuch_message_t *message = notmuch_messages_get (messages); + notmuch_messages_t *replies; + for (replies = notmuch_message_get_replies (message); + notmuch_messages_valid (replies); + notmuch_messages_move_to_next (replies)) { + notmuch_message_t *message = notmuch_messages_get (replies); + const char *mid = notmuch_message_get_message_id (message); + + printf("%s\n", mid); + } + } +EOF +cat < EXPECTED +== stdout == +87iqd9rn3l.fsf@vertex.dottedmag +87ocn0qh6d.fsf@yoom.home.cworth.org +== stderr == +EOF +test_expect_equal_file EXPECTED OUTPUT + test_done From 70b30066f67ea75d405c8a246bf6ed923e2bdd59 Mon Sep 17 00:00:00 2001 From: David Bremner Date: Fri, 24 Jul 2020 08:14:02 -0300 Subject: [PATCH 310/427] test: add regression test for n_t_get_messages This is similar to the case of toplevel messages. Currently everything is cached, so no database access is necessary. This might change in the future, but it should not crash in either case. --- test/T568-lib-thread.sh | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/test/T568-lib-thread.sh b/test/T568-lib-thread.sh index 4703950b..c7d4f26b 100755 --- a/test/T568-lib-thread.sh +++ b/test/T568-lib-thread.sh @@ -172,4 +172,30 @@ cat < EXPECTED EOF test_expect_equal_file EXPECTED OUTPUT +test_begin_subtest "iterate over all messages with closed database" +cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR} + { + notmuch_messages_t *messages; + for (messages = notmuch_thread_get_messages (thread); + notmuch_messages_valid (messages); + notmuch_messages_move_to_next (messages)) { + notmuch_message_t *message = notmuch_messages_get (messages); + const char *mid = notmuch_message_get_message_id (message); + printf("%s\n", mid); + } + } +EOF +cat < EXPECTED +== stdout == +20091117190054.GU3165@dottiness.seas.harvard.edu +87iqd9rn3l.fsf@vertex.dottedmag +20091117203301.GV3165@dottiness.seas.harvard.edu +87fx8can9z.fsf@vertex.dottedmag +yunaayketfm.fsf@aiko.keithp.com +20091118005040.GA25380@dottiness.seas.harvard.edu +87ocn0qh6d.fsf@yoom.home.cworth.org +== stderr == +EOF +test_expect_equal_file EXPECTED OUTPUT + test_done From c5017c76d939610860a64906be77fe62a64eae96 Mon Sep 17 00:00:00 2001 From: David Bremner Date: Fri, 24 Jul 2020 08:14:03 -0300 Subject: [PATCH 311/427] test: add regression tests for n_t_get_{authors, subject} This is returning explicitely cached data, so no database access is needed. --- test/T568-lib-thread.sh | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/test/T568-lib-thread.sh b/test/T568-lib-thread.sh index c7d4f26b..0f9fa443 100755 --- a/test/T568-lib-thread.sh +++ b/test/T568-lib-thread.sh @@ -198,4 +198,37 @@ yunaayketfm.fsf@aiko.keithp.com EOF test_expect_equal_file EXPECTED OUTPUT +test_begin_subtest "get authors from closed database" +cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR} + { + const char *authors; + authors = notmuch_thread_get_authors (thread); + printf("%d\n%s\n", thread != NULL, authors); + } +EOF +cat < EXPECTED +== stdout == +1 +Lars Kellogg-Stedman, Mikhail Gusarov, Keith Packard, Carl Worth +== stderr == +EOF +test_expect_equal_file EXPECTED OUTPUT + +test_begin_subtest "get subject from closed database" +cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR} + { + const char *subject; + subject = notmuch_thread_get_subject (thread); + printf("%d\n%s\n", thread != NULL, subject); + } +EOF +cat < EXPECTED +== stdout == +1 +[notmuch] Working with Maildir storage? +== stderr == +EOF +test_expect_equal_file EXPECTED OUTPUT + + test_done From 43406e7d4c081583878ede8f2b5a55f937fa4edd Mon Sep 17 00:00:00 2001 From: David Bremner Date: Fri, 24 Jul 2020 08:14:04 -0300 Subject: [PATCH 312/427] test: add regression tests for oldest and newest dates These are strictly to watch for crashes, so don't be too fussy about the actual timestamps. --- test/T568-lib-thread.sh | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/test/T568-lib-thread.sh b/test/T568-lib-thread.sh index 0f9fa443..3eb28f3b 100755 --- a/test/T568-lib-thread.sh +++ b/test/T568-lib-thread.sh @@ -230,5 +230,37 @@ cat < EXPECTED EOF test_expect_equal_file EXPECTED OUTPUT +test_begin_subtest "oldest date from closed database" +cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR} + { + time_t stamp; + stamp = notmuch_thread_get_oldest_date (thread); + printf("%d\n%d\n", thread != NULL, stamp > 0); + } +EOF +cat < EXPECTED +== stdout == +1 +1 +== stderr == +EOF +test_expect_equal_file EXPECTED OUTPUT + +test_begin_subtest "newest date from closed database" +cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR} + { + time_t stamp; + stamp = notmuch_thread_get_newest_date (thread); + printf("%d\n%d\n", thread != NULL, stamp > 0); + } +EOF +cat < EXPECTED +== stdout == +1 +1 +== stderr == +EOF +test_expect_equal_file EXPECTED OUTPUT + test_done From 864f422f149c709e5701ba194a32931d9f216e4c Mon Sep 17 00:00:00 2001 From: David Bremner Date: Fri, 24 Jul 2020 08:14:05 -0300 Subject: [PATCH 313/427] test: regression test for n_thread_get_tags Code is taken from the API docs, with the twist that the underlying database is closed. Not crashing is the main point. --- test/T568-lib-thread.sh | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/test/T568-lib-thread.sh b/test/T568-lib-thread.sh index 3eb28f3b..82e4ecb8 100755 --- a/test/T568-lib-thread.sh +++ b/test/T568-lib-thread.sh @@ -262,5 +262,28 @@ cat < EXPECTED EOF test_expect_equal_file EXPECTED OUTPUT +test_begin_subtest "iterate tags from closed database" +cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR} + { + notmuch_tags_t *tags; + const char *tag; + for (tags = notmuch_thread_get_tags (thread); + notmuch_tags_valid (tags); + notmuch_tags_move_to_next (tags)) + { + tag = notmuch_tags_get (tags); + printf ("%s\n", tag); + } + } +EOF +cat < EXPECTED +== stdout == +inbox +signed +unread +== stderr == +EOF +test_expect_equal_file EXPECTED OUTPUT + test_done From 00f48f397abcdc662041726b25dac0173880f2e5 Mon Sep 17 00:00:00 2001 From: David Bremner Date: Fri, 24 Jul 2020 08:14:06 -0300 Subject: [PATCH 314/427] test: destroy thread from closed database Check for (non)-crash. --- test/T568-lib-thread.sh | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/test/T568-lib-thread.sh b/test/T568-lib-thread.sh index 82e4ecb8..66066854 100755 --- a/test/T568-lib-thread.sh +++ b/test/T568-lib-thread.sh @@ -285,5 +285,19 @@ unread EOF test_expect_equal_file EXPECTED OUTPUT +test_begin_subtest "destroy thread with closed database" +cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR} + { + time_t stamp; + notmuch_thread_destroy (thread); + printf("SUCCESS\n"); + } +EOF +cat < EXPECTED +== stdout == +SUCCESS +== stderr == +EOF +test_expect_equal_file EXPECTED OUTPUT test_done From 29a58ecf4a8859766861170cd6f7adec9441edea Mon Sep 17 00:00:00 2001 From: David Bremner Date: Wed, 29 Jul 2020 21:01:29 -0300 Subject: [PATCH 315/427] test: drop NOTMUCH_DEFAULT_XAPIAN_BACKEND from T360-symbol-test Inspired by the suggestion of id:20200727154108.16269-3-congdanhqx@gmail.com to drop the configuration test for the default backend. This version is hopefully robust against backend changes. --- test/T360-symbol-hiding.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/T360-symbol-hiding.sh b/test/T360-symbol-hiding.sh index 43921cb4..b34f1e54 100755 --- a/test/T360-symbol-hiding.sh +++ b/test/T360-symbol-hiding.sh @@ -14,11 +14,11 @@ test_description='exception symbol hiding' test_begin_subtest 'running test' run_test mkdir -p ${PWD}/fakedb/.notmuch $TEST_DIRECTORY/symbol-test ${PWD}/fakedb ${PWD}/nonexistent 2>&1 \ - | notmuch_dir_sanitize | sed -e "s,\`,\',g" -e "s,${NOTMUCH_DEFAULT_XAPIAN_BACKEND},backend,g" > OUTPUT + | notmuch_dir_sanitize | sed -e "s,\`,\',g" -e "s,No [^[:space:]]* database,No XXXXXX database,g" > OUTPUT cat < EXPECTED A Xapian exception occurred opening database: Couldn't stat 'CWD/fakedb/.notmuch/xapian' -caught No backend database found at path 'CWD/nonexistent' +caught No XXXXXX database found at path 'CWD/nonexistent' EOF test_expect_equal_file EXPECTED OUTPUT From 99a7aac8f2af6f3adb8f816be46ee33eb1d57515 Mon Sep 17 00:00:00 2001 From: David Bremner Date: Wed, 29 Jul 2020 21:01:30 -0300 Subject: [PATCH 316/427] test: drop use of db_ending This will allow the dropping of the test for the default ending from configure. --- test/T050-new.sh | 4 ++-- test/T060-count.sh | 5 +++-- test/T150-tagging.sh | 4 ++-- test/T560-lib-error.sh | 14 +++++++------- test/test-lib.sh | 11 ----------- 5 files changed, 14 insertions(+), 24 deletions(-) diff --git a/test/T050-new.sh b/test/T050-new.sh index dfc8508f..009b2633 100755 --- a/test/T050-new.sh +++ b/test/T050-new.sh @@ -341,9 +341,9 @@ notmuch config set new.tags $OLDCONFIG test_begin_subtest "Xapian exception: read only files" -chmod u-w ${MAIL_DIR}/.notmuch/xapian/*.${db_ending} +chmod u-w ${MAIL_DIR}/.notmuch/xapian/*.* output=$(NOTMUCH_NEW --debug 2>&1 | sed 's/: .*$//' ) -chmod u+w ${MAIL_DIR}/.notmuch/xapian/*.${db_ending} +chmod u+w ${MAIL_DIR}/.notmuch/xapian/*.* test_expect_equal "$output" "A Xapian exception occurred opening database" diff --git a/test/T060-count.sh b/test/T060-count.sh index 0c0bf473..a1ebf8ba 100755 --- a/test/T060-count.sh +++ b/test/T060-count.sh @@ -95,7 +95,8 @@ test_expect_equal_file EXPECTED OUTPUT backup_database test_begin_subtest "error message for database open" -dd if=/dev/zero of="${MAIL_DIR}/.notmuch/xapian/postlist.${db_ending}" count=3 +target=(${MAIL_DIR}/.notmuch/xapian/postlist.*) +dd if=/dev/zero of="$target" count=3 notmuch count '*' 2>OUTPUT 1>/dev/null output=$(sed 's/^\(A Xapian exception [^:]*\):.*$/\1/' OUTPUT) test_expect_equal "${output}" "A Xapian exception occurred opening database" @@ -107,7 +108,7 @@ set logging file count-files-gdb.log set logging on break count_files commands -shell cp /dev/null ${MAIL_DIR}/.notmuch/xapian/postlist.${db_ending} +shell cp /dev/null ${MAIL_DIR}/.notmuch/xapian/postlist.* continue end run diff --git a/test/T150-tagging.sh b/test/T150-tagging.sh index 208b4b98..2f0b3531 100755 --- a/test/T150-tagging.sh +++ b/test/T150-tagging.sh @@ -305,9 +305,9 @@ test_begin_subtest "Tag name beginning with -" test_expect_code 1 'notmuch tag +- One' test_begin_subtest "Xapian exception: read only files" -chmod u-w ${MAIL_DIR}/.notmuch/xapian/*.${db_ending} +chmod u-w ${MAIL_DIR}/.notmuch/xapian/*.* output=$(notmuch tag +something '*' 2>&1 | sed 's/: .*$//' ) -chmod u+w ${MAIL_DIR}/.notmuch/xapian/*.${db_ending} +chmod u+w ${MAIL_DIR}/.notmuch/xapian/*.* test_expect_equal "$output" "A Xapian exception occurred opening database" test_done diff --git a/test/T560-lib-error.sh b/test/T560-lib-error.sh index 70df292a..260ac120 100755 --- a/test/T560-lib-error.sh +++ b/test/T560-lib-error.sh @@ -211,8 +211,7 @@ int main (int argc, char** argv) fprintf (stderr, "error opening database: %d %s\n", stat, msg ? msg : ""); exit (1); } - path = talloc_asprintf (db, "%s/.notmuch/xapian/postlist.${db_ending}", argv[1]); - fd = open(path,O_WRONLY|O_TRUNC); + fd = open(argv[2],O_WRONLY|O_TRUNC); if (fd < 0) { fprintf (stderr, "error opening %s\n", argv[1]); exit (1); @@ -228,9 +227,10 @@ cat <<'EOF' > c_tail } EOF +POSTLIST_PATH=(${MAIL_DIR}/.notmuch/xapian/postlist.*) backup_database test_begin_subtest "Xapian exception finding message" -cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR} +cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR} ${POSTLIST_PATH} { notmuch_message_t *message = NULL; stat = notmuch_database_find_message (db, "id:nonexistent", &message); @@ -247,7 +247,7 @@ restore_database backup_database test_begin_subtest "Xapian exception getting tags" -cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR} +cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR} ${POSTLIST_PATH} { notmuch_tags_t *tags = NULL; tags = notmuch_database_get_all_tags (db); @@ -265,7 +265,7 @@ restore_database backup_database test_begin_subtest "Xapian exception creating directory" -cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR} +cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR} ${POSTLIST_PATH} { notmuch_directory_t *directory = NULL; stat = notmuch_database_get_directory (db, "none/existing", &directory); @@ -282,7 +282,7 @@ restore_database backup_database test_begin_subtest "Xapian exception searching messages" -cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR} +cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR} ${POSTLIST_PATH} { notmuch_messages_t *messages = NULL; notmuch_query_t *query=notmuch_query_create (db, "*"); @@ -301,7 +301,7 @@ restore_database backup_database test_begin_subtest "Xapian exception counting messages" -cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR} +cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR} ${POSTLIST_PATH} { int count; notmuch_query_t *query=notmuch_query_create (db, "id:87ocn0qh6d.fsf@yoom.home.cworth.org"); diff --git a/test/test-lib.sh b/test/test-lib.sh index 31c858d1..527c9e8b 100644 --- a/test/test-lib.sh +++ b/test/test-lib.sh @@ -1261,17 +1261,6 @@ test -z "$NO_PYTHON" && test_set_prereq PYTHON ln -s x y 2>/dev/null && test -h y 2>/dev/null && test_set_prereq SYMLINKS rm -f y -# convert variable from configure to more convenient form -case "$NOTMUCH_DEFAULT_XAPIAN_BACKEND" in - glass) - db_ending=glass - ;; - chert) - db_ending=DB - ;; - *) - error "Unknown Xapian backend $NOTMUCH_DEFAULT_XAPIAN_BACKEND" -esac # declare prerequisites for external binaries used in tests test_declare_external_prereq dtach test_declare_external_prereq emacs From 3f8b01a3ef9e50a772897c67e0146172b3849749 Mon Sep 17 00:00:00 2001 From: David Bremner Date: Thu, 16 Jul 2020 08:11:26 -0300 Subject: [PATCH 317/427] build: upload html docs as part of release process Use a URL https://notmuchmail.org/doc/latest to leave room for a future more ambitious scheme deploying multiple versions. This also forces the html docs to built as part of the release process. In the future this should be updated to tolerate generating a release without sphinx installed. This needs a new target analogous to build-info and build-man that does nothing if sphinx is not installed. --- Makefile.global | 1 + Makefile.local | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/Makefile.global b/Makefile.global index 98b6962e..4fd796e3 100644 --- a/Makefile.global +++ b/Makefile.global @@ -39,6 +39,7 @@ DEB_TAG=debian/$(UPSTREAM_TAG)-1 RELEASE_HOST=notmuchmail.org RELEASE_DIR=/srv/notmuchmail.org/www/releases +DOC_DIR=/srv/notmuchmail.org/www/doc/latest RELEASE_URL=https://notmuchmail.org/releases TAR_FILE=$(PACKAGE)-$(VERSION).tar.xz ELPA_FILE:=$(PACKAGE)-emacs-$(ELPA_VERSION).tar diff --git a/Makefile.local b/Makefile.local index 22577617..39f36d50 100644 --- a/Makefile.local +++ b/Makefile.local @@ -64,7 +64,7 @@ update-versions: # are part of the release and need to take the version from the # version file). .PHONY: release -release: verify-source-tree-and-version +release: verify-source-tree-and-version sphinx-html $(MAKE) VERSION=$(VERSION) verify-newer $(MAKE) VERSION=$(VERSION) clean $(MAKE) VERSION=$(VERSION) test @@ -80,6 +80,7 @@ ifeq ($(REALLY_UPLOAD),yes) git push origin $(VERSION) $(DEB_TAG) release pristine-tar cd releases && scp $(TAR_FILE) $(SHA256_FILE) $(DETACHED_SIG_FILE) $(RELEASE_HOST):$(RELEASE_DIR) ssh $(RELEASE_HOST) "rm -f $(RELEASE_DIR)/LATEST-$(PACKAGE)-* ; ln -s $(TAR_FILE) $(RELEASE_DIR)/LATEST-$(TAR_FILE)" + rsync --verbose --delete --recursive doc/_build/html/ $(RELEASE_HOST):$(DOC_DIR) endif @echo "Please send a release announcement using $(PACKAGE)-$(VERSION).announce as a template." From 31dab9b1cd66c7f285fe86021f66940f679325d4 Mon Sep 17 00:00:00 2001 From: David Bremner Date: Sat, 1 Aug 2020 09:25:50 -0300 Subject: [PATCH 318/427] lib: return NULL from n_d_get_default_indexopts on error This is a rare and probably serious programming error, so better not to silently return a default value. --- lib/indexopts.c | 2 +- lib/notmuch.h | 1 + test/T562-lib-database.sh | 1 - 3 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/indexopts.c b/lib/indexopts.c index 1e8d180e..82a0026f 100644 --- a/lib/indexopts.c +++ b/lib/indexopts.c @@ -32,7 +32,7 @@ notmuch_database_get_default_indexopts (notmuch_database_t *db) char *decrypt_policy; notmuch_status_t err = notmuch_database_get_config (db, "index.decrypt", &decrypt_policy); if (err) - return ret; + return NULL; if (decrypt_policy) { if ((! (strcasecmp (decrypt_policy, "true"))) || diff --git a/lib/notmuch.h b/lib/notmuch.h index f9e9cc41..aaf92470 100644 --- a/lib/notmuch.h +++ b/lib/notmuch.h @@ -2320,6 +2320,7 @@ notmuch_config_list_destroy (notmuch_config_list_t *config_list); * added to the index. At the moment it is a featureless stub. * * @since libnotmuch 5.1 (notmuch 0.26) + * @retval NULL in case of error */ notmuch_indexopts_t * notmuch_database_get_default_indexopts (notmuch_database_t *db); diff --git a/test/T562-lib-database.sh b/test/T562-lib-database.sh index 32fda72e..ce62eaa8 100755 --- a/test/T562-lib-database.sh +++ b/test/T562-lib-database.sh @@ -373,7 +373,6 @@ EOF test_expect_equal_file EXPECTED OUTPUT test_begin_subtest "get indexopts from closed database" -test_subtest_known_broken cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR} { notmuch_indexopts_t *result; From e5e17926dbb0739ddd23b56599a8a580225f01a2 Mon Sep 17 00:00:00 2001 From: David Bremner Date: Sat, 1 Aug 2020 09:25:51 -0300 Subject: [PATCH 319/427] test: add regression test for n_messages_collect_tags Also test n_messages_destroy. --- test/T568-lib-thread.sh | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/test/T568-lib-thread.sh b/test/T568-lib-thread.sh index 66066854..ac13d986 100755 --- a/test/T568-lib-thread.sh +++ b/test/T568-lib-thread.sh @@ -285,6 +285,37 @@ unread EOF test_expect_equal_file EXPECTED OUTPUT +test_begin_subtest "collect tags with closed database" +cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR} + { + notmuch_messages_t *messages = notmuch_thread_get_messages (thread); + + notmuch_tags_t *tags = notmuch_messages_collect_tags (messages); + + const char *tag; + for (tags = notmuch_thread_get_tags (thread); + notmuch_tags_valid (tags); + notmuch_tags_move_to_next (tags)) + { + tag = notmuch_tags_get (tags); + printf ("%s\n", tag); + } + notmuch_tags_destroy (tags); + notmuch_messages_destroy (messages); + + printf("SUCCESS\n"); + } +EOF +cat < EXPECTED +== stdout == +inbox +signed +unread +SUCCESS +== stderr == +EOF +test_expect_equal_file EXPECTED OUTPUT + test_begin_subtest "destroy thread with closed database" cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR} { From 2b6e73d895ccd35ede4fd9f20952de133423cf08 Mon Sep 17 00:00:00 2001 From: David Bremner Date: Sat, 1 Aug 2020 09:25:52 -0300 Subject: [PATCH 320/427] test: split header for lib-message tests. This allows finer control over when to close the database. --- test/T566-lib-message.sh | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/test/T566-lib-message.sh b/test/T566-lib-message.sh index 4cf35810..3b0e85b5 100755 --- a/test/T566-lib-message.sh +++ b/test/T566-lib-message.sh @@ -18,7 +18,7 @@ cat <<'EOF' > c_tail } EOF -cat < c_head +cat < c_head0 #include #include #include @@ -36,9 +36,11 @@ int main (int argc, char** argv) exit (1); } EXPECT0(notmuch_database_find_message (db, id, &message)); - EXPECT0(notmuch_database_close (db)); EOF +cp c_head0 c_head +echo " EXPECT0(notmuch_database_close (db));" >> c_head + test_begin_subtest "Handle getting message-id from closed database" cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR} { From c151506773e76345508af8e46d7787d73fcffc62 Mon Sep 17 00:00:00 2001 From: David Bremner Date: Sat, 1 Aug 2020 09:25:53 -0300 Subject: [PATCH 321/427] test: regression test for n_m_get_filenames Closing the database after the iterator is created is not a problem. --- test/T566-lib-message.sh | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/test/T566-lib-message.sh b/test/T566-lib-message.sh index 3b0e85b5..0ba601f9 100755 --- a/test/T566-lib-message.sh +++ b/test/T566-lib-message.sh @@ -138,6 +138,29 @@ cat < EXPECTED EOF test_expect_equal_file EXPECTED OUTPUT +test_begin_subtest "iterate over all message filenames from closed database" +cat c_head0 - c_tail <<'EOF' | test_C ${MAIL_DIR} + { + notmuch_filenames_t *filenames; + filenames = notmuch_message_get_filenames (message); + EXPECT0(notmuch_database_close (db)); + for (; notmuch_filenames_valid (filenames); + notmuch_filenames_move_to_next (filenames)) { + const char *filename = notmuch_filenames_get (filenames); + printf("%s\n", filename); + } + notmuch_filenames_destroy (filenames); + printf("SUCCESS\n"); + } +EOF +cat < EXPECTED +== stdout == +MAIL_DIR/01:2, +SUCCESS +== stderr == +EOF +test_expect_equal_file EXPECTED OUTPUT + test_begin_subtest "Handle getting ghost flag from closed database" cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR} { From 919b89156c4753a79e35f2b96d2d5022578fa626 Mon Sep 17 00:00:00 2001 From: David Bremner Date: Sat, 1 Aug 2020 09:25:54 -0300 Subject: [PATCH 322/427] test: add known broken test for n_directory_get_child_directories Start a new test file (for the notmuch_directory_* API group) to hold this test. --- test/T563-lib-directory.sh | 61 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100755 test/T563-lib-directory.sh diff --git a/test/T563-lib-directory.sh b/test/T563-lib-directory.sh new file mode 100755 index 00000000..15fb8835 --- /dev/null +++ b/test/T563-lib-directory.sh @@ -0,0 +1,61 @@ +#!/usr/bin/env bash +test_description="notmuch_directory_* API" + +. $(dirname "$0")/test-lib.sh || exit 1 + +add_email_corpus + +test_begin_subtest "building database" +test_expect_success "NOTMUCH_NEW" + +cat < c_head +#include +#include +#include +#include +int main (int argc, char** argv) +{ + notmuch_database_t *db; + notmuch_directory_t *dir; + notmuch_status_t stat = NOTMUCH_STATUS_SUCCESS; + char *msg = NULL; + + stat = notmuch_database_open_verbose (argv[1], NOTMUCH_DATABASE_MODE_READ_WRITE, &db, &msg); + if (stat != NOTMUCH_STATUS_SUCCESS) { + fprintf (stderr, "error opening database: %d %s\n", stat, msg ? msg : ""); + exit (1); + } + + EXPECT0(notmuch_database_get_directory (db, "bar", &dir)); + EXPECT0(notmuch_database_close (db)); +EOF + +cat <<'EOF' > c_tail + if (stat) { + const char *stat_str = notmuch_database_status_string (db); + if (stat_str) + fputs (stat_str, stderr); + } + +} +EOF + +test_begin_subtest "get child directories for a closed db" +test_subtest_known_broken +cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR} + { + notmuch_filenames_t *children; + children = notmuch_directory_get_child_directories (dir); + printf ("%d\n", children == NULL); + stat = NOTMUCH_STATUS_XAPIAN_EXCEPTION; + } +EOF +cat < EXPECTED +== stdout == +1 +== stderr == +A Xapian exception occurred at lib/directory.cc:XXX: Database has been closed +EOF +test_expect_equal_file EXPECTED OUTPUT + +test_done From 9668583a49e9fe2e39844ceaab20779c9444ba8f Mon Sep 17 00:00:00 2001 From: David Bremner Date: Sat, 1 Aug 2020 09:25:55 -0300 Subject: [PATCH 323/427] lib: catch exceptions in n_directory_get_child_directories Also clarify API in error case. --- lib/directory.cc | 23 ++++++++++++++++++++--- lib/notmuch.h | 2 ++ test/T563-lib-directory.sh | 1 - 3 files changed, 22 insertions(+), 4 deletions(-) diff --git a/lib/directory.cc b/lib/directory.cc index b9c3d77f..5d13afec 100644 --- a/lib/directory.cc +++ b/lib/directory.cc @@ -49,6 +49,19 @@ struct _notmuch_directory { time_t mtime; }; +#define LOG_XAPIAN_EXCEPTION(directory, error) _log_xapian_exception (__location__, directory, error) + +static void +_log_xapian_exception (const char *where, notmuch_directory_t *dir, const Xapian::Error error) { + notmuch_database_t *notmuch = dir->notmuch; + _notmuch_database_log (notmuch, + "A Xapian exception occurred at %s: %s\n", + where, + error.get_msg ().c_str ()); + notmuch->exception_reported = true; +} + + /* We end up having to call the destructor explicitly because we had * to use "placement new" in order to initialize C++ objects within a * block that we allocated with talloc. So C++ is making talloc @@ -267,14 +280,18 @@ notmuch_filenames_t * notmuch_directory_get_child_directories (notmuch_directory_t *directory) { char *term; - notmuch_filenames_t *child_directories; + notmuch_filenames_t *child_directories = NULL; term = talloc_asprintf (directory, "%s%u:", _find_prefix ("directory-direntry"), directory->document_id); - child_directories = _create_filenames_for_terms_with_prefix (directory, - directory->notmuch, term); + try { + child_directories = _create_filenames_for_terms_with_prefix (directory, + directory->notmuch, term); + } catch (Xapian::Error &error) { + LOG_XAPIAN_EXCEPTION (directory, error); + } talloc_free (term); diff --git a/lib/notmuch.h b/lib/notmuch.h index aaf92470..e59fc571 100644 --- a/lib/notmuch.h +++ b/lib/notmuch.h @@ -2158,6 +2158,8 @@ notmuch_directory_get_child_files (notmuch_directory_t *directory); * * The returned filenames will be the basename-entries only (not * complete paths). + * + * Returns NULL if it triggers a Xapian exception */ notmuch_filenames_t * notmuch_directory_get_child_directories (notmuch_directory_t *directory); diff --git a/test/T563-lib-directory.sh b/test/T563-lib-directory.sh index 15fb8835..5e7da676 100755 --- a/test/T563-lib-directory.sh +++ b/test/T563-lib-directory.sh @@ -41,7 +41,6 @@ cat <<'EOF' > c_tail EOF test_begin_subtest "get child directories for a closed db" -test_subtest_known_broken cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR} { notmuch_filenames_t *children; From 3890d989665f5906fa82a10f1a57157c7b23503c Mon Sep 17 00:00:00 2001 From: David Bremner Date: Sat, 1 Aug 2020 09:25:56 -0300 Subject: [PATCH 324/427] test: add known broken test for n_directory_get_child_files This is a clone of the one for get_child_directories --- test/T563-lib-directory.sh | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/test/T563-lib-directory.sh b/test/T563-lib-directory.sh index 5e7da676..d71c884d 100755 --- a/test/T563-lib-directory.sh +++ b/test/T563-lib-directory.sh @@ -57,4 +57,22 @@ A Xapian exception occurred at lib/directory.cc:XXX: Database has been closed EOF test_expect_equal_file EXPECTED OUTPUT +test_begin_subtest "get child filenames for a closed db" +test_subtest_known_broken +cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR} + { + notmuch_filenames_t *children; + children = notmuch_directory_get_child_files (dir); + printf ("%d\n", children == NULL); + stat = NOTMUCH_STATUS_XAPIAN_EXCEPTION; + } +EOF +cat < EXPECTED +== stdout == +1 +== stderr == +A Xapian exception occurred at lib/directory.cc:XXX: Database has been closed +EOF +test_expect_equal_file EXPECTED OUTPUT + test_done From 337665e26a4f0bb0e24a9160f45faf444032bf5d Mon Sep 17 00:00:00 2001 From: David Bremner Date: Sat, 1 Aug 2020 09:25:57 -0300 Subject: [PATCH 325/427] lib: catch exceptions in n_directory_get_child_files Also clarify API in error case. --- lib/directory.cc | 12 ++++++++---- lib/notmuch.h | 2 ++ test/T563-lib-directory.sh | 1 - 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/lib/directory.cc b/lib/directory.cc index 5d13afec..79ceea31 100644 --- a/lib/directory.cc +++ b/lib/directory.cc @@ -261,15 +261,19 @@ notmuch_filenames_t * notmuch_directory_get_child_files (notmuch_directory_t *directory) { char *term; - notmuch_filenames_t *child_files; + notmuch_filenames_t *child_files = NULL; term = talloc_asprintf (directory, "%s%u:", _find_prefix ("file-direntry"), directory->document_id); - child_files = _create_filenames_for_terms_with_prefix (directory, - directory->notmuch, - term); + try { + child_files = _create_filenames_for_terms_with_prefix (directory, + directory->notmuch, + term); + } catch (Xapian::Error &error) { + LOG_XAPIAN_EXCEPTION (directory, error); + } talloc_free (term); diff --git a/lib/notmuch.h b/lib/notmuch.h index e59fc571..9a2a3b1f 100644 --- a/lib/notmuch.h +++ b/lib/notmuch.h @@ -2148,6 +2148,8 @@ notmuch_directory_get_mtime (notmuch_directory_t *directory); * * The returned filenames will be the basename-entries only (not * complete paths). + * + * Returns NULL if it triggers a Xapian exception */ notmuch_filenames_t * notmuch_directory_get_child_files (notmuch_directory_t *directory); diff --git a/test/T563-lib-directory.sh b/test/T563-lib-directory.sh index d71c884d..61ed8add 100755 --- a/test/T563-lib-directory.sh +++ b/test/T563-lib-directory.sh @@ -58,7 +58,6 @@ EOF test_expect_equal_file EXPECTED OUTPUT test_begin_subtest "get child filenames for a closed db" -test_subtest_known_broken cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR} { notmuch_filenames_t *children; From b03cc6cf90c302b66453feb081b36fabe927a576 Mon Sep 17 00:00:00 2001 From: David Bremner Date: Sat, 1 Aug 2020 09:25:58 -0300 Subject: [PATCH 326/427] test: known broken test for n_directory_delete with closed db. There is a return value bug in notmuch_directory_delete that is hiding the exception. --- test/T563-lib-directory.sh | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/test/T563-lib-directory.sh b/test/T563-lib-directory.sh index 61ed8add..b91a1c87 100755 --- a/test/T563-lib-directory.sh +++ b/test/T563-lib-directory.sh @@ -74,4 +74,40 @@ A Xapian exception occurred at lib/directory.cc:XXX: Database has been closed EOF test_expect_equal_file EXPECTED OUTPUT +backup_database +test_begin_subtest "delete directory document for a closed db" +test_subtest_known_broken +cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR} + { + stat = notmuch_directory_delete (dir); + printf ("%d\n", stat == NOTMUCH_STATUS_XAPIAN_EXCEPTION); + } +EOF +cat < EXPECTED +== stdout == +1 +== stderr == +A Xapian exception occurred deleting directory entry: Database has been closed. +EOF +test_expect_equal_file EXPECTED OUTPUT +restore_database + +backup_database +test_begin_subtest "get/set mtime of directory for a closed db" +cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR} + { + time_t stamp = notmuch_directory_get_mtime (dir); + stat = notmuch_directory_set_mtime (dir, stamp); + printf ("%d\n", stat == NOTMUCH_STATUS_XAPIAN_EXCEPTION); + } +EOF +cat < EXPECTED +== stdout == +1 +== stderr == +A Xapian exception occurred setting directory mtime: Database has been closed. +EOF +test_expect_equal_file EXPECTED OUTPUT +restore_database + test_done From f4f5db0f1a6e89f56bfd73f8517c1cac7af20290 Mon Sep 17 00:00:00 2001 From: David Bremner Date: Sat, 1 Aug 2020 09:25:59 -0300 Subject: [PATCH 327/427] lib: fix return value for n_directory_delete Falling out of the catch meant the error return was lost --- lib/directory.cc | 2 +- test/T563-lib-directory.sh | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/directory.cc b/lib/directory.cc index 79ceea31..eee8254e 100644 --- a/lib/directory.cc +++ b/lib/directory.cc @@ -323,7 +323,7 @@ notmuch_directory_delete (notmuch_directory_t *directory) } notmuch_directory_destroy (directory); - return NOTMUCH_STATUS_SUCCESS; + return status; } void diff --git a/test/T563-lib-directory.sh b/test/T563-lib-directory.sh index b91a1c87..7e44e805 100755 --- a/test/T563-lib-directory.sh +++ b/test/T563-lib-directory.sh @@ -76,7 +76,6 @@ test_expect_equal_file EXPECTED OUTPUT backup_database test_begin_subtest "delete directory document for a closed db" -test_subtest_known_broken cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR} { stat = notmuch_directory_delete (dir); From e8ffbf84ffa5ae6c02afd339a75ebb24c0a958b2 Mon Sep 17 00:00:00 2001 From: David Bremner Date: Sat, 1 Aug 2020 09:26:00 -0300 Subject: [PATCH 328/427] test: regression test for n_directory_{get,set}_mtime The mtime is cached, so closing the db is not a problem. Writing the mtime throws an exception, which is caught. --- test/T563-lib-directory.sh | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/test/T563-lib-directory.sh b/test/T563-lib-directory.sh index 7e44e805..28325ff2 100755 --- a/test/T563-lib-directory.sh +++ b/test/T563-lib-directory.sh @@ -109,4 +109,22 @@ EOF test_expect_equal_file EXPECTED OUTPUT restore_database +backup_database +test_begin_subtest "get/set mtime of directory for a closed db" +cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR} + { + time_t stamp = notmuch_directory_get_mtime (dir); + stat = notmuch_directory_set_mtime (dir, stamp); + printf ("%d\n", stat == NOTMUCH_STATUS_XAPIAN_EXCEPTION); + } +EOF +cat < EXPECTED +== stdout == +1 +== stderr == +A Xapian exception occurred setting directory mtime: Database has been closed. +EOF +test_expect_equal_file EXPECTED OUTPUT +restore_database + test_done From a0b2a54aa7514c3360c791fc186848df49a8c5fd Mon Sep 17 00:00:00 2001 From: David Bremner Date: Sat, 1 Aug 2020 09:26:01 -0300 Subject: [PATCH 329/427] test: regression test for n_d_get_config_list on closed db. Exception is caught. --- test/T590-libconfig.sh | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/test/T590-libconfig.sh b/test/T590-libconfig.sh index 46f3a76d..602fad0b 100755 --- a/test/T590-libconfig.sh +++ b/test/T590-libconfig.sh @@ -61,6 +61,21 @@ valid = 0 EOF test_expect_equal_file EXPECTED OUTPUT +test_begin_subtest "notmuch_database_get_config_list: closed db" +cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR} +{ + notmuch_config_list_t *list; + EXPECT0(notmuch_database_close (db)); + stat = notmuch_database_get_config_list (db, "nonexistent", &list); + printf("%d\n", stat == NOTMUCH_STATUS_XAPIAN_EXCEPTION); +} +EOF +cat <<'EOF' >EXPECTED +== stdout == +1 +== stderr == +EOF +test_expect_equal_file EXPECTED OUTPUT test_begin_subtest "notmuch_database_get_config_list: all pairs" cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR} From e3f88436b7850ac04468416223e136eaaca4aee3 Mon Sep 17 00:00:00 2001 From: David Bremner Date: Sat, 1 Aug 2020 09:26:02 -0300 Subject: [PATCH 330/427] test: regression test for traversing config list with closed db Also mention error return in API docs --- lib/notmuch.h | 1 + test/T590-libconfig.sh | 22 ++++++++++++++++++++++ 2 files changed, 23 insertions(+) diff --git a/lib/notmuch.h b/lib/notmuch.h index 9a2a3b1f..f3cb0fe2 100644 --- a/lib/notmuch.h +++ b/lib/notmuch.h @@ -2291,6 +2291,7 @@ notmuch_config_list_key (notmuch_config_list_t *config_list); * next call to notmuch_config_list_value or notmuch config_list_destroy * * @since libnotmuch 4.4 (notmuch 0.23) + * @retval NULL for errors */ const char * notmuch_config_list_value (notmuch_config_list_t *config_list); diff --git a/test/T590-libconfig.sh b/test/T590-libconfig.sh index 602fad0b..360e45b0 100755 --- a/test/T590-libconfig.sh +++ b/test/T590-libconfig.sh @@ -100,6 +100,28 @@ zzzafter afterval EOF test_expect_equal_file EXPECTED OUTPUT +test_begin_subtest "notmuch_database_get_config_list: all pairs (closed db)" +cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR} +{ + notmuch_config_list_t *list; + EXPECT0(notmuch_database_get_config_list (db, "", &list)); + EXPECT0(notmuch_database_close (db)); + for (; notmuch_config_list_valid (list); notmuch_config_list_move_to_next (list)) { + printf("%s %d\n", notmuch_config_list_key (list), NULL == notmuch_config_list_value(list)); + } + notmuch_config_list_destroy (list); +} +EOF +cat <<'EOF' >EXPECTED +== stdout == +aaabefore 1 +testkey1 1 +testkey2 1 +zzzafter 1 +== stderr == +EOF +test_expect_equal_file EXPECTED OUTPUT + test_begin_subtest "notmuch_database_get_config_list: one prefix" cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR} { From 0e4695abaa7bda20d92813571ad53fb141f3e38c Mon Sep 17 00:00:00 2001 From: David Bremner Date: Sat, 1 Aug 2020 09:26:03 -0300 Subject: [PATCH 331/427] test: regression tests for n_indexopts_{get,set}_decrypt_policy The main criteria is that they don't crash. Working with a closed database is a bonus. --- test/T562-lib-database.sh | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/test/T562-lib-database.sh b/test/T562-lib-database.sh index ce62eaa8..d6097418 100755 --- a/test/T562-lib-database.sh +++ b/test/T562-lib-database.sh @@ -388,4 +388,43 @@ cat < EXPECTED EOF test_expect_equal_file EXPECTED OUTPUT +test_begin_subtest "get decryption policy from closed database" +cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR} + { + notmuch_indexopts_t *result; + result = notmuch_database_get_default_indexopts (db); + EXPECT0(notmuch_database_close (db)); + notmuch_decryption_policy_t policy = notmuch_indexopts_get_decrypt_policy (result); + printf ("%d\n", policy == NOTMUCH_DECRYPT_AUTO); + notmuch_indexopts_destroy (result); + printf ("SUCCESS\n"); + } +EOF +cat < EXPECTED +== stdout == +1 +SUCCESS +== stderr == +EOF +test_expect_equal_file EXPECTED OUTPUT + +test_begin_subtest "set decryption policy with closed database" +cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR} + { + notmuch_indexopts_t *result; + result = notmuch_database_get_default_indexopts (db); + EXPECT0(notmuch_database_close (db)); + notmuch_decryption_policy_t policy = notmuch_indexopts_get_decrypt_policy (result); + stat = notmuch_indexopts_set_decrypt_policy (result, policy); + printf("%d\n%d\n", policy == NOTMUCH_DECRYPT_AUTO, stat == NOTMUCH_STATUS_SUCCESS); + } +EOF +cat < EXPECTED +== stdout == +1 +1 +== stderr == +EOF +test_expect_equal_file EXPECTED OUTPUT + test_done From e4c4cc1b34cfaa6d7b96053644fa821878a67c56 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C4=90o=C3=A0n=20Tr=E1=BA=A7n=20C=C3=B4ng=20Danh?= Date: Mon, 3 Aug 2020 22:40:55 +0700 Subject: [PATCH 332/427] configure: check for pytest with python -m pytest MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit On different distro, pytest is suffixed with different patterns. On the other hand, `python3-pytest' could be invoked correctly, via `python3 -m pytest', the latter is used by our tests, now. Switch to `$python -m pytest` to fix address all incompatible naming. Signed-off-by: Đoàn Trần Công Danh --- configure | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure b/configure index 98f5f1ea..0921c0cc 100755 --- a/configure +++ b/configure @@ -812,7 +812,7 @@ if [ $have_python3 -eq 1 ]; then printf "Checking for python3 pytest (>= 3.0)... " conf=$(mktemp) printf "[pytest]\nminversion=3.0\n" > $conf - if pytest-3 -c $conf --version >/dev/null 2>&1; then + if "$python" -m pytest -c $conf --version >/dev/null 2>&1; then printf "Yes.\n" have_python3_pytest=1 else From 9ca14bcbab0cd6c2b9a7717927390245a853ab05 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C4=90o=C3=A0n=20Tr=E1=BA=A7n=20C=C3=B4ng=20Danh?= Date: Mon, 3 Aug 2020 22:40:56 +0700 Subject: [PATCH 333/427] configure: drop check for default xapian backend MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Starting from xapian 1.3.5, xapian switched default backend to glass. From 00cdfe10 (build: drop support for xapian versions less than 1.4, 2020-04-22), we only support xapian 1.4.0+. Effectively, we don't need to check for default xapian backend anymore. Further more, from 99a7aac8 (test: drop use of db_ending, 2020-07-29), our test framework has become independence from default xapian. Let's drop it. Signed-off-by: Đoàn Trần Công Danh --- configure | 23 ----------------------- 1 file changed, 23 deletions(-) diff --git a/configure b/configure index 0921c0cc..a357eb91 100755 --- a/configure +++ b/configure @@ -455,26 +455,6 @@ if [ ${have_xapian} = "0" ]; then errors=$((errors + 1)) fi -if [ ${have_xapian} = "1" ]; then - default_xapian_backend="" - printf "Testing default Xapian backend... " - cat >_default_backend.cc < -int main(int argc, char** argv) { - Xapian::WritableDatabase db("test.db",Xapian::DB_CREATE_OR_OPEN); -} -EOF - ${CXX} ${CXXFLAGS_for_sh} ${xapian_cxxflags} _default_backend.cc -o _default_backend ${xapian_ldflags} - ./_default_backend - if [ -f test.db/iamglass ]; then - default_xapian_backend=glass - else - default_xapian_backend=chert - fi - printf "%s\n" "${default_xapian_backend}"; - rm -rf test.db _default_backend _default_backend.cc -fi - GMIME_MINVER=3.0.3 printf "Checking for GMime development files (>= $GMIME_MINVER)... " @@ -1508,9 +1488,6 @@ NOTMUCH_SRCDIR='${NOTMUCH_SRCDIR}' # Whether to have Xapian retry lock NOTMUCH_HAVE_XAPIAN_DB_RETRY_LOCK=${WITH_RETRY_LOCK} -# Which backend will Xapian use by default? -NOTMUCH_DEFAULT_XAPIAN_BACKEND=${default_xapian_backend} - # Whether GMime can verify X.509 certificate validity NOTMUCH_GMIME_X509_CERT_VALIDITY=${gmime_x509_cert_validity} From f5ae8040b3ebba8ffa1be485c3a74397df98138e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C4=90o=C3=A0n=20Tr=E1=BA=A7n=20C=C3=B4ng=20Danh?= Date: Mon, 3 Aug 2020 22:40:57 +0700 Subject: [PATCH 334/427] T355: specify hash algorithm explicitly MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit On some systems (notably, the one shipped with LibreSSL), default fingerprint digest algorithm is SHA256. On other systems, users can change default digest algorithm by changing default_md in /etc/ssl/default_md. Let's ask openssl to provide us specific algorithm to make the test more deterministic. Signed-off-by: Đoàn Trần Công Danh --- test/T355-smime.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/T355-smime.sh b/test/T355-smime.sh index f8cec62c..8b2b52be 100755 --- a/test/T355-smime.sh +++ b/test/T355-smime.sh @@ -6,7 +6,7 @@ test_description='S/MIME signature verification and decryption' test_require_external_prereq openssl test_require_external_prereq gpgsm -FINGERPRINT=$(openssl x509 -fingerprint -in "$NOTMUCH_SRCDIR/test/smime/key+cert.pem" -noout | sed -e 's/^.*=//' -e s/://g) +FINGERPRINT=$(openssl x509 -sha1 -fingerprint -in "$NOTMUCH_SRCDIR/test/smime/key+cert.pem" -noout | sed -e 's/^.*=//' -e s/://g) add_gpgsm_home From e3fd546ad75411ed310202e52e1c88bbd9616651 Mon Sep 17 00:00:00 2001 From: Teemu Likonen Date: Fri, 7 Aug 2020 07:46:41 +0300 Subject: [PATCH 335/427] Emacs: Ensure left-to-right display for message headers In notmuch-show buffer insert invisible U+200E LEFT-TO-RIGHT MARK character at the beginning of message header paragraph if the From header contains a right-to-left character. This ensures that the header paragraph is always rendered in left-to-right mode. See Emacs Lisp reference manual section "(elisp) Bidirectional Display" for more info. --- emacs/notmuch-show.el | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/emacs/notmuch-show.el b/emacs/notmuch-show.el index c9170466..0eb27e33 100644 --- a/emacs/notmuch-show.el +++ b/emacs/notmuch-show.el @@ -466,10 +466,16 @@ unchanged ADDRESS if parsing fails." (defun notmuch-show-insert-headerline (headers date tags depth) "Insert a notmuch style headerline based on HEADERS for a message at DEPTH in the current thread." - (let ((start (point))) + (let ((start (point)) + (from (notmuch-sanitize + (notmuch-show-clean-address (plist-get headers :From))))) + (when (string-match "\\cR" from) + ;; If the From header has a right-to-left character add + ;; invisible U+200E LEFT-TO-RIGHT MARK character which forces + ;; the header paragraph as left-to-right text. + (insert (propertize (string ?\x200e) 'invisible t))) (insert (notmuch-show-spaces-n (* notmuch-show-indent-messages-width depth)) - (notmuch-sanitize - (notmuch-show-clean-address (plist-get headers :From))) + from " (" date ") (" From a4617f29ce81e7ae3e0cb747fdb9070f88407a28 Mon Sep 17 00:00:00 2001 From: Jonas Bernoulli Date: Sat, 8 Aug 2020 13:49:36 +0200 Subject: [PATCH 336/427] emacs: Shorten long lines --- emacs/coolj.el | 2 +- emacs/notmuch-address.el | 38 +++++++------ emacs/notmuch-company.el | 17 ++++-- emacs/notmuch-crypto.el | 16 +++--- emacs/notmuch-draft.el | 12 ++-- emacs/notmuch-hello.el | 43 ++++++++++----- emacs/notmuch-jump.el | 7 ++- emacs/notmuch-lib.el | 62 +++++++++++++-------- emacs/notmuch-maildir-fcc.el | 14 ++--- emacs/notmuch-message.el | 3 +- emacs/notmuch-mua.el | 78 +++++++++++++++----------- emacs/notmuch-show.el | 103 +++++++++++++++++++++++------------ emacs/notmuch-tag.el | 14 +++-- emacs/notmuch-tree.el | 55 +++++++++++++------ emacs/notmuch-wash.el | 19 +++++-- emacs/notmuch.el | 27 ++++++--- emacs/rstdoc.el | 3 +- 17 files changed, 326 insertions(+), 187 deletions(-) diff --git a/emacs/coolj.el b/emacs/coolj.el index 350d537f..5d311170 100644 --- a/emacs/coolj.el +++ b/emacs/coolj.el @@ -1,6 +1,6 @@ ;;; coolj.el --- automatically wrap long lines -*- coding:utf-8 -*- -;; Copyright (C) 2000, 2001, 2004, 2005, 2006, 2007, 2008, 2009 Free Software Foundation, Inc. +;; Copyright (C) 2000, 2001, 2004-2009 Free Software Foundation, Inc. ;; Authors: Kai Grossjohann ;; Alex Schroeder diff --git a/emacs/notmuch-address.el b/emacs/notmuch-address.el index 0d56fba7..2a9c411a 100644 --- a/emacs/notmuch-address.el +++ b/emacs/notmuch-address.el @@ -195,10 +195,11 @@ external commands." ((eq notmuch-address-command 'internal) (unless (notmuch-address--harvest-ready) ;; First, run quick synchronous harvest based on what the user - ;; entered so far + ;; entered so far. (notmuch-address-harvest original t)) (prog1 (notmuch-address-matching original) - ;; Then start the (potentially long-running) full asynchronous harvest if necessary + ;; Then start the (potentially long-running) full asynchronous + ;; harvest if necessary. (notmuch-address-harvest-trigger))) (t (process-lines notmuch-address-command original)))) @@ -241,7 +242,8 @@ external commands." (push chosen notmuch-address-history) (delete-region beg end) (insert chosen) - (run-hook-with-args 'notmuch-address-post-completion-functions chosen)) + (run-hook-with-args 'notmuch-address-post-completion-functions + chosen)) (message "No matches.") (ding)))) (t nil))) @@ -393,10 +395,11 @@ to be a saved address hash." ;; The file exists, check it is a file we saved (notmuch-address--get-address-hash)) (with-temp-file notmuch-address-save-filename - (let ((save-plist (list :version notmuch-address--save-hash-version - :completion-settings notmuch-address-internal-completion - :last-harvest notmuch-address-last-harvest - :completions notmuch-address-completions))) + (let ((save-plist + (list :version notmuch-address--save-hash-version + :completion-settings notmuch-address-internal-completion + :last-harvest notmuch-address-last-harvest + :completions notmuch-address-completions))) (print "notmuch-address-hash" (current-buffer)) (print save-plist (current-buffer)))) (message "\ @@ -408,16 +411,17 @@ appear to be an address savefile. Not overwriting." (let ((now (float-time))) (when (> (- now notmuch-address-last-harvest) 86400) (setq notmuch-address-last-harvest now) - (notmuch-address-harvest nil nil - (lambda (proc event) - ;; If harvest fails, we want to try - ;; again when the trigger is next - ;; called - (if (string= event "finished\n") - (progn - (notmuch-address--save-address-hash) - (setq notmuch-address-full-harvest-finished t)) - (setq notmuch-address-last-harvest 0))))))) + (notmuch-address-harvest + nil nil + (lambda (proc event) + ;; If harvest fails, we want to try + ;; again when the trigger is next + ;; called + (if (string= event "finished\n") + (progn + (notmuch-address--save-address-hash) + (setq notmuch-address-full-harvest-finished t)) + (setq notmuch-address-last-harvest 0))))))) ;; diff --git a/emacs/notmuch-company.el b/emacs/notmuch-company.el index ac998f9b..c1f3594e 100644 --- a/emacs/notmuch-company.el +++ b/emacs/notmuch-company.el @@ -69,9 +69,11 @@ (cl-case command (interactive (company-begin-backend 'notmuch-company)) (prefix (and (derived-mode-p 'message-mode) - (looking-back (concat notmuch-address-completion-headers-regexp ".*") - (line-beginning-position)) - (setq notmuch-company-last-prefix (company-grab "[:,][ \t]*\\(.*\\)" 1 (point-at-bol))))) + (looking-back + (concat notmuch-address-completion-headers-regexp ".*") + (line-beginning-position)) + (setq notmuch-company-last-prefix + (company-grab "[:,][ \t]*\\(.*\\)" 1 (point-at-bol))))) (candidates (cond ((notmuch-address--harvest-ready) ;; Update harvested addressed from time to time @@ -80,17 +82,20 @@ (t (cons :async (lambda (callback) - ;; First run quick asynchronous harvest based on what the user entered so far + ;; First run quick asynchronous harvest + ;; based on what the user entered so far (notmuch-address-harvest arg nil (lambda (_proc _event) (funcall callback (notmuch-address-matching arg)) - ;; Then start the (potentially long-running) full asynchronous harvest if necessary + ;; Then start the (potentially long-running) + ;; full asynchronous harvest if necessary (notmuch-address-harvest-trigger)))))))) (match (if (string-match notmuch-company-last-prefix arg) (match-end 0) 0)) - (post-completion (run-hook-with-args 'notmuch-address-post-completion-functions arg)) + (post-completion + (run-hook-with-args 'notmuch-address-post-completion-functions arg)) (no-cache t)))) diff --git a/emacs/notmuch-crypto.el b/emacs/notmuch-crypto.el index c201d0d7..2327ff1f 100644 --- a/emacs/notmuch-crypto.el +++ b/emacs/notmuch-crypto.el @@ -161,7 +161,8 @@ by user FROM." (goto-char (point-max)) (insert (format "-- Key %s in message %s:\n" fingerprint id)) - (call-process notmuch-crypto-gpg-program nil t t "--batch" "--no-tty" "--list-keys" fingerprint)) + (call-process notmuch-crypto-gpg-program nil t t + "--batch" "--no-tty" "--list-keys" fingerprint)) (recenter -1)))) (declare-function notmuch-show-refresh-view "notmuch-show" (&optional reset-state)) @@ -220,12 +221,13 @@ corresponding key when the status button is pressed." (with-current-buffer buffer (goto-char (point-max)) (insert (format "--- Retrieving key %s:\n" keyid))) - (let ((p (make-process :name "notmuch GPG key retrieval" - :connection-type 'pipe - :buffer buffer - :stderr buffer - :command (list notmuch-crypto-gpg-program "--recv-keys" keyid) - :sentinel #'notmuch-crypto--async-key-sentinel))) + (let ((p (make-process + :name "notmuch GPG key retrieval" + :connection-type 'pipe + :buffer buffer + :stderr buffer + :command (list notmuch-crypto-gpg-program "--recv-keys" keyid) + :sentinel #'notmuch-crypto--async-key-sentinel))) (process-put p :gpg-key-id keyid) (process-put p :notmuch-show-buffer (current-buffer)) (process-put p :notmuch-show-point (point)) diff --git a/emacs/notmuch-draft.el b/emacs/notmuch-draft.el index 27abc7d9..c3cb619f 100644 --- a/emacs/notmuch-draft.el +++ b/emacs/notmuch-draft.el @@ -154,12 +154,14 @@ Used when a new version is saved, or the message is sent." `notmuch-draft-save-plaintext' controls the behaviour." (cl-case notmuch-draft-save-plaintext ((ask) - (unless (yes-or-no-p "(Customize `notmuch-draft-save-plaintext' to avoid this warning) + (unless (yes-or-no-p + "(Customize `notmuch-draft-save-plaintext' to avoid this warning) This message contains mml tags that suggest it is intended to be encrypted. Really save and index an unencrypted copy? ") (error "Save aborted"))) ((nil) - (error "Refusing to save draft with encryption tags (see `notmuch-draft-save-plaintext')")) + (error "Refusing to save draft with encryption tags (see `%s')" + 'notmuch-draft-save-plaintext)) ((t) (ignore)))) @@ -192,14 +194,16 @@ applied to newly inserted messages)." (message-remove-header "Message-ID") (message-add-header (concat "Message-ID: <" id ">"))) (t - (message "You have customized emacs so Message-ID is not a deletable header, so not changing it") + (message "You have customized emacs so Message-ID is not a %s" + "deletable header, so not changing it") (setq id nil))) (cond ((member 'Date message-deletable-headers) (message-remove-header "Date") (message-add-header (concat "Date: " (message-make-date)))) (t - (message "You have customized emacs so Date is not a deletable header, so not changing it"))) + (message "You have customized emacs so Date is not a deletable %s" + "header, so not changing it"))) (message-add-header "X-Notmuch-Emacs-Draft: True") (notmuch-draft-quote-some-mml) (notmuch-maildir-setup-message-for-saving) diff --git a/emacs/notmuch-hello.el b/emacs/notmuch-hello.el index 0ff5aaff..11c625ea 100644 --- a/emacs/notmuch-hello.el +++ b/emacs/notmuch-hello.el @@ -29,7 +29,8 @@ (require 'notmuch-lib) (require 'notmuch-mua) -(declare-function notmuch-search "notmuch" (&optional query oldest-first target-thread target-line continuation)) +(declare-function notmuch-search "notmuch" + (&optional query oldest-first target-thread target-line continuation)) (declare-function notmuch-poll "notmuch" ()) (declare-function notmuch-tree "notmuch-tree" (&optional query query-context target buffer-name open-target unthreaded)) @@ -91,18 +92,28 @@ searches so they still work in customize." :tag "Saved Search" :args '((list :inline t :format "%v" - (group :format "%v" :inline t (const :format " Name: " :name) (string :format "%v")) - (group :format "%v" :inline t (const :format " Query: " :query) (string :format "%v"))) + (group :format "%v" :inline t + (const :format " Name: " :name) + (string :format "%v")) + (group :format "%v" :inline t + (const :format " Query: " :query) + (string :format "%v"))) (checklist :inline t :format "%v" - (group :format "%v" :inline t (const :format "Shortcut key: " :key) (key-sequence :format "%v")) - (group :format "%v" :inline t (const :format "Count-Query: " :count-query) (string :format "%v")) - (group :format "%v" :inline t (const :format "" :sort-order) + (group :format "%v" :inline t + (const :format "Shortcut key: " :key) + (key-sequence :format "%v")) + (group :format "%v" :inline t + (const :format "Count-Query: " :count-query) + (string :format "%v")) + (group :format "%v" :inline t + (const :format "" :sort-order) (choice :tag " Sort Order" (const :tag "Default" nil) (const :tag "Oldest-first" oldest-first) (const :tag "Newest-first" newest-first))) - (group :format "%v" :inline t (const :format "" :search-type) + (group :format "%v" :inline t + (const :format "" :search-type) (choice :tag " Search Type" (const :tag "Search mode" nil) (const :tag "Tree mode" tree) @@ -474,7 +485,8 @@ should be. Returns a cons cell `(tags-per-line width)'." ((floatp notmuch-column-control) (let* ((available-width (- (window-width) notmuch-hello-indent)) - (proposed-width (max (* available-width notmuch-column-control) widest))) + (proposed-width (max (* available-width notmuch-column-control) + widest))) (floor available-width proposed-width))) (t @@ -536,8 +548,9 @@ options will be handled as specified for (unless (= (call-process-region (point-min) (point-max) notmuch-command t t nil "count" "--batch") 0) - (notmuch-logged-error "notmuch count --batch failed" - "Please check that the notmuch CLI is new enough to support `count + (notmuch-logged-error + "notmuch count --batch failed" + "Please check that the notmuch CLI is new enough to support `count --batch'. In general we recommend running matching versions of the CLI and emacs interface.")) @@ -553,7 +566,8 @@ the CLI and emacs interface.")) search-query (plist-get options :filter))) (message-count (prog1 (read (current-buffer)) (forward-line 1)))) - (when (and filtered-query (or (plist-get options :show-empty-searches) (> message-count 0))) + (when (and filtered-query (or (plist-get options :show-empty-searches) + (> message-count 0))) (setq elem-plist (plist-put elem-plist :query filtered-query)) (plist-put elem-plist :count message-count)))) query-list)))) @@ -740,7 +754,9 @@ Complete list of currently available key bindings: ;; dark background. (setq image (cons 'image (append (cdr image) - (list :background (face-background 'notmuch-hello-logo-background))))) + (list :background + (face-background + 'notmuch-hello-logo-background))))) (insert-image image)) (widget-insert " ")) @@ -760,7 +776,8 @@ Complete list of currently available key bindings: (notmuch-hello-update)) :help-echo "Refresh" (notmuch-hello-nice-number - (string-to-number (car (process-lines notmuch-command "count"))))) + (string-to-number + (car (process-lines notmuch-command "count"))))) (widget-insert " messages.\n"))) diff --git a/emacs/notmuch-jump.el b/emacs/notmuch-jump.el index 84fe2b9c..6d8cd97f 100644 --- a/emacs/notmuch-jump.el +++ b/emacs/notmuch-jump.el @@ -70,7 +70,8 @@ fast way to jump to a saved search from anywhere in Notmuch." (if action-map (notmuch-jump action-map "Search: ") - (error "To use notmuch-jump, please customize shortcut keys in notmuch-saved-searches.")))) + (error "To use notmuch-jump, \ +please customize shortcut keys in notmuch-saved-searches.")))) (defvar notmuch-jump--action nil) @@ -200,7 +201,9 @@ buffer." (define-key map keystr `(lambda () (interactive) (setq notmuch-jump--action - ',(apply-partially #'notmuch-jump action-submap new-prompt)) + ',(apply-partially #'notmuch-jump + action-submap + new-prompt)) (exit-minibuffer))))))) map)) diff --git a/emacs/notmuch-lib.el b/emacs/notmuch-lib.el index 0bb08eb2..9339f9d0 100644 --- a/emacs/notmuch-lib.el +++ b/emacs/notmuch-lib.el @@ -316,8 +316,10 @@ It does not prepend if ACTUAL-KEY is already listed in TAIL." tail))) ;; Documentation for command (push (cons key-string - (or (and (symbolp binding) (get binding 'notmuch-doc)) - (and (functionp binding) (notmuch-documentation-first-line binding)))) + (or (and (symbolp binding) + (get binding 'notmuch-doc)) + (and (functionp binding) + (notmuch-documentation-first-line binding)))) tail))) tail) @@ -327,13 +329,13 @@ It does not prepend if ACTUAL-KEY is already listed in TAIL." ;; binding whose "key" is 'remap, and whose "binding" is itself a ;; keymap that maps not from keys to commands, but from old (remapped) ;; functions to the commands to use in their stead. - (map-keymap - (lambda (command binding) - (mapc - (lambda (actual-key) - (setq tail (notmuch-describe-key actual-key binding prefix ua-keys tail))) - (where-is-internal command base-keymap))) - remap-keymap) + (map-keymap (lambda (command binding) + (mapc (lambda (actual-key) + (setq tail + (notmuch-describe-key actual-key binding + prefix ua-keys tail))) + (where-is-internal command base-keymap))) + remap-keymap) tail) (defun notmuch-describe-keymap (keymap ua-keys base-keymap &optional prefix tail) @@ -356,9 +358,13 @@ prefix argument. PREFIX and TAIL are used internally." (notmuch-describe-remaps binding ua-keys base-keymap prefix tail) (notmuch-describe-keymap - binding ua-keys base-keymap (notmuch-prefix-key-description key) tail)))) + binding ua-keys base-keymap + (notmuch-prefix-key-description key) + tail)))) (binding - (setq tail (notmuch-describe-key (vector key) binding prefix ua-keys tail))))) + (setq tail + (notmuch-describe-key (vector key) + binding prefix ua-keys tail))))) keymap) tail) @@ -368,11 +374,15 @@ prefix argument. PREFIX and TAIL are used internally." (while (string-match "\\\\{\\([^}[:space:]]*\\)}" doc beg) (let ((desc (save-match-data - (let* ((keymap-name (substring doc (match-beginning 1) (match-end 1))) + (let* ((keymap-name (substring doc + (match-beginning 1) + (match-end 1))) (keymap (symbol-value (intern keymap-name))) (ua-keys (where-is-internal 'universal-argument keymap t)) (desc-alist (notmuch-describe-keymap keymap ua-keys keymap)) - (desc-list (mapcar (lambda (arg) (concat (car arg) "\t" (cdr arg))) desc-alist))) + (desc-list (mapcar (lambda (arg) + (concat (car arg) "\t" (cdr arg))) + desc-alist))) (mapconcat #'identity desc-list "\n"))))) (setq doc (replace-match desc 1 1 doc))) (setq beg (match-end 0))) @@ -391,7 +401,8 @@ its prefixed behavior by setting the 'notmuch-prefix-doc property of its command symbol." (interactive) (let* ((mode major-mode) - (doc (substitute-command-keys (notmuch-substitute-command-keys (documentation mode t))))) + (doc (substitute-command-keys + (notmuch-substitute-command-keys (documentation mode t))))) (with-current-buffer (generate-new-buffer "*notmuch-help*") (insert doc) (goto-char (point-min)) @@ -411,8 +422,10 @@ of its command symbol." (let* ((subkeymap (key-binding prefix)) (ua-keys (where-is-internal 'universal-argument nil t)) (prefix-string (notmuch-prefix-key-description prefix)) - (desc-alist (notmuch-describe-keymap subkeymap ua-keys subkeymap prefix-string)) - (desc-list (mapcar (lambda (arg) (concat (car arg) "\t" (cdr arg))) desc-alist)) + (desc-alist (notmuch-describe-keymap + subkeymap ua-keys subkeymap prefix-string)) + (desc-list (mapcar (lambda (arg) (concat (car arg) "\t" (cdr arg))) + desc-alist)) (desc (mapconcat #'identity desc-list "\n"))) (with-help-window (help-buffer) (with-current-buffer standard-output @@ -547,7 +560,8 @@ This replaces spaces, percents, and double quotes in STR with '( ;; Avoid HTML parts. "text/html" - ;; multipart/related usually contain a text/html part and some associated graphics. + ;; multipart/related usually contain a text/html part and some + ;; associated graphics. "multipart/related" )) @@ -602,8 +616,9 @@ the given type." ,(notmuch-id-to-query (plist-get msg :id)))) (coding-system-for-read (if binaryp 'no-conversion - (let ((coding-system (mm-charset-to-coding-system - (plist-get part :content-charset)))) + (let ((coding-system + (mm-charset-to-coding-system + (plist-get part :content-charset)))) ;; Sadly, ;; `mm-charset-to-coding-system' seems ;; to return things that are not @@ -615,7 +630,8 @@ the given type." ;; charset is US-ASCII. RFC6657 ;; complicates this somewhat. 'us-ascii))))) - (apply #'call-process notmuch-command nil '(t nil) nil args) + (apply #'call-process + notmuch-command nil '(t nil) nil args) (buffer-string)))))) (when (and cache data) (plist-put part plist-elem data)) @@ -670,7 +686,8 @@ current buffer, if possible." (let* ((have-content (plist-member part :content)) (charset (if have-content 'gnus-decoded (plist-get part :content-charset))) - (handle (mm-make-handle (current-buffer) `(,content-type (charset . ,charset))))) + (handle (mm-make-handle (current-buffer) + `(,content-type (charset . ,charset))))) ;; If the user wants the part inlined, insert the content and ;; test whether we are able to inline it (which includes both ;; capability and suitability tests). @@ -786,7 +803,8 @@ provided, it is taken from `process-command'." ((exit) (process-exit-status proc)) ((signal) msg)))) (when exit-status - (notmuch-check-exit-status exit-status (or command (process-command proc)) + (notmuch-check-exit-status exit-status + (or command (process-command proc)) nil err)))) (defun notmuch-check-exit-status (exit-status command &optional output err) diff --git a/emacs/notmuch-maildir-fcc.el b/emacs/notmuch-maildir-fcc.el index e95de63b..4b10a731 100644 --- a/emacs/notmuch-maildir-fcc.el +++ b/emacs/notmuch-maildir-fcc.el @@ -135,8 +135,9 @@ by notmuch-mua-mail." ;; really want this header inserted. (when (or (not (= (elt subdir 0) ?/)) - (y-or-n-p (format "Fcc header %s is an absolute path and notmuch insert is requested.\nInsert header anyway? " - subdir))) + (y-or-n-p + (format "Fcc header %s is an absolute path and notmuch insert is requested. +Insert header anyway? " subdir))) (message-add-header (concat "Fcc: " subdir)))) (defun notmuch-maildir-add-file-style-fcc-header (subdir) @@ -249,9 +250,8 @@ If CREATE is non-nil then create the folder if necessary." ;; typo, or just the user want a new folder, let the user decide ;; how to deal with it. (error - (let ((response (notmuch-read-char-choice - "Insert failed: (r)etry, (c)reate folder, (i)gnore, or (e)dit the header? " - '(?r ?c ?i ?e)))) + (let ((response (notmuch-read-char-choice "Insert failed: \ +\(r)etry, (c)reate folder, (i)gnore, or (e)dit the header? " '(?r ?c ?i ?e)))) (cl-case response (?r (notmuch-maildir-fcc-with-notmuch-insert fcc-header)) (?c (notmuch-maildir-fcc-with-notmuch-insert fcc-header 't)) @@ -333,8 +333,8 @@ if needed." (notmuch-maildir-fcc-write-buffer-to-maildir fcc-header 't) ;; The fcc-header is not a valid maildir see if the user wants to ;; fix it in some way. - (let* ((prompt (format "Fcc %s is not a maildir: (r)etry, (c)reate folder, (i)gnore, or (e)dit the header? " - fcc-header)) + (let* ((prompt (format "Fcc %s is not a maildir: \ +\(r)etry, (c)reate folder, (i)gnore, or (e)dit the header? " fcc-header)) (response (notmuch-read-char-choice prompt '(?r ?c ?i ?e)))) (cl-case response (?r (notmuch-maildir-fcc-file-fcc fcc-header)) diff --git a/emacs/notmuch-message.el b/emacs/notmuch-message.el index 0164472f..c2242070 100644 --- a/emacs/notmuch-message.el +++ b/emacs/notmuch-message.el @@ -60,7 +60,8 @@ the first is a notmuch query and the rest are the tag changes to be applied to the matching messages.") (defun notmuch-message-apply-queued-tag-changes () - ;; Apply the tag changes queued in the buffer-local variable notmuch-message-queued-tag-changes. + ;; Apply the tag changes queued in the buffer-local variable + ;; notmuch-message-queued-tag-changes. (dolist (query-and-tags notmuch-message-queued-tag-changes) (notmuch-tag (car query-and-tags) (cdr query-and-tags)))) diff --git a/emacs/notmuch-mua.el b/emacs/notmuch-mua.el index 1efc88ea..74dfb384 100644 --- a/emacs/notmuch-mua.el +++ b/emacs/notmuch-mua.el @@ -205,10 +205,12 @@ Typically this is added to `notmuch-mua-send-hook'." (defun notmuch-mua-reply-crypto (parts) "Add mml sign-encrypt flag if any part of original message is encrypted." (cl-loop for part in parts - if (notmuch-match-content-type (plist-get part :content-type) "multipart/encrypted") - do (mml-secure-message-sign-encrypt) - else if (notmuch-match-content-type (plist-get part :content-type) "multipart/*") - do (notmuch-mua-reply-crypto (plist-get part :content)))) + if (notmuch-match-content-type (plist-get part :content-type) + "multipart/encrypted") + do (mml-secure-message-sign-encrypt) + else if (notmuch-match-content-type (plist-get part :content-type) + "multipart/*") + do (notmuch-mua-reply-crypto (plist-get part :content)))) ;; There is a bug in emacs 23's message.el that results in a newline ;; not being inserted after the References header, so the next header @@ -250,8 +252,9 @@ Typically this is added to `notmuch-mua-send-hook'." ;; the original message. ((same-window-regexps '("\\*mail .*"))) - ;; We modify message-header-format-alist to get around a bug in message.el. - ;; See the comment above on notmuch-mua-insert-references. + ;; We modify message-header-format-alist to get around + ;; a bug in message.el. See the comment above on + ;; notmuch-mua-insert-references. (let ((message-header-format-alist (cl-loop for pair in message-header-format-alist if (eq (car pair) 'References) @@ -266,7 +269,8 @@ Typically this is added to `notmuch-mua-send-hook'." (notmuch-headers-plist-to-alist reply-headers) nil (notmuch-mua-get-switch-function)))) - ;; Create a buffer-local queue for tag changes triggered when sending the reply + ;; Create a buffer-local queue for tag changes triggered when + ;; sending the reply. (when notmuch-message-replied-tags (setq-local notmuch-message-queued-tag-changes (list (cons query-string notmuch-message-replied-tags)))) @@ -293,27 +297,29 @@ Typically this is added to `notmuch-mua-send-hook'." (insert "From: " from "\n") (insert "Date: " date "\n\n") - (insert (with-temp-buffer - (let - ;; Don't attempt to clean up messages, excerpt - ;; citations, etc. in the original message before - ;; quoting. - ((notmuch-show-insert-text/plain-hook nil) - ;; Don't omit long parts. - (notmuch-show-max-text-part-size 0) - ;; Insert headers for parts as appropriate for replying. - (notmuch-show-insert-header-p-function notmuch-mua-reply-insert-header-p-function) - ;; Ensure that any encrypted parts are - ;; decrypted during the generation of the reply - ;; text. - (notmuch-show-process-crypto process-crypto) - ;; Don't indent multipart sub-parts. - (notmuch-show-indent-multipart nil)) - ;; We don't want sigstatus buttons (an information leak and usually wrong anyway). - (cl-letf (((symbol-function 'notmuch-crypto-insert-sigstatus-button) #'ignore) - ((symbol-function 'notmuch-crypto-insert-encstatus-button) #'ignore)) - (notmuch-show-insert-body original (plist-get original :body) 0) - (buffer-substring-no-properties (point-min) (point-max)))))) + (insert + (with-temp-buffer + (let + ;; Don't attempt to clean up messages, excerpt + ;; citations, etc. in the original message before + ;; quoting. + ((notmuch-show-insert-text/plain-hook nil) + ;; Don't omit long parts. + (notmuch-show-max-text-part-size 0) + ;; Insert headers for parts as appropriate for replying. + (notmuch-show-insert-header-p-function + notmuch-mua-reply-insert-header-p-function) + ;; Ensure that any encrypted parts are + ;; decrypted during the generation of the reply + ;; text. + (notmuch-show-process-crypto process-crypto) + ;; Don't indent multipart sub-parts. + (notmuch-show-indent-multipart nil)) + ;; We don't want sigstatus buttons (an information leak and usually wrong anyway). + (cl-letf (((symbol-function 'notmuch-crypto-insert-sigstatus-button) #'ignore) + ((symbol-function 'notmuch-crypto-insert-encstatus-button) #'ignore)) + (notmuch-show-insert-body original (plist-get original :body) 0) + (buffer-substring-no-properties (point-min) (point-max)))))) (set-mark (point)) (goto-char start) @@ -383,10 +389,13 @@ modified. This function is notmuch addaptation of (unless (assq 'From other-headers) (push (cons 'From (message-make-from - (notmuch-user-name) (notmuch-user-primary-email))) other-headers)) + (notmuch-user-name) + (notmuch-user-primary-email))) + other-headers)) (notmuch-mua-pop-to-buffer (message-buffer-name "mail" to) - (or switch-function (notmuch-mua-get-switch-function))) + (or switch-function + (notmuch-mua-get-switch-function))) (let ((headers (append ;; The following is copied from `message-mail' @@ -499,7 +508,8 @@ the From: address." (with-current-buffer temp-buffer (erase-buffer) (let ((coding-system-for-read 'no-conversion)) - (call-process notmuch-command nil t nil "show" "--format=raw" id)) + (call-process notmuch-command nil t nil + "show" "--format=raw" id)) ;; Because we process the messages in reverse order, ;; always generate a forwarded subject, then use the ;; last (i.e. first) one. @@ -524,7 +534,8 @@ the From: address." (message-add-header (concat "References: " (mapconcat 'identity forward-references " ")))) - ;; Create a buffer-local queue for tag changes triggered when sending the message + ;; Create a buffer-local queue for tag changes triggered when + ;; sending the message. (when notmuch-message-forwarded-tags (setq-local notmuch-message-queued-tag-changes (cl-loop for id in forward-queries @@ -609,7 +620,8 @@ unencrypted. Really send? ")))) (run-hooks 'notmuch-mua-send-hook) (when (and (notmuch-mua-check-no-misplaced-secure-tag) (notmuch-mua-check-secure-tag-has-newline)) - (cl-letf (((symbol-function 'message-do-fcc) #'notmuch-maildir-message-do-fcc)) + (cl-letf (((symbol-function 'message-do-fcc) + #'notmuch-maildir-message-do-fcc)) (if exit (message-send-and-exit arg) (message-send arg))))) diff --git a/emacs/notmuch-show.el b/emacs/notmuch-show.el index 0eb27e33..41f31c46 100644 --- a/emacs/notmuch-show.el +++ b/emacs/notmuch-show.el @@ -51,7 +51,8 @@ (declare-function notmuch-count-attachments "notmuch" (mm-handle)) (declare-function notmuch-save-attachments "notmuch" (mm-handle &optional queryp)) (declare-function notmuch-tree "notmuch-tree" - (&optional query query-context target buffer-name open-target unthreaded)) + (&optional query query-context target buffer-name + open-target unthreaded)) (declare-function notmuch-tree-get-message-properties "notmuch-tree" nil) (declare-function notmuch-unthreaded (&optional query query-context target buffer-name open-target)) @@ -95,10 +96,11 @@ visible for any given message." :group 'notmuch-show :group 'notmuch-hooks) -(defcustom notmuch-show-insert-text/plain-hook '(notmuch-wash-wrap-long-lines - notmuch-wash-tidy-citations - notmuch-wash-elide-blank-lines - notmuch-wash-excerpt-citations) +(defcustom notmuch-show-insert-text/plain-hook + '(notmuch-wash-wrap-long-lines + notmuch-wash-tidy-citations + notmuch-wash-elide-blank-lines + notmuch-wash-excerpt-citations) "Functions used to improve the display of text/plain parts." :type 'hook :options '(notmuch-wash-convert-inline-patch-to-part @@ -348,7 +350,9 @@ operation on the contents of the current buffer." (with-temp-buffer (insert all) (if indenting - (indent-rigidly (point-min) (point-max) (- (* notmuch-show-indent-messages-width depth)))) + (indent-rigidly (point-min) + (point-max) + (- (* notmuch-show-indent-messages-width depth)))) ;; Remove the original header. (goto-char (point-min)) (re-search-forward "^$" (point-max) nil) @@ -395,7 +399,9 @@ operation on the contents of the current buffer." (if (re-search-forward "(\\([^()]*\\))$" (line-end-position) t) (let ((inhibit-read-only t)) (replace-match (concat "(" - (notmuch-tag-format-tags tags (notmuch-show-get-prop :orig-tags)) + (notmuch-tag-format-tags + tags + (notmuch-show-get-prop :orig-tags)) ")")))))) (defun notmuch-clean-address (address) @@ -481,7 +487,8 @@ message at DEPTH in the current thread." ") (" (notmuch-tag-format-tags tags tags) ")\n") - (overlay-put (make-overlay start (point)) 'face 'notmuch-message-summary-face))) + (overlay-put (make-overlay start (point)) + 'face 'notmuch-message-summary-face))) (defun notmuch-show-insert-header (header header-value) "Insert a single header." @@ -508,7 +515,8 @@ message at DEPTH in the current thread." 'face 'message-mml :supertype 'notmuch-button-type) -(defun notmuch-show-insert-part-header (nth content-type declared-type &optional name comment) +(defun notmuch-show-insert-part-header (nth content-type declared-type + &optional name comment) (let ((button) (base-label (concat (when name (concat name ": ")) declared-type @@ -532,8 +540,9 @@ message at DEPTH in the current thread." (when button (let ((overlay (button-get button 'overlay)) (lazy-part (button-get button :notmuch-lazy-part))) - ;; We have a part to toggle if there is an overlay or if there is a lazy part. - ;; If neither is present we cannot toggle the part so we just return nil. + ;; We have a part to toggle if there is an overlay or if there + ;; is a lazy part. If neither is present we cannot toggle the + ;; part so we just return nil. (when (or overlay lazy-part) (let* ((show (button-get button :notmuch-part-hidden)) (new-start (button-start button)) @@ -634,7 +643,8 @@ will return nil if the CID is unknown or cannot be retrieved." (plist-get part :content))) (defun notmuch-show-insert-part-multipart/alternative (msg part content-type nth depth button) - (let ((chosen-type (car (notmuch-multipart/alternative-choose msg (notmuch-show-multipart/*-to-list part)))) + (let ((chosen-type (car (notmuch-multipart/alternative-choose + msg (notmuch-show-multipart/*-to-list part)))) (inner-parts (plist-get part :content)) (start (point))) ;; This inserts all parts of the chosen type rather than just one, @@ -770,7 +780,8 @@ will return nil if the CID is unknown or cannot be retrieved." (unwind-protect (progn (unless (icalendar-import-buffer file t) - (error "Icalendar import error. See *icalendar-errors* for more information")) + (error "Icalendar import error. %s" + "See *icalendar-errors* for more information")) (set-buffer (get-file-buffer file)) (setq result (buffer-substring (point-min) (point-max))) (set-buffer-modified-p nil) @@ -788,10 +799,13 @@ will return nil if the CID is unknown or cannot be retrieved." ;; ;; For newer emacs, we fall back to notmuch-show-insert-part-*/* ;; (see notmuch-show-handlers-for) - (defun notmuch-show-insert-part-text/enriched (msg part content-type nth depth button) - ;; By requiring enriched below, we ensure that the function enriched-decode-display-prop - ;; is defined before it will be shadowed by the letf below. Otherwise the version - ;; in enriched.el may be loaded a bit later and used instead (for the first time). + (defun notmuch-show-insert-part-text/enriched + (msg part content-type nth depth button) + ;; By requiring enriched below, we ensure that the function + ;; enriched-decode-display-prop is defined before it will be + ;; shadowed by the letf below. Otherwise the version in + ;; enriched.el may be loaded a bit later and used instead (for + ;; the first time). (require 'enriched) (cl-letf (((symbol-function 'enriched-decode-display-prop) (lambda (start end &optional param) (list start end)))) @@ -949,7 +963,9 @@ will return nil if the CID is unknown or cannot be retrieved." (narrow-to-region part-beg part-end) (delete-region part-beg part-end) (apply #'notmuch-show-insert-bodypart-internal part-args) - (indent-rigidly part-beg part-end (* notmuch-show-indent-messages-width depth))) + (indent-rigidly part-beg + part-end + (* notmuch-show-indent-messages-width depth))) (goto-char part-end) (delete-char 1) (notmuch-show-record-part-information (cadr part-args) @@ -1007,12 +1023,14 @@ is t, hide the part initially and show the button." (nth (plist-get part :id)) (long (and (notmuch-match-content-type mime-type "text/*") (> notmuch-show-max-text-part-size 0) - (> (length (plist-get part :content)) notmuch-show-max-text-part-size))) + (> (length (plist-get part :content)) + notmuch-show-max-text-part-size))) (beg (point)) ;; This default header-p function omits the part button for ;; the first (or only) part if this is text/plain. (button (when (funcall notmuch-show-insert-header-p-function part hide) - (notmuch-show-insert-part-header nth mime-type content-type (plist-get part :filename)))) + (notmuch-show-insert-part-header nth mime-type content-type + (plist-get part :filename)))) ;; Hide the part initially if HIDE is t, or if it is too long ;; and we have a button to allow toggling. (show-part (not (or (equal hide t) @@ -1110,13 +1128,17 @@ is t, hide the part initially and show the button." ;; Indent according to the depth in the thread. (if notmuch-show-indent-content - (indent-rigidly content-start content-end (* notmuch-show-indent-messages-width depth))) + (indent-rigidly content-start + content-end + (* notmuch-show-indent-messages-width depth))) (setq message-end (point-max-marker)) ;; Save the extents of this message over the whole text of the ;; message. - (put-text-property message-start message-end :notmuch-message-extent (cons message-start message-end)) + (put-text-property message-start message-end + :notmuch-message-extent + (cons message-start message-end)) ;; Create overlays used to control visibility (plist-put msg :headers-overlay (make-overlay headers-start headers-end)) @@ -1150,7 +1172,8 @@ is t, hide the part initially and show the button." (defun notmuch-show-toggle-elide-non-matching () "Toggle the display of non-matching messages." (interactive) - (setq notmuch-show-elide-non-matching-messages (not notmuch-show-elide-non-matching-messages)) + (setq notmuch-show-elide-non-matching-messages + (not notmuch-show-elide-non-matching-messages)) (message (if notmuch-show-elide-non-matching-messages "Showing matching messages only." "Showing all messages.")) @@ -1417,8 +1440,9 @@ This includes: ;; Open those that were open. (goto-char (point-min)) - (cl-loop do (notmuch-show-message-visible (notmuch-show-get-message-properties) - (member (notmuch-show-get-message-id) open)) + (cl-loop do (notmuch-show-message-visible + (notmuch-show-get-message-properties) + (member (notmuch-show-get-message-id) open)) until (not (notmuch-show-goto-message-next))) (dolist (win-msg-pair win-msg-alist) @@ -1651,7 +1675,8 @@ effects." (defun notmuch-show-set-message-properties (props) (save-excursion (notmuch-show-move-to-message-top) - (put-text-property (point) (+ (point) 1) :notmuch-message-properties props))) + (put-text-property (point) (+ (point) 1) + :notmuch-message-properties props))) (defun notmuch-show-get-message-properties () "Return the properties of the current message as a plist. @@ -1804,8 +1829,9 @@ user decision and we should not override it." (setq notmuch-show--seen-has-errored 't) (setq header-line-format (concat header-line-format - (propertize " [some mark read tag changes may have failed]" - 'face font-lock-warning-face))))))))) + (propertize + " [some mark read tag changes may have failed]" + 'face font-lock-warning-face))))))))) (defun notmuch-show-filter-thread (query) "Filter or LIMIT the current thread based on a new query string. @@ -1827,7 +1853,8 @@ Reshows the current thread with matches defined by the new query-string." (goto-char (point-min)) (while (not done) (if (notmuch-show-message-visible-p) - (setq message-ids (append message-ids (list (notmuch-show-get-message-id))))) + (setq message-ids + (append message-ids (list (notmuch-show-get-message-id))))) (setq done (not (notmuch-show-goto-message-next))) ) message-ids @@ -1891,7 +1918,8 @@ shown." (notmuch-show-archive-thread-then-next))) (defun notmuch-show-rewind () - "Backup through the thread (reverse scrolling compared to \\[notmuch-show-advance-and-archive]). + "Backup through the thread (reverse scrolling compared to \ +\\[notmuch-show-advance-and-archive]). Specifically, if the beginning of the previous email is fewer than `window-height' lines from the current point, move to it @@ -2083,11 +2111,14 @@ message." (setq shell-command (concat notmuch-command " show --format=mbox --exclude=false " (shell-quote-argument - (mapconcat 'identity (notmuch-show-get-message-ids-for-open-messages) " OR ")) + (mapconcat 'identity + (notmuch-show-get-message-ids-for-open-messages) + " OR ")) " | " command)) (setq shell-command (concat notmuch-command " show --format=raw " - (shell-quote-argument (notmuch-show-get-message-id)) " | " command))) + (shell-quote-argument (notmuch-show-get-message-id)) + " | " command))) (let ((cwd default-directory) (buf (get-buffer-create (concat "*notmuch-pipe*")))) (with-current-buffer buf @@ -2188,8 +2219,9 @@ argument, hide all of the messages." (interactive) (save-excursion (goto-char (point-min)) - (cl-loop do (notmuch-show-message-visible (notmuch-show-get-message-properties) - (not current-prefix-arg)) + (cl-loop do (notmuch-show-message-visible + (notmuch-show-get-message-properties) + (not current-prefix-arg)) until (not (notmuch-show-goto-message-next)))) (force-window-update)) @@ -2521,7 +2553,8 @@ the new buffer." (interactive (list (completing-read "Mime type to use (default text/plain): " (mailcap-mime-types) nil nil nil nil "text/plain"))) - (notmuch-show-apply-to-current-part-handle #'notmuch-show--mm-display-part mime-type)) + (notmuch-show-apply-to-current-part-handle #'notmuch-show--mm-display-part + mime-type)) (defun notmuch-show-imenu-prev-index-position-function () "Move point to previous message in notmuch-show buffer. diff --git a/emacs/notmuch-tag.el b/emacs/notmuch-tag.el index c54e6ad0..34cd493a 100644 --- a/emacs/notmuch-tag.el +++ b/emacs/notmuch-tag.el @@ -44,7 +44,9 @@ :args '((list :inline t :format "%v" (key-sequence :tag "Key") - (radio :tag "Tag operations" (repeat :tag "Tag list" (string :format "%v" :tag "change")) + (radio :tag "Tag operations" + (repeat :tag "Tag list" + (string :format "%v" :tag "change")) (variable :tag "Tag variable")) (string :tag "Name")))) @@ -316,7 +318,9 @@ changed (the normal case) are shown using formats from `notmuch-tag-formats'." (let* ((tag-state (cond ((not (member tag tags)) 'deleted) ((not (member tag orig-tags)) 'added))) - (formatted-tag (gethash (cons tag tag-state) notmuch-tag--format-cache 'missing))) + (formatted-tag (gethash (cons tag tag-state) + notmuch-tag--format-cache + 'missing))) (when (eq formatted-tag 'missing) (let ((base (notmuch-tag--get-formats tag notmuch-tag-formats)) (over (cl-case tag-state @@ -338,9 +342,9 @@ changed (the normal case) are shown using formats from (notmuch-apply-face (mapconcat #'identity ;; nil indicated that the tag was deliberately hidden - (delq nil (mapcar - (apply-partially #'notmuch-tag-format-tag tags orig-tags) - all-tags)) + (delq nil (mapcar (apply-partially #'notmuch-tag-format-tag + tags orig-tags) + all-tags)) " ") face t))) diff --git a/emacs/notmuch-tree.el b/emacs/notmuch-tree.el index ab90f652..fcfb972d 100644 --- a/emacs/notmuch-tree.el +++ b/emacs/notmuch-tree.el @@ -34,7 +34,8 @@ (require 'notmuch-tag) (require 'notmuch-parser) -(declare-function notmuch-search "notmuch" (&optional query oldest-first target-thread target-line)) +(declare-function notmuch-search "notmuch" + (&optional query oldest-first target-thread target-line)) (declare-function notmuch-call-notmuch-process "notmuch" (&rest args)) (declare-function notmuch-read-query "notmuch" (prompt)) (declare-function notmuch-search-find-thread-id "notmuch" (&optional bare)) @@ -284,15 +285,18 @@ FUNC." (set-keymap-parent map notmuch-common-keymap) ;; The following override the global keymap. ;; Override because we want to close message pane first. - (define-key map [remap notmuch-help] (notmuch-tree-close-message-pane-and #'notmuch-help)) + (define-key map [remap notmuch-help] + (notmuch-tree-close-message-pane-and #'notmuch-help)) ;; Override because we first close message pane and then close tree buffer. (define-key map [remap notmuch-bury-or-kill-this-buffer] 'notmuch-tree-quit) ;; Override because we close message pane after the search query is entered. (define-key map [remap notmuch-search] 'notmuch-tree-to-search) ;; Override because we want to close message pane first. - (define-key map [remap notmuch-mua-new-mail] (notmuch-tree-close-message-pane-and #'notmuch-mua-new-mail)) + (define-key map [remap notmuch-mua-new-mail] + (notmuch-tree-close-message-pane-and #'notmuch-mua-new-mail)) ;; Override because we want to close message pane first. - (define-key map [remap notmuch-jump-search] (notmuch-tree-close-message-pane-and #'notmuch-jump-search)) + (define-key map [remap notmuch-jump-search] + (notmuch-tree-close-message-pane-and #'notmuch-jump-search)) (define-key map "S" 'notmuch-search-from-tree-current-query) (define-key map "U" 'notmuch-unthreaded-from-tree-current-query) @@ -306,16 +310,24 @@ FUNC." (define-key map "b" 'notmuch-show-resend-message) ;; these apply to the message pane - (define-key map (kbd "M-TAB") (notmuch-tree-to-message-pane #'notmuch-show-previous-button)) - (define-key map (kbd "") (notmuch-tree-to-message-pane #'notmuch-show-previous-button)) - (define-key map (kbd "TAB") (notmuch-tree-to-message-pane #'notmuch-show-next-button)) - (define-key map "$" (notmuch-tree-to-message-pane #'notmuch-show-toggle-process-crypto)) + (define-key map (kbd "M-TAB") + (notmuch-tree-to-message-pane #'notmuch-show-previous-button)) + (define-key map (kbd "") + (notmuch-tree-to-message-pane #'notmuch-show-previous-button)) + (define-key map (kbd "TAB") + (notmuch-tree-to-message-pane #'notmuch-show-next-button)) + (define-key map "$" + (notmuch-tree-to-message-pane #'notmuch-show-toggle-process-crypto)) ;; bindings from show (or elsewhere) but we close the message pane first. - (define-key map "f" (notmuch-tree-close-message-pane-and #'notmuch-show-forward-message)) - (define-key map "r" (notmuch-tree-close-message-pane-and #'notmuch-show-reply-sender)) - (define-key map "R" (notmuch-tree-close-message-pane-and #'notmuch-show-reply)) - (define-key map "V" (notmuch-tree-close-message-pane-and #'notmuch-show-view-raw-message)) + (define-key map "f" + (notmuch-tree-close-message-pane-and #'notmuch-show-forward-message)) + (define-key map "r" + (notmuch-tree-close-message-pane-and #'notmuch-show-reply-sender)) + (define-key map "R" + (notmuch-tree-close-message-pane-and #'notmuch-show-reply)) + (define-key map "V" + (notmuch-tree-close-message-pane-and #'notmuch-show-view-raw-message)) ;; The main tree view bindings (define-key map (kbd "RET") 'notmuch-tree-show-message) @@ -354,7 +366,9 @@ Some useful entries are: (defun notmuch-tree-set-message-properties (props) (save-excursion (beginning-of-line) - (put-text-property (point) (+ (point) 1) :notmuch-message-properties props))) + (put-text-property (point) + (+ (point) 1) + :notmuch-message-properties props))) (defun notmuch-tree-set-prop (prop val &optional props) (let ((inhibit-read-only t) @@ -407,7 +421,8 @@ updated." ;; from overwriting the buffer local copy of ;; notmuch-tree-previous-subject if this is called while the ;; buffer is displaying. - (let ((notmuch-tree-previous-subject (notmuch-tree-get-prop :previous-subject))) + (let ((notmuch-tree-previous-subject + (notmuch-tree-get-prop :previous-subject))) (delete-region (point) (1+ (line-end-position))) (notmuch-tree-insert-msg msg)) (let ((new-end (line-end-position))) @@ -596,7 +611,8 @@ Shows in split pane or whole window according to value of "Close the message-window. Return t if close succeeds." (interactive) (when (and (window-live-p notmuch-tree-message-window) - (eq (window-buffer notmuch-tree-message-window) notmuch-tree-message-buffer)) + (eq (window-buffer notmuch-tree-message-window) + notmuch-tree-message-buffer)) (delete-window notmuch-tree-message-window) (unless (get-buffer-window-list notmuch-tree-message-buffer) (kill-buffer notmuch-tree-message-buffer)) @@ -611,7 +627,8 @@ message will be \"unarchived\", i.e. the tag changes in `notmuch-archive-tags' will be reversed." (interactive "P") (when notmuch-archive-tags - (notmuch-tree-tag (notmuch-tag-change-list notmuch-archive-tags unarchive)))) + (notmuch-tree-tag + (notmuch-tag-change-list notmuch-archive-tags unarchive)))) (defun notmuch-tree-archive-message-then-next (&optional unarchive) "Archive the current message and move to next matching message." @@ -786,7 +803,8 @@ unchanged ADDRESS if parsing fails." (let ((face (if match 'notmuch-tree-match-date-face 'notmuch-tree-no-match-date-face))) - (propertize (format format-string (plist-get msg :date_relative)) 'face face))) + (propertize (format format-string (plist-get msg :date_relative)) + 'face face))) ((string-equal field "tree") (let ((tree-status (plist-get msg :tree-status)) @@ -880,7 +898,8 @@ message together with all its descendents." ((and (< 0 depth) last) (push "╰" tree-status)) ((and (eq 0 depth) first last) -;; (push "─" tree-status)) choice between this and next line is matter of taste. + ;; Choice between these two variants is a matter of taste. + ;; (push "─" tree-status)) (push " " tree-status)) ((and (eq 0 depth) first (not last)) (push "┬" tree-status)) diff --git a/emacs/notmuch-wash.el b/emacs/notmuch-wash.el index fd0314cc..3a9ffdd0 100644 --- a/emacs/notmuch-wash.el +++ b/emacs/notmuch-wash.el @@ -25,7 +25,9 @@ (require 'coolj) (require 'notmuch-lib) -(declare-function notmuch-show-insert-bodypart "notmuch-show" (msg part depth &optional hide)) + +(declare-function notmuch-show-insert-bodypart "notmuch-show" + (msg part depth &optional hide)) (defvar notmuch-show-indent-messages-width) ;; @@ -186,9 +188,12 @@ message parts." (let* ((type (overlay-get overlay 'type)) (invis-spec (overlay-get overlay 'invisible)) (state (if (invisible-p invis-spec) "hidden" "visible")) - (label-format (symbol-value (intern-soft (concat "notmuch-wash-button-" - type "-" state "-format")))) - (lines-count (count-lines (overlay-start overlay) (overlay-end overlay)))) + (label-format (symbol-value + (intern-soft + (format "notmuch-wash-button-%s-%s-format" + type state)))) + (lines-count (count-lines (overlay-start overlay) + (overlay-end overlay)))) (format label-format lines-count))) (defun notmuch-wash-region-to-button (msg beg end type &optional prefix) @@ -238,7 +243,8 @@ that PREFIX should not include a newline." (let* ((cite-start (match-beginning 0)) (cite-end (match-end 0)) (cite-lines (count-lines cite-start cite-end))) - (overlay-put (make-overlay cite-start cite-end) 'face 'notmuch-wash-cited-text) + (overlay-put (make-overlay cite-start cite-end) + 'face 'notmuch-wash-cited-text) (when (> cite-lines (+ notmuch-wash-citation-lines-prefix notmuch-wash-citation-lines-suffix 1)) @@ -260,7 +266,8 @@ that PREFIX should not include a newline." (sig-end-marker (make-marker))) (set-marker sig-start-marker sig-start) (set-marker sig-end-marker (point-max)) - (overlay-put (make-overlay sig-start-marker sig-end-marker) 'face 'message-cited-text) + (overlay-put (make-overlay sig-start-marker sig-end-marker) + 'face 'message-cited-text) (notmuch-wash-region-to-button msg sig-start-marker sig-end-marker "signature")))))) diff --git a/emacs/notmuch.el b/emacs/notmuch.el index c97997fe..07a58da7 100644 --- a/emacs/notmuch.el +++ b/emacs/notmuch.el @@ -516,7 +516,9 @@ thread." (current-buffer) notmuch-search-query-string ;; Name the buffer based on the subject. - (concat "*" (truncate-string-to-width subject 30 nil nil t) "*")) + (concat "*" + (truncate-string-to-width subject 30 nil nil t) + "*")) (message "End of search results.")))) (defun notmuch-tree-from-search-current-query () @@ -800,7 +802,9 @@ non-authors is found, assume that all of the authors match." ;; If there is any invisible text, add it as a tooltip to the ;; visible text. (when (not (string= invisible-string "")) - (setq visible-string (propertize visible-string 'help-echo (concat "..." invisible-string)))) + (setq visible-string + (propertize visible-string + 'help-echo (concat "..." invisible-string)))) ;; Insert the visible and, if present, invisible author strings. (insert visible-string) @@ -892,7 +896,8 @@ See `notmuch-tag' for information on the format of TAG-CHANGES." (longest-length 0)) (cl-loop for tuple in notmuch-saved-searches if (let ((quoted-query - (regexp-quote (notmuch-saved-search-get tuple :query)))) + (regexp-quote + (notmuch-saved-search-get tuple :query)))) (and (string-match (concat "^" quoted-query) query) (> (length (match-string 0 query)) longest-length))) @@ -905,9 +910,10 @@ See `notmuch-tag' for information on the format of TAG-CHANGES." (concat "*notmuch-saved-search-" saved-search-name "*")) (saved-search (concat "*notmuch-search-" - (replace-regexp-in-string (concat "^" (regexp-quote saved-search-query)) - (concat "[ " saved-search-name " ]") - query) + (replace-regexp-in-string + (concat "^" (regexp-quote saved-search-query)) + (concat "[ " saved-search-name " ]") + query) "*")) (t (concat "*notmuch-search-" query "*")) @@ -926,7 +932,8 @@ PROMPT is the string to prompt with." "subject:" "attachment:") (mapcar (lambda (tag) (concat "tag:" tag)) all-tags) (mapcar (lambda (tag) (concat "is:" tag)) all-tags) - (mapcar (lambda (mimetype) (concat "mimetype:" mimetype)) (mailcap-mime-types))))) + (mapcar (lambda (mimetype) (concat "mimetype:" mimetype)) + (mailcap-mime-types))))) (let ((keymap (copy-keymap minibuffer-local-map)) (current-query (cl-case major-mode (notmuch-search-mode (notmuch-search-get-query)) @@ -1078,8 +1085,10 @@ current search results AND the additional query string provided." Runs a new search matching only messages that match both the current search results AND that are tagged with the given tag." (interactive - (list (notmuch-select-tag-with-completion "Filter by tag: " notmuch-search-query-string))) - (notmuch-search (concat notmuch-search-query-string " and tag:" tag) notmuch-search-oldest-first)) + (list (notmuch-select-tag-with-completion "Filter by tag: " + notmuch-search-query-string))) + (notmuch-search (concat notmuch-search-query-string " and tag:" tag) + notmuch-search-oldest-first)) (defun notmuch-search-by-tag (tag) "Display threads matching TAG in a notmuch-search buffer." diff --git a/emacs/rstdoc.el b/emacs/rstdoc.el index 2225aefc..2af91186 100644 --- a/emacs/rstdoc.el +++ b/emacs/rstdoc.el @@ -63,7 +63,8 @@ (defun rstdoc--insert-docstring (symbol docstring) (insert (format "\n.. |docstring::%s| replace::\n" symbol)) - (insert (replace-regexp-in-string "^" " " (rstdoc--rst-quote-string docstring))) + (insert (replace-regexp-in-string "^" " " + (rstdoc--rst-quote-string docstring))) (insert "\n")) (defvar rst--escape-alist From 6fb7d35069c8770b872128156cb4f0511da6b6e9 Mon Sep 17 00:00:00 2001 From: Jonas Bernoulli Date: Sat, 8 Aug 2020 13:49:37 +0200 Subject: [PATCH 337/427] emacs: Remove excess empty lines Most people who write lots of lisp tend to only sparsely use empty "separator" lines within forms. In lisp they feel unnecessary and since most files stick to this convention we get a bit confused when there are extra empty lines. It feels like the s-expressions are falling into pieces. All of this is especially true between a function's doc-string and body because the doc-string is colored differently, which visually already separates it quite sufficiently from the code that follows. --- emacs/make-deps.el | 2 - emacs/notmuch-address.el | 2 - emacs/notmuch-crypto.el | 4 -- emacs/notmuch-hello.el | 18 --------- emacs/notmuch-jump.el | 5 --- emacs/notmuch-lib.el | 10 ----- emacs/notmuch-maildir-fcc.el | 7 ---- emacs/notmuch-mua.el | 26 ------------- emacs/notmuch-parser.el | 5 --- emacs/notmuch-show.el | 75 ++---------------------------------- emacs/notmuch-tag.el | 3 -- emacs/notmuch-tree.el | 9 ----- emacs/notmuch-wash.el | 18 +-------- emacs/notmuch.el | 7 ---- 14 files changed, 5 insertions(+), 186 deletions(-) diff --git a/emacs/make-deps.el b/emacs/make-deps.el index 5b6db698..dcac319c 100644 --- a/emacs/make-deps.el +++ b/emacs/make-deps.el @@ -23,7 +23,6 @@ (defun batch-make-deps () "Invoke `make-deps' for each file on the command line." - (setq debug-on-error t) (dolist (file command-line-args-left) (let ((default-directory command-line-default-directory)) @@ -37,7 +36,6 @@ This prints make dependencies to `standard-output' based on the top-level `require' expressions in the current buffer. Paths in rules will be given relative to DIR, or `default-directory'." - (setq dir (or dir default-directory)) (save-excursion (goto-char (point-min)) diff --git a/emacs/notmuch-address.el b/emacs/notmuch-address.el index 2a9c411a..ca4da3f3 100644 --- a/emacs/notmuch-address.el +++ b/emacs/notmuch-address.el @@ -302,7 +302,6 @@ matching ADDR-PREFIX*' are queried. Address harvesting may take some time so the address collection runs asynchronously unless SYNCHRONOUS is t. In case of asynchronous execution, CALLBACK is called when harvesting finishes." - (let* ((sent (eq (car notmuch-address-internal-completion) 'sent)) (config-query (cadr notmuch-address-internal-completion)) (prefix-query (when addr-prefix @@ -335,7 +334,6 @@ execution, CALLBACK is called when harvesting finishes." ;; Kill any existing process (when current-proc (kill-buffer (process-buffer current-proc))) ; this also kills the process - (setq current-proc (apply 'notmuch-start-notmuch proc-name proc-buf callback ; process sentinel diff --git a/emacs/notmuch-crypto.el b/emacs/notmuch-crypto.el index 2327ff1f..58947a20 100644 --- a/emacs/notmuch-crypto.el +++ b/emacs/notmuch-crypto.el @@ -123,17 +123,14 @@ by user FROM." face 'notmuch-crypto-signature-good-key)) (setq button-action 'notmuch-crypto-sigstatus-good-callback help-msg (concat "Click to list key ID 0x" fingerprint ".")))) - ((string= status "error") (setq label (concat "Unknown key ID " keyid " or unsupported algorithm") button-action 'notmuch-crypto-sigstatus-error-callback help-msg (concat "Click to retrieve key ID " keyid " from keyserver."))) - ((string= status "bad") (setq label (concat "Bad signature (claimed key ID " keyid ")") face 'notmuch-crypto-signature-bad)) - (status (setq label (concat "Unknown signature status: " status))) (t @@ -232,7 +229,6 @@ corresponding key when the status button is pressed." (process-put p :notmuch-show-buffer (current-buffer)) (process-put p :notmuch-show-point (point)) (message "Getting the GPG key %s asynchronously..." keyid))) - (let ((window (display-buffer buffer))) (with-selected-window window (with-current-buffer buffer diff --git a/emacs/notmuch-hello.el b/emacs/notmuch-hello.el index 11c625ea..e71e55f3 100644 --- a/emacs/notmuch-hello.el +++ b/emacs/notmuch-hello.el @@ -150,7 +150,6 @@ or a list of the form (NAME QUERY COUNT-QUERY)." ;; The saved-search format is also used by the all-tags notmuch-hello ;; section. This section generates its own saved-search list in one of ;; the latter two forms. - :get 'notmuch-hello--saved-searches-to-plist :type '(repeat notmuch-saved-search-plist) :tag "List of Saved Searches" @@ -482,20 +481,17 @@ should be. Returns a cons cell `(tags-per-line width)'." ;; Count is 9 wide (8 digits plus space), 1 for the space ;; after the name. (+ 9 1 (max notmuch-column-control widest))))) - ((floatp notmuch-column-control) (let* ((available-width (- (window-width) notmuch-hello-indent)) (proposed-width (max (* available-width notmuch-column-control) widest))) (floor available-width proposed-width))) - (t (max 1 (/ (- (window-width) notmuch-hello-indent) ;; Count is 9 wide (8 digits plus space), 1 for the space ;; after the name. (+ 9 1 widest))))))) - (cons tags-per-line (/ (max 1 (- (window-width) notmuch-hello-indent ;; Count is 9 wide (8 digits plus @@ -545,7 +541,6 @@ options will be handled as specified for (or (plist-get options :filter-count) (plist-get options :filter)))) "\n"))) - (unless (= (call-process-region (point-min) (point-max) notmuch-command t t nil "count" "--batch") 0) (notmuch-logged-error @@ -553,9 +548,7 @@ options will be handled as specified for "Please check that the notmuch CLI is new enough to support `count --batch'. In general we recommend running matching versions of the CLI and emacs interface.")) - (goto-char (point-min)) - (notmuch-remove-if-not #'identity (mapcar @@ -621,7 +614,6 @@ with `notmuch-hello-query-counts'." (setq column-indent 0) (widget-insert "\n"))) reordered-list) - ;; If the last line was not full (and hence did not include a ;; carriage return), insert one now. (unless (eq (% count tags-per-line) 0) @@ -780,7 +772,6 @@ Complete list of currently available key bindings: (car (process-lines notmuch-command "count"))))) (widget-insert " messages.\n"))) - (defun notmuch-hello-insert-saved-searches () "Insert the saved-searches section." (let ((searches (notmuch-hello-query-counts @@ -977,7 +968,6 @@ following: (defun notmuch-hello (&optional no-display) "Run notmuch and display saved searches, known tags, etc." (interactive) - (notmuch-assert-cli-sane) ;; This may cause a window configuration change, so if the ;; auto-refresh hook is already installed, avoid recursive refresh. @@ -985,32 +975,25 @@ following: (if no-display (set-buffer "*notmuch-hello*") (switch-to-buffer "*notmuch-hello*"))) - ;; Install auto-refresh hook (when notmuch-hello-auto-refresh (add-hook 'window-configuration-change-hook #'notmuch-hello-window-configuration-change)) - (let ((target-line (line-number-at-pos)) (target-column (current-column)) (inhibit-read-only t)) - ;; Delete all editable widget fields. Editable widget fields are ;; tracked in a buffer local variable `widget-field-list' (and ;; others). If we do `erase-buffer' without properly deleting the ;; widgets, some widget-related functions are confused later. (mapc 'widget-delete widget-field-list) - (erase-buffer) - (unless (eq major-mode 'notmuch-hello-mode) (notmuch-hello-mode)) - (let ((all (overlay-lists))) ;; Delete all the overlays. (mapc 'delete-overlay (car all)) (mapc 'delete-overlay (cdr all))) - (mapc (lambda (section) (let ((point-before (point))) @@ -1023,7 +1006,6 @@ following: (widget-insert "\n")))) notmuch-hello-sections) (widget-setup) - ;; Move point back to where it was before refresh. Use line and ;; column instead of point directly to be insensitive to additions ;; and removals of text within earlier lines. diff --git a/emacs/notmuch-jump.el b/emacs/notmuch-jump.el index 6d8cd97f..adf79650 100644 --- a/emacs/notmuch-jump.el +++ b/emacs/notmuch-jump.el @@ -43,7 +43,6 @@ keys configured in the :key property of `notmuch-saved-searches'. Typically these shortcuts are a single key long, so this is a fast way to jump to a saved search from anywhere in Notmuch." (interactive) - ;; Build the action map (let (action-map) (dolist (saved-search notmuch-saved-searches) @@ -67,7 +66,6 @@ fast way to jump to a saved search from anywhere in Notmuch." `(lambda () (notmuch-search ',query ',oldest-first))))) action-map))))) (setq action-map (nreverse action-map)) - (if action-map (notmuch-jump action-map "Search: ") (error "To use notmuch-jump, \ @@ -90,7 +88,6 @@ where KEY is a key binding, LABEL is a string label to display in the buffer, and ACTION is a nullary function to call. LABEL may be null, in which case the action will still be bound, but will not appear in the pop-up buffer." - (let* ((items (notmuch-jump--format-actions action-map)) ;; Format the table of bindings and the full prompt (table @@ -115,7 +112,6 @@ not appear in the pop-up buffer." (notmuch-jump--action nil)) ;; Read the action (read-from-minibuffer full-prompt nil minibuffer-map) - ;; If we got an action, do it (when notmuch-jump--action (funcall notmuch-jump--action)))) @@ -126,7 +122,6 @@ not appear in the pop-up buffer." Returns a list of strings, one for each item with a label in ACTION-MAP. These strings can be inserted into a tabular buffer." - ;; Compute the maximum key description width (let ((key-width 1)) (pcase-dolist (`(,key ,desc) action-map) diff --git a/emacs/notmuch-lib.el b/emacs/notmuch-lib.el index 9339f9d0..615207ed 100644 --- a/emacs/notmuch-lib.el +++ b/emacs/notmuch-lib.el @@ -418,7 +418,6 @@ of its command symbol." (while (< i (length prefix)) (aset prefix i (aref key i)) (setq i (1+ i))) - (let* ((subkeymap (key-binding prefix)) (ua-keys (where-is-internal 'universal-argument nil t)) (prefix-string (notmuch-prefix-key-description prefix)) @@ -486,7 +485,6 @@ This includes newlines, tabs, and other funny characters." The caller is responsible for prepending the term prefix and a colon. This performs minimal escaping in order to produce user-friendly queries." - (save-match-data (if (or (equal term "") ;; To be pessimistic, only pass through terms composed @@ -731,7 +729,6 @@ must be a face name (a symbol or string), a property list of face attributes, or a list of these. If START and/or END are omitted, they default to the beginning/end of OBJECT. For convenience when applied to strings, this returns OBJECT." - ;; A face property can have three forms: a face name (a string or ;; symbol), a property list, or a list of these two forms. In the ;; list case, the faces will be combined, with the earlier faces @@ -774,7 +771,6 @@ This logs MSG and EXTRA to the *Notmuch errors* buffer and signals MSG as an error. If EXTRA is non-nil, text referring the user to the *Notmuch errors* buffer will be appended to the signaled error. This function does not return." - (with-current-buffer (get-buffer-create "*Notmuch errors*") (goto-char (point-max)) (unless (bobp) @@ -819,7 +815,6 @@ command and its arguments. OUTPUT, if provided, is a string giving the output of command. ERR, if provided, is the error output of command. OUTPUT and ERR will be included in the error message." - (cond ((eq exit-status 0) t) ((eq exit-status 20) @@ -865,7 +860,6 @@ You may need to restart Emacs or upgrade your notmuch package.")) This wraps `call-process'. DESTINATION has the same meaning as for `call-process'. ARGS is as described for `notmuch-call-notmuch-process'." - (let (stdin-string) (while (keywordp (car args)) (cl-case (car args) @@ -903,7 +897,6 @@ notmuch's output as an S-expression and returns the parsed value. Like `notmuch-call-notmuch-process', if notmuch exits with a non-zero status, this will report its output and signal an error." - (with-temp-buffer (let ((err-file (make-temp-file "nmerr"))) (unwind-protect @@ -931,7 +924,6 @@ when the process exits, or nil for none. The caller must *not* invoke `set-process-sentinel' directly on the returned process, as that will interfere with the handling of stderr and the exit status." - (let (err-file err-buffer proc err-proc ;; Find notmuch using Emacs' `exec-path' (command (or (executable-find notmuch-command) @@ -956,7 +948,6 @@ status." (process-put err-proc 'err-file err-file) (process-put err-proc 'err-buffer err-buffer) (set-process-sentinel err-proc #'notmuch-start-notmuch-error-sentinel)) - ;; On Emacs versions before 25, there is no way to capture ;; stdout and stderr separately for asynchronous processes, or ;; even to redirect stderr to a file, so we use a trivial shell @@ -969,7 +960,6 @@ status." "exec 2>\"$1\"; shift; exec \"$0\" \"$@\"" command err-file args))) (process-put proc 'err-file err-file)) - (process-put proc 'sub-sentinel sentinel) (process-put proc 'real-command (cons notmuch-command args)) (set-process-sentinel proc #'notmuch-start-notmuch-sentinel) diff --git a/emacs/notmuch-maildir-fcc.el b/emacs/notmuch-maildir-fcc.el index 4b10a731..66f32fd4 100644 --- a/emacs/notmuch-maildir-fcc.el +++ b/emacs/notmuch-maildir-fcc.el @@ -94,22 +94,18 @@ Sets the Fcc header based on the values of `notmuch-fcc-dirs'. Originally intended to be use a hook function, but now called directly by notmuch-mua-mail." - (let ((subdir (cond ((or (not notmuch-fcc-dirs) (message-field-value "Fcc")) ;; Nothing set or an existing header. nil) - ((stringp notmuch-fcc-dirs) notmuch-fcc-dirs) - ((and (listp notmuch-fcc-dirs) (stringp (car notmuch-fcc-dirs))) ;; Old style - no longer works. (error "Invalid `notmuch-fcc-dirs' setting (old style)")) - ((listp notmuch-fcc-dirs) (let* ((from (message-field-value "From")) (match @@ -121,10 +117,8 @@ by notmuch-mua-mail." (cdr match) (message "No Fcc header added.") nil))) - (t (error "Invalid `notmuch-fcc-dirs' setting (neither string nor list)"))))) - (when subdir (if notmuch-maildir-use-notmuch-insert (notmuch-maildir-add-notmuch-insert-style-fcc-header subdir) @@ -133,7 +127,6 @@ by notmuch-mua-mail." (defun notmuch-maildir-add-notmuch-insert-style-fcc-header (subdir) ;; Notmuch insert does not accept absolute paths, so check the user ;; really want this header inserted. - (when (or (not (= (elt subdir 0) ?/)) (y-or-n-p (format "Fcc header %s is an absolute path and notmuch insert is requested. diff --git a/emacs/notmuch-mua.el b/emacs/notmuch-mua.el index 74dfb384..3bc06b4b 100644 --- a/emacs/notmuch-mua.el +++ b/emacs/notmuch-mua.el @@ -228,22 +228,17 @@ Typically this is added to `notmuch-mua-send-hook'." original) (when process-crypto (setq args (append args '("--decrypt=true")))) - (if reply-all (setq args (append args '("--reply-to=all"))) (setq args (append args '("--reply-to=sender")))) (setq args (append args (list query-string))) - ;; Get the reply object as SEXP, and parse it into an elisp object. (setq reply (apply #'notmuch-call-notmuch-sexp args)) - ;; Extract the original message to simplify the following code. (setq original (plist-get reply :original)) - ;; Extract the headers of both the reply and the original message. (let* ((original-headers (plist-get original :headers)) (reply-headers (plist-get reply :reply-headers))) - ;; If sender is non-nil, set the From: header to its value. (when sender (plist-put reply-headers :From sender)) @@ -251,7 +246,6 @@ Typically this is added to `notmuch-mua-send-hook'." ;; Overlay the composition window on that being used to read ;; the original message. ((same-window-regexps '("\\*mail .*"))) - ;; We modify message-header-format-alist to get around ;; a bug in message.el. See the comment above on ;; notmuch-mua-insert-references. @@ -268,13 +262,11 @@ Typically this is added to `notmuch-mua-send-hook'." (notmuch-sanitize (plist-get reply-headers :Subject)) (notmuch-headers-plist-to-alist reply-headers) nil (notmuch-mua-get-switch-function)))) - ;; Create a buffer-local queue for tag changes triggered when ;; sending the reply. (when notmuch-message-replied-tags (setq-local notmuch-message-queued-tag-changes (list (cons query-string notmuch-message-replied-tags)))) - ;; Insert the message body - but put it in front of the signature ;; if one is present, and after any other content ;; message*setup-hooks may have added to the message body already. @@ -286,17 +278,14 @@ Typically this is added to `notmuch-mua-send-hook'." (if message-signature-insert-empty-line (forward-line -1)) (goto-char (point-max)))) - (let ((from (plist-get original-headers :From)) (date (plist-get original-headers :Date)) (start (point))) - ;; notmuch-mua-cite-function constructs a citation line based ;; on the From and Date headers of the original message, which ;; are assumed to be in the buffer. (insert "From: " from "\n") (insert "Date: " date "\n\n") - (insert (with-temp-buffer (let @@ -320,22 +309,18 @@ Typically this is added to `notmuch-mua-send-hook'." ((symbol-function 'notmuch-crypto-insert-encstatus-button) #'ignore)) (notmuch-show-insert-body original (plist-get original :body) 0) (buffer-substring-no-properties (point-min) (point-max)))))) - (set-mark (point)) (goto-char start) ;; Quote the original message according to the user's configured style. (funcall notmuch-mua-cite-function))) - ;; Crypto processing based crypto content of the original message (when process-crypto (notmuch-mua-reply-crypto (plist-get original :body)))) - ;; Push mark right before signature, if any. (message-goto-signature) (unless (eobp) (end-of-line -1)) (push-mark) - (message-goto-body) (set-buffer-modified-p nil)) @@ -381,18 +366,15 @@ modified. This function is notmuch addaptation of return-action &rest ignored) "Invoke the notmuch mail composition window." (interactive) - (when notmuch-mua-user-agent-function (let ((user-agent (funcall notmuch-mua-user-agent-function))) (when (not (string= "" user-agent)) (push (cons 'User-Agent user-agent) other-headers)))) - (unless (assq 'From other-headers) (push (cons 'From (message-make-from (notmuch-user-name) (notmuch-user-primary-email))) other-headers)) - (notmuch-mua-pop-to-buffer (message-buffer-name "mail" to) (or switch-function (notmuch-mua-get-switch-function))) @@ -422,7 +404,6 @@ modified. This function is notmuch addaptation of (message-hide-headers) (set-buffer-modified-p nil) (notmuch-mua-maybe-set-window-dedicated) - (message-goto-to)) (defcustom notmuch-identities nil @@ -495,10 +476,8 @@ the From: address." ;; applied later. forward-references ;; List of accumulated message-references of forwarded messages forward-queries) ;; List of corresponding message-query - ;; Generate the template for the outgoing message. (notmuch-mua-mail nil "" other-headers nil (notmuch-mua-get-switch-function)) - (save-excursion ;; Insert all of the forwarded messages. (mapc (lambda (id) @@ -524,7 +503,6 @@ the From: address." ;; `message-forward-make-body' always puts the message at ;; the top, so do them in reverse order. (reverse messages)) - ;; Add in the appropriate subject. (save-restriction (message-narrow-to-headers) @@ -533,7 +511,6 @@ the From: address." (message-remove-header "References") (message-add-header (concat "References: " (mapconcat 'identity forward-references " ")))) - ;; Create a buffer-local queue for tag changes triggered when ;; sending the message. (when notmuch-message-forwarded-tags @@ -541,7 +518,6 @@ the From: address." (cl-loop for id in forward-queries collect (cons id notmuch-message-forwarded-tags)))) - ;; `message-forward-make-body' shows the User-agent header. Hide ;; it again. (message-hide-headers) @@ -553,7 +529,6 @@ the From: address." If PROMPT-FOR-SENDER is non-nil, the user will be prompted for the From: address first. If REPLY-ALL is non-nil, the message will be addressed to all recipients of the source message." - ;; In current emacs (24.3) select-active-regions is set to t by ;; default. The reply insertion code sets the region to the quoted ;; message to make it easy to delete (kill-region or C-w). These two @@ -565,7 +540,6 @@ will be addressed to all recipients of the source message." ;; primary selection was previously in a non-emacs window but not if ;; it was in an emacs window. To avoid the problem in the latter case ;; we deactivate mark. - (let ((sender (when prompt-for-sender (notmuch-mua-prompt-for-sender))) diff --git a/emacs/notmuch-parser.el b/emacs/notmuch-parser.el index dc9fbe2f..06e7487b 100644 --- a/emacs/notmuch-parser.el +++ b/emacs/notmuch-parser.el @@ -38,7 +38,6 @@ can return 'retry to indicate that not enough input is available. The parser always consumes input from point in the current buffer. Hence, the caller is allowed to delete any data before point and may resynchronize after an error by moving point." - (vector 'notmuch-sexp-parser ;; List depth 0 @@ -60,7 +59,6 @@ parser is currently inside a list and the next token ends the list, this moves point just past the terminator and returns 'end. Otherwise, this moves point to just past the end of the value and returns the value." - (skip-chars-forward " \n\r\t") (cond ((eobp) 'retry) ((= (char-after) ?\)) @@ -134,7 +132,6 @@ a list, it moves point past the token that opens the list and returns t. Later calls to `notmuch-sexp-read' will return the elements inside the list. If the input in buffer is not the beginning of a list, throw invalid-read-syntax." - (skip-chars-forward " \n\r\t") (cond ((eobp) 'retry) ((= (char-after) ?\() @@ -151,7 +148,6 @@ beginning of a list, throw invalid-read-syntax." Moves point to the beginning of any trailing data or to the end of the buffer if there is only trailing whitespace." - (skip-chars-forward " \n\r\t") (unless (eobp) (error "Trailing garbage following expression"))) @@ -173,7 +169,6 @@ complete value in the list. It operates incrementally and should be called whenever the input buffer has been extended with additional data. The caller just needs to ensure it does not move point in the input buffer." - ;; Set up the initial state (unless (local-variable-p 'notmuch-sexp--parser) (set (make-local-variable 'notmuch-sexp--parser) diff --git a/emacs/notmuch-show.el b/emacs/notmuch-show.el index 41f31c46..4520d479 100644 --- a/emacs/notmuch-show.el +++ b/emacs/notmuch-show.el @@ -320,7 +320,6 @@ position of the message in the thread." FN is called with one argument, the message properties. It should operation on the contents of the current buffer." - ;; Remake the header to ensure that all information is available. (let* ((to (notmuch-show-get-to)) (cc (notmuch-show-get-cc)) @@ -329,7 +328,6 @@ operation on the contents of the current buffer." (date (notmuch-show-get-date)) (tags (notmuch-show-get-tags)) (depth (notmuch-show-get-depth)) - (header (concat "Subject: " subject "\n" "To: " to "\n" @@ -375,7 +373,6 @@ operation on the contents of the current buffer." 'message-header-subject) (t 'message-header-other)))) - (overlay-put (make-overlay (point) (re-search-forward ":")) 'face 'message-header-name) (overlay-put (make-overlay (point) (re-search-forward ".*$")) @@ -421,39 +418,30 @@ parsing fails." ((string-match "\\(.*\\) <\\(.*\\)>" address) (setq p-name (match-string 1 address) p-address (match-string 2 address))) - ;; "" style. ((string-match "<\\(.*\\)>" address) (setq p-address (match-string 1 address))) - ;; Everything else. (t (setq p-address address))) - (when p-name ;; Remove elements of the mailbox part that are not relevant for ;; display, even if they are required during transport: ;; ;; Backslashes. (setq p-name (replace-regexp-in-string "\\\\" "" p-name)) - ;; Outer single and double quotes, which might be nested. (cl-loop with start-of-loop - do (setq start-of-loop p-name) - + do (setq start-of-loop p-name) when (string-match "^\"\\(.*\\)\"$" p-name) - do (setq p-name (match-string 1 p-name)) - + do (setq p-name (match-string 1 p-name)) when (string-match "^'\\(.*\\)'$" p-name) - do (setq p-name (match-string 1 p-name)) - + do (setq p-name (match-string 1 p-name)) until (string= start-of-loop p-name))) - ;; If the address is 'foo@bar.com ' then show just ;; 'foo@bar.com'. (when (string= p-name p-address) (setq p-name nil)) - (cons p-address p-name)) (error (cons address nil)))) @@ -523,7 +511,6 @@ message at DEPTH in the current thread." (unless (string-equal declared-type content-type) (concat " (as " content-type ")")) comment))) - (setq button (insert-button (concat "[ " base-label " ]") @@ -664,14 +651,12 @@ will return nil if the CID is unknown or cannot be retrieved." (defun notmuch-show-insert-part-multipart/related (msg part content-type nth depth button) (let ((inner-parts (plist-get part :content)) (start (point))) - ;; Render the primary part. FIXME: Support RFC 2387 Start header. (notmuch-show-insert-bodypart msg (car inner-parts) depth) ;; Add hidden buttons for the rest (mapc (lambda (inner-part) (notmuch-show-insert-bodypart msg inner-part depth t)) (cdr inner-parts)) - (when notmuch-show-indent-multipart (indent-rigidly start (point) 1))) t) @@ -679,18 +664,15 @@ will return nil if the CID is unknown or cannot be retrieved." (defun notmuch-show-insert-part-multipart/signed (msg part content-type nth depth button) (when button (button-put button 'face 'notmuch-crypto-part-header)) - ;; Insert a button detailing the signature status. (notmuch-crypto-insert-sigstatus-button (car (plist-get part :sigstatus)) (notmuch-show-get-header :From msg)) - (let ((inner-parts (plist-get part :content)) (start (point))) ;; Show all of the parts. (mapc (lambda (inner-part) (notmuch-show-insert-bodypart msg inner-part depth)) inner-parts) - (when notmuch-show-indent-multipart (indent-rigidly start (point) 1))) t) @@ -698,21 +680,17 @@ will return nil if the CID is unknown or cannot be retrieved." (defun notmuch-show-insert-part-multipart/encrypted (msg part content-type nth depth button) (when button (button-put button 'face 'notmuch-crypto-part-header)) - ;; Insert a button detailing the encryption status. (notmuch-crypto-insert-encstatus-button (car (plist-get part :encstatus))) - ;; Insert a button detailing the signature status. (notmuch-crypto-insert-sigstatus-button (car (plist-get part :sigstatus)) (notmuch-show-get-header :From msg)) - (let ((inner-parts (plist-get part :content)) (start (point))) ;; Show all of the parts. (mapc (lambda (inner-part) (notmuch-show-insert-bodypart msg inner-part depth)) inner-parts) - (when notmuch-show-indent-multipart (indent-rigidly start (point) 1))) t) @@ -727,7 +705,6 @@ will return nil if the CID is unknown or cannot be retrieved." (mapc (lambda (inner-part) (notmuch-show-insert-bodypart msg inner-part depth)) inner-parts) - (when notmuch-show-indent-multipart (indent-rigidly start (point) 1))) t) @@ -736,19 +713,15 @@ will return nil if the CID is unknown or cannot be retrieved." (let* ((message (car (plist-get part :content))) (body (car (plist-get message :body))) (start (point))) - ;; Override `notmuch-message-headers' to force `From' to be ;; displayed. (let ((notmuch-message-headers '("From" "Subject" "To" "Cc" "Date"))) (notmuch-show-insert-headers (plist-get message :headers))) - ;; Blank line after headers to be compatible with the normal ;; message display. (insert "\n") - ;; Show the body (notmuch-show-insert-bodypart msg body depth) - (when notmuch-show-indent-multipart (indent-rigidly start (point) 1))) t) @@ -832,7 +805,6 @@ will return nil if the CID is unknown or cannot be retrieved." ;; It's easier to drive shr ourselves than to work around the ;; goofy things `mm-shr' does (like irreversibly taking over ;; content ID handling). - ;; FIXME: If we block an image, offer a button to load external ;; images. (let ((shr-blocked-images notmuch-show-text/html-blocked-images)) @@ -908,7 +880,6 @@ will return nil if the CID is unknown or cannot be retrieved." (defun notmuch-show-create-part-overlays (button beg end) "Add an overlay to the part between BEG and END." - ;; If there is no button (i.e., the part is text/plain and the first ;; part) or if the part has no content then we don't make the part ;; toggleable. @@ -919,7 +890,6 @@ will return nil if the CID is unknown or cannot be retrieved." (defun notmuch-show-record-part-information (part beg end) "Store PART as a text property from BEG to END." - ;; Record part information. Since we already inserted subparts, ;; don't override existing :notmuch-part properties. (notmuch-map-text-property beg end :notmuch-part @@ -1017,7 +987,6 @@ this part.") HIDE determines whether to show or hide the part and the button as follows: If HIDE is nil, show the part and the button. If HIDE is t, hide the part initially and show the button." - (let* ((content-type (downcase (plist-get part :content-type))) (mime-type (notmuch-show-mime-type part)) (nth (plist-get part :id)) @@ -1036,16 +1005,13 @@ is t, hide the part initially and show the button." (show-part (not (or (equal hide t) (and long button)))) (content-beg (point))) - ;; Store the computed mime-type for later use (e.g. by attachment handlers). (plist-put part :computed-type mime-type) - (if show-part (notmuch-show-insert-bodypart-internal msg part mime-type nth depth button) (when button (button-put button :notmuch-lazy-part (list msg part mime-type nth depth button)))) - ;; Some of the body part handlers leave point somewhere up in the ;; part, so we make sure that we're down at the end. (goto-char (point-max)) @@ -1062,12 +1028,10 @@ is t, hide the part initially and show the button." (defun notmuch-show-insert-body (msg body depth) "Insert the body BODY at depth DEPTH in the current thread." - ;; Register all content IDs for this message. According to RFC ;; 2392, content IDs are *global*, but it's okay if an MUA treats ;; them as only global within a message. (notmuch-show--register-cids msg (car body)) - (mapc (lambda (part) (notmuch-show-insert-bodypart msg part depth)) body)) (defun notmuch-show-make-symbol (type) @@ -1088,18 +1052,14 @@ is t, hide the part initially and show the button." content-start content-end headers-start headers-end (bare-subject (notmuch-show-strip-re (plist-get headers :Subject)))) - (setq message-start (point-marker)) - (notmuch-show-insert-headerline headers (or (if notmuch-show-relative-dates (plist-get msg :date_relative) nil) (plist-get headers :Date)) (plist-get msg :tags) depth) - (setq content-start (point-marker)) - ;; Set `headers-start' to point after the 'Subject:' header to be ;; compatible with the existing implementation. This just sets it ;; to after the first header. @@ -1114,9 +1074,7 @@ is t, hide the part initially and show the button." (forward-line 1)) (setq headers-start (point-marker))) (setq headers-end (point-marker)) - (setq notmuch-show-previous-subject bare-subject) - ;; A blank line between the headers and the body. (insert "\n") (notmuch-show-insert-body msg (plist-get msg :body) @@ -1125,36 +1083,28 @@ is t, hide the part initially and show the button." (unless (bolp) (insert "\n")) (setq content-end (point-marker)) - ;; Indent according to the depth in the thread. (if notmuch-show-indent-content (indent-rigidly content-start content-end (* notmuch-show-indent-messages-width depth))) - (setq message-end (point-max-marker)) - ;; Save the extents of this message over the whole text of the ;; message. (put-text-property message-start message-end :notmuch-message-extent (cons message-start message-end)) - ;; Create overlays used to control visibility (plist-put msg :headers-overlay (make-overlay headers-start headers-end)) (plist-put msg :message-overlay (make-overlay headers-start content-end)) - (plist-put msg :depth depth) - ;; Save the properties for this message. Currently this saves the ;; entire message (augmented it with other stuff), which seems ;; like overkill. We might save a reduced subset (for example, not ;; the content). (notmuch-show-set-message-properties msg) - ;; Set header visibility. (notmuch-show-headers-visible msg notmuch-message-headers-visible) - ;; Message visibility depends on whether it matched the search ;; criteria. (notmuch-show-message-visible msg (and (plist-get msg :match) @@ -1302,9 +1252,7 @@ matched." (switch-to-buffer (get-buffer-create buffer-name)) ;; No need to track undo information for this buffer. (setq buffer-undo-list t) - (notmuch-show-mode) - ;; Set various buffer local variables to their appropriate initial ;; state. Do this after enabling `notmuch-show-mode' so that they ;; aren't wiped out. @@ -1313,24 +1261,19 @@ matched." notmuch-show-query-context (if (or (string= query-context "") (string= query-context "*")) nil query-context) - notmuch-show-process-crypto notmuch-crypto-process-mime ;; If `elide-toggle', invert the default value. notmuch-show-elide-non-matching-messages (if elide-toggle (not notmuch-show-only-matching-messages) notmuch-show-only-matching-messages)) - (add-hook 'post-command-hook #'notmuch-show-command-hook nil t) (jit-lock-register #'notmuch-show-buttonise-links) - (notmuch-tag-clear-cache) - (let ((inhibit-read-only t)) (if (notmuch-show--build-buffer) ;; Messages were inserted into the buffer. (current-buffer) - ;; No messages were inserted - presumably none matched the ;; query. (kill-buffer (current-buffer)) @@ -1373,26 +1316,21 @@ If no messages match the query return NIL." (setq queries (cdr queries))) (when forest (notmuch-show-insert-forest forest) - ;; Store the original tags for each message so that we can ;; display changes. (notmuch-show-mapc (lambda () (notmuch-show-set-prop :orig-tags (notmuch-show-get-tags)))) - ;; Set the header line to the subject of the first message. (setq header-line-format (replace-regexp-in-string "%" "%%" (notmuch-sanitize (notmuch-show-strip-re (notmuch-show-get-subject))))) - (run-hooks 'notmuch-show-hook) - (if state (notmuch-show-apply-state state) ;; With no state to apply, just go to the first message. (notmuch-show-goto-first-wanted-message))) - ;; Report back to the caller whether any messages matched. forest)) @@ -1437,14 +1375,12 @@ This includes: - moving to the correct current message in every displayed window." (let ((win-msg-alist (car state)) (open (cadr state))) - ;; Open those that were open. (goto-char (point-min)) (cl-loop do (notmuch-show-message-visible (notmuch-show-get-message-properties) (member (notmuch-show-get-message-id) open)) until (not (notmuch-show-goto-message-next))) - (dolist (win-msg-pair win-msg-alist) (with-selected-window (car win-msg-pair) ;; Go to the previously open message in this window @@ -1466,7 +1402,6 @@ reset based on the original query." ;; manually. (remove-overlays) (erase-buffer) - (unless (notmuch-show--build-buffer state) ;; No messages were inserted. (kill-buffer (current-buffer)) @@ -1887,16 +1822,13 @@ current window), advance to the next open message." (> visible-end-of-this-message (window-end))) ;; The bottom of this message is not visible - scroll. (scroll-up nil)) - ((not (= end-of-this-message (point-max))) ;; This is not the last message - move to the next visible one. (notmuch-show-next-open-message)) - ((not (= (point) (point-max))) ;; This is the last message, but the cursor is not at the end of ;; the buffer. Move it there. (goto-char (point-max))) - (t ;; This is the last message - change the return value (setq ret t))) @@ -2533,7 +2465,6 @@ part to be treated as if it had that mime-type." (interactive) (notmuch-show-apply-to-current-part-handle #'mm-pipe-part)) - (defun notmuch-show--mm-display-part (handle) "Use mm-display-part to display HANDLE in a new buffer. diff --git a/emacs/notmuch-tag.el b/emacs/notmuch-tag.el index 34cd493a..85c80f54 100644 --- a/emacs/notmuch-tag.el +++ b/emacs/notmuch-tag.el @@ -331,7 +331,6 @@ changed (the normal case) are shown using formats from (otherwise nil)))) (setq formatted-tag (notmuch-tag--do-format tag tag base)) (setq formatted-tag (notmuch-tag--do-format tag formatted-tag over)) - (puthash (cons tag tag-state) formatted-tag notmuch-tag--format-cache))) formatted-tag)) @@ -356,7 +355,6 @@ changed (the normal case) are shown using formats from a list of strings of the form \"+TAG\" or \"-TAG\". 'query' will be a string containing the search query that determines the messages that are about to be tagged." - :type 'hook :options '(notmuch-hl-line-mode) :group 'notmuch-hooks) @@ -406,7 +404,6 @@ completions. CURRENT-TAGS may contain duplicates. PROMPT, if non-nil, is the query string to present in the minibuffer. It defaults to \"Tags\". INITIAL-INPUT, if non-nil, will be the initial input in the minibuffer." - (let* ((all-tag-list (notmuch-tag-completions)) (add-tag-list (mapcar (apply-partially 'concat "+") all-tag-list)) (remove-tag-list (mapcar (apply-partially 'concat "-") current-tags)) diff --git a/emacs/notmuch-tree.el b/emacs/notmuch-tree.el index fcfb972d..d90f2aa0 100644 --- a/emacs/notmuch-tree.el +++ b/emacs/notmuch-tree.el @@ -891,7 +891,6 @@ A message tree is another name for a single sub-thread: i.e., a message together with all its descendents." (let ((msg (car tree)) (replies (cadr tree))) - (cond ((and (< 0 depth) (not last)) (push "├" tree-status)) @@ -907,7 +906,6 @@ message together with all its descendents." (push "╰" tree-status)) ((and (eq 0 depth) (not first) (not last)) (push "├" tree-status))) - (push (concat (if replies "┬" "─") "►") tree-status) (setq msg (plist-put msg :first (and first (eq 0 depth)))) (setq msg (plist-put msg :tree-status tree-status)) @@ -915,11 +913,9 @@ message together with all its descendents." (notmuch-tree-goto-and-insert-msg msg) (pop tree-status) (pop tree-status) - (if last (push " " tree-status) (push "│" tree-status)) - (notmuch-tree-insert-thread replies (1+ depth) tree-status))) (defun notmuch-tree-insert-thread (thread depth tree-status) @@ -958,7 +954,6 @@ Pressing \\[notmuch-tree-show-message] on any line displays that message. Complete list of currently available key bindings: \\{notmuch-tree-mode-map}" - (setq notmuch-buffer-refresh-function #'notmuch-tree-refresh-view) (hl-line-mode 1) (setq buffer-read-only t @@ -1022,7 +1017,6 @@ the same as for the function notmuch-tree." ;; (such as reply) do. It is a buffer local variable so setting it ;; will not affect genuine show buffers. (setq notmuch-show-process-crypto notmuch-crypto-process-mime) - (erase-buffer) (goto-char (point-min)) (let* ((search-args (concat basic-query @@ -1079,13 +1073,10 @@ The arguments are: (if unthreaded "unthreaded-" "tree-") query "*"))))) (inhibit-read-only t)) - (switch-to-buffer buffer)) ;; Don't track undo information for this buffer (set 'buffer-undo-list t) - (notmuch-tree-worker query query-context target open-target unthreaded) - (setq truncate-lines t)) (defun notmuch-unthreaded (&optional query query-context target buffer-name open-target) diff --git a/emacs/notmuch-wash.el b/emacs/notmuch-wash.el index 3a9ffdd0..c3b24cb9 100644 --- a/emacs/notmuch-wash.el +++ b/emacs/notmuch-wash.el @@ -203,12 +203,10 @@ BEG and END are buffer locations. TYPE should a string, either \"citation\" or \"signature\". Optional PREFIX is some arbitrary text to insert before the button, probably for indentation. Note that PREFIX should not include a newline." - ;; This uses some slightly tricky conversions between strings and ;; symbols because of the way the button code works. Note that ;; replacing intern-soft with make-symbol will cause this to fail, ;; since the newly created symbol has no plist. - (let ((overlay (make-overlay beg end)) (button-type (intern-soft (concat "notmuch-wash-button-" type "-toggle-type")))) @@ -276,25 +274,20 @@ that PREFIX should not include a newline." (defun notmuch-wash-elide-blank-lines (msg depth) "Elide leading, trailing and successive blank lines." - ;; Algorithm derived from `article-strip-multiple-blank-lines' in ;; `gnus-art.el'. - ;; Make all blank lines empty. (goto-char (point-min)) (while (re-search-forward "^[[:space:]\t]+$" nil t) (replace-match "" nil t)) - ;; Replace multiple empty lines with a single empty line. (goto-char (point-min)) (while (re-search-forward "^\n\\(\n+\\)" nil t) (delete-region (match-beginning 1) (match-end 1))) - ;; Remove a leading blank line. (goto-char (point-min)) (if (looking-at "\n") (delete-region (match-beginning 0) (match-end 0))) - ;; Remove a trailing blank line. (goto-char (point-max)) (if (looking-at "\n") @@ -313,20 +306,15 @@ Perform several transformations on the message body: text, - Remove citation trailers standing alone after a block of cited text." - ;; Remove lines of repeated citation leaders with no other content. (goto-char (point-min)) (while (re-search-forward "\\(^>[> ]*\n\\)\\{2,\\}" nil t) (replace-match "\\1")) - - ;; Remove citation leaders standing alone before a block of cited - ;; text. + ;; Remove citation leaders standing alone before a block of cited text. (goto-char (point-min)) (while (re-search-forward "\\(\n\\|^[^>].*\\)\n\\(^>[> ]*\n\\)" nil t) (replace-match "\\1\n")) - - ;; Remove citation trailers standing alone after a block of cited - ;; text. + ;; Remove citation trailers standing alone after a block of cited text. (goto-char (point-min)) (while (re-search-forward "\\(^>[> ]*\n\\)\\(^$\\|^[^>].*\\)" nil t) (replace-match "\\2"))) @@ -341,7 +329,6 @@ the message lines to the minimum of the width of the window or its value. Otherwise, this function will wrap long lines in the message at the window width. When doing so, citation leaders in the wrapped text are maintained." - (let* ((coolj-wrap-follows-window-size nil) (indent (* depth notmuch-show-indent-messages-width)) (limit (if (numberp notmuch-wash-wrap-lines-length) @@ -405,7 +392,6 @@ original filename the sender had." Given that this function guesses whether a buffer includes a patch and then guesses the extent of the patch, there is scope for error." - (goto-char (point-min)) (when (re-search-forward diff-file-header-re nil t) (beginning-of-line -1) diff --git a/emacs/notmuch.el b/emacs/notmuch.el index 07a58da7..04334c76 100644 --- a/emacs/notmuch.el +++ b/emacs/notmuch.el @@ -755,7 +755,6 @@ non-authors is found, assume that all of the authors match." (visible-string formatted-authors) (invisible-string "") (padding "")) - ;; Truncate the author string to fit the specification. (if (> (length formatted-authors) (length formatted-sample)) @@ -782,7 +781,6 @@ non-authors is found, assume that all of the authors match." (length visible-string) (length "...")) ? )))) - ;; Use different faces to show matching and non-matching authors. (if (string-match "\\(.*\\)|\\(.*\\)" visible-string) ;; The visible string contains both matching and @@ -798,14 +796,12 @@ non-authors is found, assume that all of the authors match." ;; The invisible string may contain both matching and ;; non-matching authors. invisible-string (notmuch-search-author-propertize invisible-string))) - ;; If there is any invisible text, add it as a tooltip to the ;; visible text. (when (not (string= invisible-string "")) (setq visible-string (propertize visible-string 'help-echo (concat "..." invisible-string)))) - ;; Insert the visible and, if present, invisible author strings. (insert visible-string) (when (not (string= invisible-string "")) @@ -831,11 +827,9 @@ non-authors is found, assume that all of the authors match." (insert (propertize (format format-string (notmuch-sanitize (plist-get result :subject))) 'face 'notmuch-search-subject))) - ((string-equal field "authors") (notmuch-search-insert-authors format-string (notmuch-sanitize (plist-get result :authors)))) - ((string-equal field "tags") (let ((tags (plist-get result :tags)) (orig-tags (plist-get result :orig-tags))) @@ -1118,7 +1112,6 @@ current search results AND that are tagged with the given tag." If the current buffer is the only notmuch buffer, bury it. If no notmuch buffers exist, run `notmuch'." (interactive) - (let (start first) ;; If the current buffer is a notmuch buffer, remember it and then ;; bury it. From caaa10876046096320f4e69053542eabe17e7e55 Mon Sep 17 00:00:00 2001 From: Jonas Bernoulli Date: Sat, 8 Aug 2020 13:49:38 +0200 Subject: [PATCH 338/427] emacs: Fix indentation --- emacs/coolj.el | 16 ++--- emacs/notmuch-address.el | 18 +++--- emacs/notmuch-compat.el | 86 ++++++++++++------------- emacs/notmuch-draft.el | 18 +++--- emacs/notmuch-hello.el | 24 +++---- emacs/notmuch-lib.el | 56 ++++++++--------- emacs/notmuch-maildir-fcc.el | 58 ++++++++--------- emacs/notmuch-mua.el | 42 +++++++------ emacs/notmuch-query.el | 6 +- emacs/notmuch-show.el | 111 +++++++++++++++++---------------- emacs/notmuch-tag.el | 4 +- emacs/notmuch-tree.el | 102 +++++++++++++++--------------- emacs/notmuch-wash.el | 2 +- emacs/notmuch.el | 76 +++++++++++----------- test/emacs-address-cleaning.el | 2 +- 15 files changed, 312 insertions(+), 309 deletions(-) diff --git a/emacs/coolj.el b/emacs/coolj.el index 5d311170..961db606 100644 --- a/emacs/coolj.el +++ b/emacs/coolj.el @@ -108,11 +108,11 @@ If the line should not be broken, return nil; point remains on the line." (move-to-column fill-column) (if (and (re-search-forward "[^ ]" (line-end-position) 1) - (> (current-column) fill-column)) + (> (current-column) fill-column)) ;; This line is too long. Can we break it? (or (coolj-find-break-backward prefix) - (progn (move-to-column fill-column) - (coolj-find-break-forward))))) + (progn (move-to-column fill-column) + (coolj-find-break-forward))))) (defun coolj-find-break-backward (prefix) "Move point backward to the first available breakpoint and return t. @@ -135,12 +135,12 @@ If no breakpoint is found, return nil." If no break point is found, return nil." (and (search-forward " " (line-end-position) 1) (progn (skip-chars-forward " " (line-end-position)) - (null (eolp))) + (null (eolp))) (if (and fill-nobreak-predicate - (run-hook-with-args-until-success - 'fill-nobreak-predicate)) - (coolj-find-break-forward) - t))) + (run-hook-with-args-until-success + 'fill-nobreak-predicate)) + (coolj-find-break-forward) + t))) (provide 'coolj) diff --git a/emacs/notmuch-address.el b/emacs/notmuch-address.el index ca4da3f3..09dda247 100644 --- a/emacs/notmuch-address.el +++ b/emacs/notmuch-address.el @@ -153,14 +153,14 @@ matching `notmuch-address-completion-headers-regexp'." (defun notmuch-address-setup () (let* ((setup-company (and notmuch-address-use-company - (require 'company nil t))) + (require 'company nil t))) (pair (cons notmuch-address-completion-headers-regexp - #'notmuch-address-expand-name))) - (when setup-company - (notmuch-company-setup)) - (unless (member pair message-completion-alist) - (setq message-completion-alist - (push pair message-completion-alist))))) + #'notmuch-address-expand-name))) + (when setup-company + (notmuch-company-setup)) + (unless (member pair message-completion-alist) + (setq message-completion-alist + (push pair message-completion-alist))))) (defun notmuch-address-toggle-internal-completion () "Toggle use of internal completion for current buffer. @@ -323,7 +323,7 @@ execution, CALLBACK is called when harvesting finishes." ,query))) (if synchronous (mapc #'notmuch-address-harvest-addr - (apply 'notmuch-call-notmuch-sexp args)) + (apply 'notmuch-call-notmuch-sexp args)) ;; Asynchronous (let* ((current-proc (if addr-prefix (car notmuch-address-harvest-procs) @@ -390,7 +390,7 @@ to be a saved address hash." (defun notmuch-address--save-address-hash () (when notmuch-address-save-filename (if (or (not (file-exists-p notmuch-address-save-filename)) - ;; The file exists, check it is a file we saved + ;; The file exists, check it is a file we saved (notmuch-address--get-address-hash)) (with-temp-file notmuch-address-save-filename (let ((save-plist diff --git a/emacs/notmuch-compat.el b/emacs/notmuch-compat.el index 2cedd39d..388ef70f 100644 --- a/emacs/notmuch-compat.el +++ b/emacs/notmuch-compat.el @@ -35,7 +35,7 @@ Backport of setq-local for emacs without setq-local (pre 24.3)." (if (fboundp 'read-char-choice) (defalias 'notmuch-read-char-choice 'read-char-choice) (defun notmuch-read-char-choice (prompt chars &optional inhibit-keyboard-quit) - "Read and return one of CHARS, prompting for PROMPT. + "Read and return one of CHARS, prompting for PROMPT. Any input that is not one of CHARS is ignored. If optional argument INHIBIT-KEYBOARD-QUIT is non-nil, ignore @@ -44,49 +44,49 @@ keyboard-quit events while waiting for a valid input. This is an exact copy of this function from emacs 24 for use on emacs 23, except with the one emacs 24 only function it calls inlined." - (unless (consp chars) - (error "Called `read-char-choice' without valid char choices")) - (let (char done show-help (helpbuf " *Char Help*")) - (let ((cursor-in-echo-area t) - (executing-kbd-macro executing-kbd-macro) - (esc-flag nil)) - (save-window-excursion ; in case we call help-form-show - (while (not done) - (unless (get-text-property 0 'face prompt) - (setq prompt (propertize prompt 'face 'minibuffer-prompt))) - (setq char (let ((inhibit-quit inhibit-keyboard-quit)) - (read-key prompt))) - (and show-help (buffer-live-p (get-buffer helpbuf)) - (kill-buffer helpbuf)) - (cond - ((not (numberp char))) - ;; If caller has set help-form, that's enough. - ;; They don't explicitly have to add help-char to chars. - ((and help-form - (eq char help-char) - (setq show-help t) - ;; This is an inlined copy of help-form-show as that - ;; was introduced in emacs 24 too. - (let ((msg (eval help-form))) - (if (stringp msg) - (with-output-to-temp-buffer " *Char Help*" - (princ msg)))))) - ((memq char chars) - (setq done t)) - ((and executing-kbd-macro (= char -1)) - ;; read-event returns -1 if we are in a kbd macro and - ;; there are no more events in the macro. Attempt to - ;; get an event interactively. - (setq executing-kbd-macro nil)) - ((not inhibit-keyboard-quit) + (unless (consp chars) + (error "Called `read-char-choice' without valid char choices")) + (let (char done show-help (helpbuf " *Char Help*")) + (let ((cursor-in-echo-area t) + (executing-kbd-macro executing-kbd-macro) + (esc-flag nil)) + (save-window-excursion ; in case we call help-form-show + (while (not done) + (unless (get-text-property 0 'face prompt) + (setq prompt (propertize prompt 'face 'minibuffer-prompt))) + (setq char (let ((inhibit-quit inhibit-keyboard-quit)) + (read-key prompt))) + (and show-help (buffer-live-p (get-buffer helpbuf)) + (kill-buffer helpbuf)) (cond - ((and (null esc-flag) (eq char ?\e)) - (setq esc-flag t)) - ((memq char '(?\C-g ?\e)) - (keyboard-quit)))))))) - ;; Display the question with the answer. But without cursor-in-echo-area. - (message "%s%s" prompt (char-to-string char)) - char))) + ((not (numberp char))) + ;; If caller has set help-form, that's enough. + ;; They don't explicitly have to add help-char to chars. + ((and help-form + (eq char help-char) + (setq show-help t) + ;; This is an inlined copy of help-form-show as that + ;; was introduced in emacs 24 too. + (let ((msg (eval help-form))) + (if (stringp msg) + (with-output-to-temp-buffer " *Char Help*" + (princ msg)))))) + ((memq char chars) + (setq done t)) + ((and executing-kbd-macro (= char -1)) + ;; read-event returns -1 if we are in a kbd macro and + ;; there are no more events in the macro. Attempt to + ;; get an event interactively. + (setq executing-kbd-macro nil)) + ((not inhibit-keyboard-quit) + (cond + ((and (null esc-flag) (eq char ?\e)) + (setq esc-flag t)) + ((memq char '(?\C-g ?\e)) + (keyboard-quit)))))))) + ;; Display the question with the answer. But without cursor-in-echo-area. + (message "%s%s" prompt (char-to-string char)) + char))) ;; End of compatibility functions diff --git a/emacs/notmuch-draft.el b/emacs/notmuch-draft.el index c3cb619f..ea995379 100644 --- a/emacs/notmuch-draft.el +++ b/emacs/notmuch-draft.el @@ -153,17 +153,17 @@ Used when a new version is saved, or the message is sent." `notmuch-draft-save-plaintext' controls the behaviour." (cl-case notmuch-draft-save-plaintext - ((ask) - (unless (yes-or-no-p - "(Customize `notmuch-draft-save-plaintext' to avoid this warning) + ((ask) + (unless (yes-or-no-p + "(Customize `notmuch-draft-save-plaintext' to avoid this warning) This message contains mml tags that suggest it is intended to be encrypted. Really save and index an unencrypted copy? ") - (error "Save aborted"))) - ((nil) - (error "Refusing to save draft with encryption tags (see `%s')" - 'notmuch-draft-save-plaintext)) - ((t) - (ignore)))) + (error "Save aborted"))) + ((nil) + (error "Refusing to save draft with encryption tags (see `%s')" + 'notmuch-draft-save-plaintext)) + ((t) + (ignore)))) (defun notmuch-draft--make-message-id () ;; message-make-message-id gives the id inside a "<" ">" pair, diff --git a/emacs/notmuch-hello.el b/emacs/notmuch-hello.el index e71e55f3..89e03c36 100644 --- a/emacs/notmuch-hello.el +++ b/emacs/notmuch-hello.el @@ -147,9 +147,9 @@ a plist. Supported properties are Other accepted forms are a cons cell of the form (NAME . QUERY) or a list of the form (NAME QUERY COUNT-QUERY)." -;; The saved-search format is also used by the all-tags notmuch-hello -;; section. This section generates its own saved-search list in one of -;; the latter two forms. + ;; The saved-search format is also used by the all-tags notmuch-hello + ;; section. This section generates its own saved-search list in one of + ;; the latter two forms. :get 'notmuch-hello--saved-searches-to-plist :type '(repeat notmuch-saved-search-plist) :tag "List of Saved Searches" @@ -382,10 +382,10 @@ afterwards.") (setq n (/ n 1000))) (setq result (or result '(0))) (apply #'concat - (number-to-string (car result)) - (mapcar (lambda (elem) - (format "%s%03d" notmuch-hello-thousands-separator elem)) - (cdr result))))) + (number-to-string (car result)) + (mapcar (lambda (elem) + (format "%s%03d" notmuch-hello-thousands-separator elem)) + (cdr result))))) (defun notmuch-hello-trim (search) "Trim whitespace." @@ -540,7 +540,7 @@ options will be handled as specified for (notmuch-hello-filtered-query count-query (or (plist-get options :filter-count) (plist-get options :filter)))) - "\n"))) + "\n"))) (unless (= (call-process-region (point-min) (point-max) notmuch-command t t nil "count" "--batch") 0) (notmuch-logged-error @@ -690,7 +690,7 @@ with `notmuch-hello-query-counts'." (fset 'notmuch-hello-mode-map notmuch-hello-mode-map) (define-derived-mode notmuch-hello-mode fundamental-mode "notmuch-hello" - "Major mode for convenient notmuch navigation. This is your entry portal into notmuch. + "Major mode for convenient notmuch navigation. This is your entry portal into notmuch. Saved searches are \"bookmarks\" for arbitrary queries. Hit RET or click on a saved search to view matching threads. Edit saved @@ -720,9 +720,9 @@ The screen may be customized via `\\[customize]'. Complete list of currently available key bindings: \\{notmuch-hello-mode-map}" - (setq notmuch-buffer-refresh-function #'notmuch-hello-update) - ;;(setq buffer-read-only t) -) + (setq notmuch-buffer-refresh-function #'notmuch-hello-update) + ;;(setq buffer-read-only t) + ) (defun notmuch-hello-generate-tag-alist (&optional hide-tags) "Return an alist from tags to queries to display in the all-tags section." diff --git a/emacs/notmuch-lib.el b/emacs/notmuch-lib.el index 615207ed..7994d5ad 100644 --- a/emacs/notmuch-lib.el +++ b/emacs/notmuch-lib.el @@ -211,7 +211,7 @@ Otherwise the output will be returned." (unless (notmuch-cli-sane-p) (notmuch-logged-error "notmuch cli seems misconfigured or unconfigured." -"Perhaps you haven't run \"notmuch setup\" yet? Try running this + "Perhaps you haven't run \"notmuch setup\" yet? Try running this on the command line, and then retry your notmuch command"))) (defun notmuch-cli-version () @@ -321,7 +321,7 @@ It does not prepend if ACTUAL-KEY is already listed in TAIL." (and (functionp binding) (notmuch-documentation-first-line binding)))) tail))) - tail) + tail) (defun notmuch-describe-remaps (remap-keymap ua-keys base-keymap prefix tail) ;; Remappings are represented as a binding whose first "event" is @@ -413,8 +413,8 @@ of its command symbol." "Show help for a subkeymap." (interactive) (let* ((key (this-command-keys-vector)) - (prefix (make-vector (1- (length key)) nil)) - (i 0)) + (prefix (make-vector (1- (length key)) nil)) + (i 0)) (while (< i (length prefix)) (aset prefix i (aref key i)) (setq i (1+ i))) @@ -527,7 +527,7 @@ This replaces spaces, percents, and double quotes in STR with (let (out) (while list (when (funcall predicate (car list)) - (push (car list) out)) + (push (car list) out)) (setq list (cdr list))) (nreverse out))) @@ -835,24 +835,24 @@ You may need to restart Emacs or upgrade your notmuch package.")) command " ")) (extra (concat "command: " command-string "\n" - (if (integerp exit-status) - (format "exit status: %s\n" exit-status) - (format "exit signal: %s\n" exit-status)) - (when err - (concat "stderr:\n" err)) - (when output - (concat "stdout:\n" output))))) - (if err - ;; We have an error message straight from the CLI. - (notmuch-logged-error - (replace-regexp-in-string "[ \n\r\t\f]*\\'" "" err) extra) - ;; We only have combined output from the CLI; don't inundate - ;; the user with it. Mimic `process-lines'. - (notmuch-logged-error (format "%s exited with status %s" - (car command) exit-status) - extra)) - ;; `notmuch-logged-error' does not return. - )))) + (if (integerp exit-status) + (format "exit status: %s\n" exit-status) + (format "exit signal: %s\n" exit-status)) + (when err + (concat "stderr:\n" err)) + (when output + (concat "stdout:\n" output))))) + (if err + ;; We have an error message straight from the CLI. + (notmuch-logged-error + (replace-regexp-in-string "[ \n\r\t\f]*\\'" "" err) extra) + ;; We only have combined output from the CLI; don't inundate + ;; the user with it. Mimic `process-lines'. + (notmuch-logged-error (format "%s exited with status %s" + (car command) exit-status) + extra)) + ;; `notmuch-logged-error' does not return. + )))) (defun notmuch-call-notmuch--helper (destination args) "Helper for synchronous notmuch invocation commands. @@ -925,9 +925,9 @@ invoke `set-process-sentinel' directly on the returned process, as that will interfere with the handling of stderr and the exit status." (let (err-file err-buffer proc err-proc - ;; Find notmuch using Emacs' `exec-path' - (command (or (executable-find notmuch-command) - (error "Command not found: %s" notmuch-command)))) + ;; Find notmuch using Emacs' `exec-path' + (command (or (executable-find notmuch-command) + (error "Command not found: %s" notmuch-command)))) (if (fboundp 'make-process) (progn (setq err-buffer (generate-new-buffer " *notmuch-stderr*")) @@ -1030,8 +1030,8 @@ region if the region is active, or both `point' otherwise." (list (point) (point)))) (define-obsolete-function-alias - 'notmuch-search-interactive-region - 'notmuch-interactive-region + 'notmuch-search-interactive-region + 'notmuch-interactive-region "notmuch 0.29") (provide 'notmuch-lib) diff --git a/emacs/notmuch-maildir-fcc.el b/emacs/notmuch-maildir-fcc.el index 66f32fd4..7d001b2d 100644 --- a/emacs/notmuch-maildir-fcc.el +++ b/emacs/notmuch-maildir-fcc.el @@ -31,7 +31,7 @@ (defvar notmuch-maildir-fcc-count 0) (defcustom notmuch-fcc-dirs "sent" - "Determines the Fcc Header which says where to save outgoing mail. + "Determines the Fcc Header which says where to save outgoing mail. Three types of values are permitted: @@ -69,13 +69,13 @@ database.path option in the notmuch configuration file). In all cases you will be prompted to create the folder or directory if it does not exist yet when sending a mail." - :type '(choice - (const :tag "No FCC header" nil) - (string :tag "A single folder") - (repeat :tag "A folder based on the From header" - (cons regexp (string :tag "Folder")))) - :require 'notmuch-fcc-initialization - :group 'notmuch-send) + :type '(choice + (const :tag "No FCC header" nil) + (string :tag "A single folder") + (repeat :tag "A folder based on the From header" + (cons regexp (string :tag "Folder")))) + :require 'notmuch-fcc-initialization + :group 'notmuch-send) (defcustom notmuch-maildir-use-notmuch-insert 't "Should fcc use notmuch insert instead of simple fcc." @@ -267,16 +267,16 @@ If CREATE is non-nil then create the folder if necessary." t)) (defun notmuch-maildir-fcc-make-uniq-maildir-id () - (let* ((ftime (float-time)) - (microseconds (mod (* 1000000 ftime) 1000000)) - (hostname (notmuch-maildir-fcc-host-fixer (system-name)))) - (setq notmuch-maildir-fcc-count (+ notmuch-maildir-fcc-count 1)) - (format "%d.%d_%d_%d.%s" - ftime - (emacs-pid) - microseconds - notmuch-maildir-fcc-count - hostname))) + (let* ((ftime (float-time)) + (microseconds (mod (* 1000000 ftime) 1000000)) + (hostname (notmuch-maildir-fcc-host-fixer (system-name)))) + (setq notmuch-maildir-fcc-count (+ notmuch-maildir-fcc-count 1)) + (format "%d.%d_%d_%d.%s" + ftime + (emacs-pid) + microseconds + notmuch-maildir-fcc-count + hostname))) (defun notmuch-maildir-fcc-dir-is-maildir-p (dir) (and (file-exists-p (concat dir "/cur/")) @@ -328,17 +328,17 @@ if needed." ;; fix it in some way. (let* ((prompt (format "Fcc %s is not a maildir: \ \(r)etry, (c)reate folder, (i)gnore, or (e)dit the header? " fcc-header)) - (response (notmuch-read-char-choice prompt '(?r ?c ?i ?e)))) - (cl-case response - (?r (notmuch-maildir-fcc-file-fcc fcc-header)) - (?c (if (file-writable-p fcc-header) - (notmuch-maildir-fcc-create-maildir fcc-header) - (message "No permission to create %s." fcc-header) - (sit-for 2)) - (notmuch-maildir-fcc-file-fcc fcc-header)) - (?i 't) - (?e (notmuch-maildir-fcc-file-fcc - (read-from-minibuffer "Fcc header: " fcc-header))))))) + (response (notmuch-read-char-choice prompt '(?r ?c ?i ?e)))) + (cl-case response + (?r (notmuch-maildir-fcc-file-fcc fcc-header)) + (?c (if (file-writable-p fcc-header) + (notmuch-maildir-fcc-create-maildir fcc-header) + (message "No permission to create %s." fcc-header) + (sit-for 2)) + (notmuch-maildir-fcc-file-fcc fcc-header)) + (?i 't) + (?e (notmuch-maildir-fcc-file-fcc + (read-from-minibuffer "Fcc header: " fcc-header))))))) (defun notmuch-maildir-fcc-write-buffer-to-maildir (destdir &optional mark-seen) "Writes the current buffer to maildir destdir. If mark-seen is diff --git a/emacs/notmuch-mua.el b/emacs/notmuch-mua.el index 3bc06b4b..7f80224f 100644 --- a/emacs/notmuch-mua.el +++ b/emacs/notmuch-mua.el @@ -58,7 +58,7 @@ window/frame that will be destroyed when the buffer is killed. You may want to customize `message-kill-buffer-on-exit' accordingly." (when (< emacs-major-version 24) - " Due to a known bug in Emacs 23, you should not set + " Due to a known bug in Emacs 23, you should not set this to `new-window' if `message-kill-buffer-on-exit' is disabled: this would result in an incorrect behavior.")) :group 'notmuch-send @@ -106,13 +106,13 @@ Note that these functions use `mail-citation-hook' if that is non-nil." This function specifies which parts of a mime message with multiple parts get a header." :type '(radio (const :tag "No part headers" - notmuch-show-reply-insert-header-p-never) + notmuch-show-reply-insert-header-p-never) (const :tag "All except multipart/* and hidden parts" - notmuch-show-reply-insert-header-p-trimmed) + notmuch-show-reply-insert-header-p-trimmed) (const :tag "Only for included text parts" - notmuch-show-reply-insert-header-p-minimal) + notmuch-show-reply-insert-header-p-minimal) (const :tag "Exactly as in show view" - notmuch-show-insert-header-p) + notmuch-show-insert-header-p) (function :tag "Other")) :group 'notmuch-reply) @@ -472,10 +472,12 @@ the From: address." (let* ((other-headers (when (or prompt-for-sender notmuch-always-prompt-for-sender) (list (cons 'From (notmuch-mua-prompt-for-sender))))) - forward-subject ;; Comes from the first message and is - ;; applied later. - forward-references ;; List of accumulated message-references of forwarded messages - forward-queries) ;; List of corresponding message-query + ;; Comes from the first message and is applied later. + forward-subject + ;; List of accumulated message-references of forwarded messages. + forward-references + ;; List of corresponding message-query. + forward-queries) ;; Generate the template for the outgoing message. (notmuch-mua-mail nil "" other-headers nil (notmuch-mua-get-switch-function)) (save-excursion @@ -529,17 +531,17 @@ the From: address." If PROMPT-FOR-SENDER is non-nil, the user will be prompted for the From: address first. If REPLY-ALL is non-nil, the message will be addressed to all recipients of the source message." -;; In current emacs (24.3) select-active-regions is set to t by -;; default. The reply insertion code sets the region to the quoted -;; message to make it easy to delete (kill-region or C-w). These two -;; things combine to put the quoted message in the primary selection. -;; -;; This is not what the user wanted and is a privacy risk (accidental -;; pasting of the quoted message). We can avoid some of the problems -;; by let-binding select-active-regions to nil. This fixes if the -;; primary selection was previously in a non-emacs window but not if -;; it was in an emacs window. To avoid the problem in the latter case -;; we deactivate mark. + ;; In current emacs (24.3) select-active-regions is set to t by + ;; default. The reply insertion code sets the region to the quoted + ;; message to make it easy to delete (kill-region or C-w). These two + ;; things combine to put the quoted message in the primary selection. + ;; + ;; This is not what the user wanted and is a privacy risk (accidental + ;; pasting of the quoted message). We can avoid some of the problems + ;; by let-binding select-active-regions to nil. This fixes if the + ;; primary selection was previously in a non-emacs window but not if + ;; it was in an emacs window. To avoid the problem in the latter case + ;; we deactivate mark. (let ((sender (when prompt-for-sender (notmuch-mua-prompt-for-sender))) diff --git a/emacs/notmuch-query.el b/emacs/notmuch-query.el index 3a835a00..1db9d7ca 100644 --- a/emacs/notmuch-query.el +++ b/emacs/notmuch-query.el @@ -42,9 +42,9 @@ is a possibly empty forest of replies." "Private function to do the actual mapping and flattening." (apply 'append (mapcar - (lambda (tree) - (funcall mapper function tree)) - seq))) + (lambda (tree) + (funcall mapper function tree)) + seq))) (defun notmuch-query-map-threads (fn threads) "Apply FN to every thread in THREADS. Flatten results to a list. diff --git a/emacs/notmuch-show.el b/emacs/notmuch-show.el index 4520d479..51f2a7d5 100644 --- a/emacs/notmuch-show.el +++ b/emacs/notmuch-show.el @@ -272,7 +272,7 @@ position of the message in the thread." `(save-excursion (let ((id (notmuch-show-get-message-id))) (let ((buf (generate-new-buffer (concat "*notmuch-msg-" id "*")))) - (with-current-buffer buf + (with-current-buffer buf (let ((coding-system-for-read 'no-conversion)) (call-process notmuch-command nil t nil "show" "--format=raw" id)) ,@body) @@ -303,7 +303,7 @@ position of the message in the thread." ("multipart/alternative" ignore identity) ("multipart/mixed" ignore identity) ("multipart/related" ignore identity) - ))) + ))) (mm-display-parts (mm-dissect-buffer))))) (defun notmuch-show-save-attachments () @@ -406,43 +406,44 @@ operation on the contents of the current buffer." cell of (AUTHOR_EMAIL AUTHOR_NAME). Return (ADDRESS nil) if parsing fails." (condition-case nil - (let (p-name p-address) - ;; It would be convenient to use `mail-header-parse-address', - ;; but that expects un-decoded mailbox parts, whereas our - ;; mailbox parts are already decoded (and hence may contain - ;; UTF-8). Given that notmuch should handle most of the awkward - ;; cases, some simple string deconstruction should be sufficient - ;; here. - (cond - ;; "User " style. - ((string-match "\\(.*\\) <\\(.*\\)>" address) - (setq p-name (match-string 1 address) - p-address (match-string 2 address))) - ;; "" style. - ((string-match "<\\(.*\\)>" address) - (setq p-address (match-string 1 address))) - ;; Everything else. - (t - (setq p-address address))) - (when p-name - ;; Remove elements of the mailbox part that are not relevant for - ;; display, even if they are required during transport: - ;; - ;; Backslashes. - (setq p-name (replace-regexp-in-string "\\\\" "" p-name)) - ;; Outer single and double quotes, which might be nested. - (cl-loop with start-of-loop - do (setq start-of-loop p-name) - when (string-match "^\"\\(.*\\)\"$" p-name) - do (setq p-name (match-string 1 p-name)) - when (string-match "^'\\(.*\\)'$" p-name) - do (setq p-name (match-string 1 p-name)) - until (string= start-of-loop p-name))) - ;; If the address is 'foo@bar.com ' then show just - ;; 'foo@bar.com'. - (when (string= p-name p-address) - (setq p-name nil)) - (cons p-address p-name)) + (let (p-name p-address) + ;; It would be convenient to use `mail-header-parse-address', + ;; but that expects un-decoded mailbox parts, whereas our + ;; mailbox parts are already decoded (and hence may contain + ;; UTF-8). Given that notmuch should handle most of the awkward + ;; cases, some simple string deconstruction should be sufficient + ;; here. + (cond + ;; "User " style. + ((string-match "\\(.*\\) <\\(.*\\)>" address) + (setq p-name (match-string 1 address) + p-address (match-string 2 address))) + + ;; "" style. + ((string-match "<\\(.*\\)>" address) + (setq p-address (match-string 1 address))) + ;; Everything else. + (t + (setq p-address address))) + (when p-name + ;; Remove elements of the mailbox part that are not relevant for + ;; display, even if they are required during transport: + ;; + ;; Backslashes. + (setq p-name (replace-regexp-in-string "\\\\" "" p-name)) + ;; Outer single and double quotes, which might be nested. + (cl-loop with start-of-loop + do (setq start-of-loop p-name) + when (string-match "^\"\\(.*\\)\"$" p-name) + do (setq p-name (match-string 1 p-name)) + when (string-match "^'\\(.*\\)'$" p-name) + do (setq p-name (match-string 1 p-name)) + until (string= start-of-loop p-name))) + ;; If the address is 'foo@bar.com ' then show just + ;; 'foo@bar.com'. + (when (string= p-name p-address) + (setq p-name nil)) + (cons p-address p-name)) (error (cons address nil)))) (defun notmuch-show-clean-address (address) @@ -607,9 +608,9 @@ will return nil if the CID is unknown or cannot be retrieved." "Instruct w3m how to retrieve content from a \"related\" part of a message." (interactive) (if (boundp 'w3m-cid-retrieve-function-alist) - (unless (assq 'notmuch-show-mode w3m-cid-retrieve-function-alist) - (push (cons 'notmuch-show-mode #'notmuch-show--cid-w3m-retrieve) - w3m-cid-retrieve-function-alist))) + (unless (assq 'notmuch-show-mode w3m-cid-retrieve-function-alist) + (push (cons 'notmuch-show-mode #'notmuch-show--cid-w3m-retrieve) + w3m-cid-retrieve-function-alist))) (setq mm-html-inhibit-images nil)) (defvar w3m-current-buffer) ;; From `w3m.el'. @@ -639,8 +640,8 @@ will return nil if the CID is unknown or cannot be retrieved." ;; should be chosen if there are more than one that match? (mapc (lambda (inner-part) (let* ((inner-type (plist-get inner-part :content-type)) - (hide (not (or notmuch-show-all-multipart/alternative-parts - (string= chosen-type inner-type))))) + (hide (not (or notmuch-show-all-multipart/alternative-parts + (string= chosen-type inner-type))))) (notmuch-show-insert-bodypart msg inner-part depth hide))) inner-parts) @@ -1008,7 +1009,7 @@ is t, hide the part initially and show the button." ;; Store the computed mime-type for later use (e.g. by attachment handlers). (plist-put part :computed-type mime-type) (if show-part - (notmuch-show-insert-bodypart-internal msg part mime-type nth depth button) + (notmuch-show-insert-bodypart-internal msg part mime-type nth depth button) (when button (button-put button :notmuch-lazy-part (list msg part mime-type nth depth button)))) @@ -1521,9 +1522,9 @@ All currently available key bindings: (setq buffer-read-only t truncate-lines t) (setq imenu-prev-index-position-function - #'notmuch-show-imenu-prev-index-position-function) + #'notmuch-show-imenu-prev-index-position-function) (setq imenu-extract-index-name-function - #'notmuch-show-imenu-extract-index-name-function)) + #'notmuch-show-imenu-extract-index-name-function)) (defun notmuch-tree-from-show-current-query () "Call notmuch tree with the current query." @@ -1746,8 +1747,8 @@ We only mark it read once: if it is changed back then that is a user decision and we should not override it." (when (and (notmuch-show-message-visible-p) (not (notmuch-show-get-prop :seen))) - (notmuch-show-mark-read) - (notmuch-show-set-prop :seen t))) + (notmuch-show-mark-read) + (notmuch-show-set-prop :seen t))) (defvar notmuch-show--seen-has-errored nil) (make-variable-buffer-local 'notmuch-show--seen-has-errored) @@ -1866,9 +1867,9 @@ any effects from previous calls to (let ((start-of-message (notmuch-show-message-top)) (start-of-window (window-start))) (cond - ;; Either this message is properly aligned with the start of the - ;; window or the start of this message is not visible on the - ;; screen - scroll. + ;; Either this message is properly aligned with the start of the + ;; window or the start of this message is not visible on the + ;; screen - scroll. ((or (= start-of-message start-of-window) (< start-of-message start-of-window)) (scroll-down) @@ -2512,9 +2513,9 @@ beginning of the line." message." `(save-excursion (save-restriction - (let ((extent (notmuch-show-message-extent))) - (narrow-to-region (car extent) (cdr extent)) - ,@body)))) + (let ((extent (notmuch-show-message-extent))) + (narrow-to-region (car extent) (cdr extent)) + ,@body)))) (defun notmuch-show--gather-urls () "Gather any URLs in the current message." diff --git a/emacs/notmuch-tag.el b/emacs/notmuch-tag.el index 85c80f54..2fcf5a9e 100644 --- a/emacs/notmuch-tag.el +++ b/emacs/notmuch-tag.el @@ -236,7 +236,7 @@ DATA is the content of an SVG picture (e.g., as returned by (defun notmuch-tag-star-icon () "Return SVG data representing a star icon. This can be used with `notmuch-tag-format-image-data'." -" + " Date: Sat, 8 Aug 2020 13:49:39 +0200 Subject: [PATCH 339/427] emacs: Closing parenthesis go on the same line --- emacs/notmuch-lib.el | 6 ++---- emacs/notmuch-show.el | 19 ++++++++----------- emacs/notmuch-tree.el | 4 ++-- emacs/notmuch.el | 6 ++---- 4 files changed, 14 insertions(+), 21 deletions(-) diff --git a/emacs/notmuch-lib.el b/emacs/notmuch-lib.el index 7994d5ad..f8958a91 100644 --- a/emacs/notmuch-lib.el +++ b/emacs/notmuch-lib.el @@ -555,13 +555,11 @@ This replaces spaces, percents, and double quotes in STR with (string= (downcase t1) (downcase t2))))) (defvar notmuch-multipart/alternative-discouraged - '( - ;; Avoid HTML parts. + '(;; Avoid HTML parts. "text/html" ;; multipart/related usually contain a text/html part and some ;; associated graphics. - "multipart/related" - )) + "multipart/related")) (defun notmuch-multipart/alternative-determine-discouraged (msg) "Return the discouraged alternatives for the specified message." diff --git a/emacs/notmuch-show.el b/emacs/notmuch-show.el index 51f2a7d5..d088c80b 100644 --- a/emacs/notmuch-show.el +++ b/emacs/notmuch-show.el @@ -297,13 +297,12 @@ position of the message in the thread." ;; ;; Any MIME part not explicitly mentioned here will be handled by an ;; external viewer as configured in the various mailcap files. - (let ((mm-inline-media-tests '( - ("text/.*" ignore identity) - ("application/pgp-signature" ignore identity) - ("multipart/alternative" ignore identity) - ("multipart/mixed" ignore identity) - ("multipart/related" ignore identity) - ))) + (let ((mm-inline-media-tests + '(("text/.*" ignore identity) + ("application/pgp-signature" ignore identity) + ("multipart/alternative" ignore identity) + ("multipart/mixed" ignore identity) + ("multipart/related" ignore identity)))) (mm-display-parts (mm-dissect-buffer))))) (defun notmuch-show-save-attachments () @@ -1791,10 +1790,8 @@ Reshows the current thread with matches defined by the new query-string." (if (notmuch-show-message-visible-p) (setq message-ids (append message-ids (list (notmuch-show-get-message-id))))) - (setq done (not (notmuch-show-goto-message-next))) - ) - message-ids - ))) + (setq done (not (notmuch-show-goto-message-next)))) + message-ids))) ;; Commands typically bound to keys. diff --git a/emacs/notmuch-tree.el b/emacs/notmuch-tree.el index 6ac0c62d..b1bb40b1 100644 --- a/emacs/notmuch-tree.el +++ b/emacs/notmuch-tree.el @@ -1020,8 +1020,8 @@ the same as for the function notmuch-tree." (erase-buffer) (goto-char (point-min)) (let* ((search-args (concat basic-query - (if query-context (concat " and (" query-context ")")) - )) + (if query-context + (concat " and (" query-context ")")))) (message-arg (if unthreaded "--unthreaded" "--entire-thread"))) (if (equal (car (process-lines notmuch-command "count" search-args)) "0") (setq search-args basic-query)) diff --git a/emacs/notmuch.el b/emacs/notmuch.el index 2e84cd34..d6e0a9d5 100644 --- a/emacs/notmuch.el +++ b/emacs/notmuch.el @@ -910,8 +910,7 @@ See `notmuch-tag' for information on the format of TAG-CHANGES." query) "*")) (t - (concat "*notmuch-search-" query "*")) - ))) + (concat "*notmuch-search-" query "*"))))) (defun notmuch-read-query (prompt) "Read a notmuch-query from the minibuffer with completion. @@ -1003,8 +1002,7 @@ the configured default sort order." (let ((proc (get-buffer-process (current-buffer))) (inhibit-read-only t)) (if proc - (error "notmuch search process already running for query `%s'" query) - ) + (error "notmuch search process already running for query `%s'" query)) (erase-buffer) (goto-char (point-min)) (save-excursion From 18d289c86309ae796e6f027c289b17ccdd38f220 Mon Sep 17 00:00:00 2001 From: Jonas Bernoulli Date: Sat, 8 Aug 2020 13:49:40 +0200 Subject: [PATCH 340/427] emacs: Only set one variable per setq form It's a bit weird to avoid having to write the "(setq ... )" more than once, just because we can. In a language that uses '=' for the same purpose we also happily use that once per assignment. While there are no benefit to using just one 'setq' there are some drawbacks. It is not always clear on first what is a key and what a value and as a result it is easy to make a mistake. Also it becomes harder to comment out just one assignment. --- emacs/notmuch-address.el | 6 +++--- emacs/notmuch-crypto.el | 23 ++++++++++++----------- emacs/notmuch-lib.el | 8 ++++---- emacs/notmuch-show.el | 26 ++++++++++++++------------ emacs/notmuch-tree.el | 4 ++-- emacs/notmuch.el | 35 ++++++++++++++++++----------------- 6 files changed, 53 insertions(+), 49 deletions(-) diff --git a/emacs/notmuch-address.el b/emacs/notmuch-address.el index 09dda247..4db7096c 100644 --- a/emacs/notmuch-address.el +++ b/emacs/notmuch-address.el @@ -381,9 +381,9 @@ to be a saved address hash." notmuch-address-internal-completion) (equal (plist-get load-plist :version) notmuch-address--save-hash-version)) - (setq notmuch-address-last-harvest (plist-get load-plist :last-harvest) - notmuch-address-completions (plist-get load-plist :completions) - notmuch-address-full-harvest-finished t) + (setq notmuch-address-last-harvest (plist-get load-plist :last-harvest)) + (setq notmuch-address-completions (plist-get load-plist :completions)) + (setq notmuch-address-full-harvest-finished t) ;; Return t to say load was successful. t))) diff --git a/emacs/notmuch-crypto.el b/emacs/notmuch-crypto.el index 58947a20..420b008f 100644 --- a/emacs/notmuch-crypto.el +++ b/emacs/notmuch-crypto.el @@ -117,20 +117,21 @@ by user FROM." (userid (plist-get sigstatus :userid))) ;; If userid is present it has full or greater validity. (if userid - (setq label (concat "Good signature by: " userid) - face 'notmuch-crypto-signature-good) - (setq label (concat "Good signature by key: " fingerprint) - face 'notmuch-crypto-signature-good-key)) - (setq button-action 'notmuch-crypto-sigstatus-good-callback - help-msg (concat "Click to list key ID 0x" fingerprint ".")))) + (progn + (setq label (concat "Good signature by: " userid)) + (setq face 'notmuch-crypto-signature-good)) + (setq label (concat "Good signature by key: " fingerprint)) + (setq face 'notmuch-crypto-signature-good-key)) + (setq button-action 'notmuch-crypto-sigstatus-good-callback) + (setq help-msg (concat "Click to list key ID 0x" fingerprint ".")))) ((string= status "error") - (setq label (concat "Unknown key ID " keyid " or unsupported algorithm") - button-action 'notmuch-crypto-sigstatus-error-callback - help-msg (concat "Click to retrieve key ID " keyid + (setq label (concat "Unknown key ID " keyid " or unsupported algorithm")) + (setq button-action 'notmuch-crypto-sigstatus-error-callback) + (setq help-msg (concat "Click to retrieve key ID " keyid " from keyserver."))) ((string= status "bad") - (setq label (concat "Bad signature (claimed key ID " keyid ")") - face 'notmuch-crypto-signature-bad)) + (setq label (concat "Bad signature (claimed key ID " keyid ")")) + (setq face 'notmuch-crypto-signature-bad)) (status (setq label (concat "Unknown signature status: " status))) (t diff --git a/emacs/notmuch-lib.el b/emacs/notmuch-lib.el index f8958a91..886da99f 100644 --- a/emacs/notmuch-lib.el +++ b/emacs/notmuch-lib.el @@ -861,8 +861,8 @@ for `call-process'. ARGS is as described for (let (stdin-string) (while (keywordp (car args)) (cl-case (car args) - (:stdin-string (setq stdin-string (cadr args) - args (cddr args))) + (:stdin-string (setq stdin-string (cadr args)) + (setq args (cddr args))) (otherwise (error "Unknown keyword argument: %s" (car args))))) (if (null stdin-string) @@ -939,8 +939,8 @@ status." :buffer buffer :command (cons command args) :connection-type 'pipe - :stderr err-buffer) - err-proc (get-buffer-process err-buffer)) + :stderr err-buffer)) + (setq err-proc (get-buffer-process err-buffer)) (process-put proc 'err-buffer err-buffer) (process-put err-proc 'err-file err-file) diff --git a/emacs/notmuch-show.el b/emacs/notmuch-show.el index d088c80b..e6d7c9ea 100644 --- a/emacs/notmuch-show.el +++ b/emacs/notmuch-show.el @@ -415,8 +415,8 @@ parsing fails." (cond ;; "User " style. ((string-match "\\(.*\\) <\\(.*\\)>" address) - (setq p-name (match-string 1 address) - p-address (match-string 2 address))) + (setq p-name (match-string 1 address)) + (setq p-address (match-string 2 address))) ;; "" style. ((string-match "<\\(.*\\)>" address) @@ -1256,14 +1256,16 @@ matched." ;; Set various buffer local variables to their appropriate initial ;; state. Do this after enabling `notmuch-show-mode' so that they ;; aren't wiped out. - (setq notmuch-show-thread-id thread-id - notmuch-show-parent-buffer parent-buffer - notmuch-show-query-context (if (or (string= query-context "") - (string= query-context "*")) - nil query-context) - notmuch-show-process-crypto notmuch-crypto-process-mime - ;; If `elide-toggle', invert the default value. - notmuch-show-elide-non-matching-messages + (setq notmuch-show-thread-id thread-id) + (setq notmuch-show-parent-buffer parent-buffer) + (setq notmuch-show-query-context + (if (or (string= query-context "") + (string= query-context "*")) + nil + query-context)) + (setq notmuch-show-process-crypto notmuch-crypto-process-mime) + ;; If `elide-toggle', invert the default value. + (setq notmuch-show-elide-non-matching-messages (if elide-toggle (not notmuch-show-only-matching-messages) notmuch-show-only-matching-messages)) @@ -1518,8 +1520,8 @@ All currently available key bindings: \\{notmuch-show-mode-map}" (setq notmuch-buffer-refresh-function #'notmuch-show-refresh-view) - (setq buffer-read-only t - truncate-lines t) + (setq buffer-read-only t) + (setq truncate-lines t) (setq imenu-prev-index-position-function #'notmuch-show-imenu-prev-index-position-function) (setq imenu-extract-index-name-function diff --git a/emacs/notmuch-tree.el b/emacs/notmuch-tree.el index b1bb40b1..827acebf 100644 --- a/emacs/notmuch-tree.el +++ b/emacs/notmuch-tree.el @@ -956,8 +956,8 @@ Complete list of currently available key bindings: \\{notmuch-tree-mode-map}" (setq notmuch-buffer-refresh-function #'notmuch-tree-refresh-view) (hl-line-mode 1) - (setq buffer-read-only t - truncate-lines t)) + (setq buffer-read-only t) + (setq truncate-lines t)) (defun notmuch-tree-process-sentinel (proc msg) "Add a message to let user know when \"notmuch tree\" exits." diff --git a/emacs/notmuch.el b/emacs/notmuch.el index d6e0a9d5..6ce2b1f9 100644 --- a/emacs/notmuch.el +++ b/emacs/notmuch.el @@ -443,8 +443,8 @@ BEG." (while (and pos (or (< pos end) first)) (when (notmuch-search-get-result pos) (funcall fn pos)) - (setq pos (notmuch-search-result-end pos) - first nil)))) + (setq pos (notmuch-search-result-end pos)) + (setq first nil)))) ;; Unindent the function argument of notmuch-search-foreach-result so ;; the indentation of callers doesn't get out of hand. (put 'notmuch-search-foreach-result 'lisp-indent-function 2) @@ -589,8 +589,8 @@ is inactive this applies to the thread at point. If ONLY-MATCHED is non-nil, only tag matched messages." (interactive (notmuch-search-interactive-tag-changes)) (unless (and beg end) - (setq beg (car (notmuch-interactive-region)) - end (cadr (notmuch-interactive-region)))) + (setq beg (car (notmuch-interactive-region))) + (setq end (cadr (notmuch-interactive-region)))) (let ((search-string (notmuch-search-find-stable-query-region beg end only-matched))) (notmuch-tag search-string tag-changes) @@ -762,8 +762,8 @@ non-authors is found, assume that all of the authors match." (length "... ")))) ;; Truncate the visible string according to the width of ;; the display string. - (setq visible-string (substring formatted-authors 0 visible-length) - invisible-string (substring formatted-authors visible-length)) + (setq visible-string (substring formatted-authors 0 visible-length)) + (setq invisible-string (substring formatted-authors visible-length)) ;; If possible, truncate the visible string at a natural ;; break (comma or pipe), as incremental search doesn't ;; match across the visible/invisible border. @@ -771,8 +771,8 @@ non-authors is found, assume that all of the authors match." ;; Second clause is destructive on `visible-string', so ;; order is important. (setq invisible-string (concat (match-string 3 visible-string) - invisible-string) - visible-string (concat (match-string 1 visible-string) + invisible-string)) + (setq visible-string (concat (match-string 1 visible-string) (match-string 2 visible-string)))) ;; `visible-string' may be shorter than the space allowed ;; by `format-string'. If so we must insert some padding @@ -785,17 +785,18 @@ non-authors is found, assume that all of the authors match." (if (string-match "\\(.*\\)|\\(.*\\)" visible-string) ;; The visible string contains both matching and ;; non-matching authors. - (setq visible-string (notmuch-search-author-propertize visible-string) - ;; The invisible string must contain only non-matching - ;; authors, as the visible-string contains both. - invisible-string (propertize invisible-string - 'face 'notmuch-search-non-matching-authors)) + (progn + (setq visible-string (notmuch-search-author-propertize visible-string)) + ;; The invisible string must contain only non-matching + ;; authors, as the visible-string contains both. + (setq invisible-string (propertize invisible-string + 'face 'notmuch-search-non-matching-authors))) ;; The visible string contains only matching authors. (setq visible-string (propertize visible-string - 'face 'notmuch-search-matching-authors) - ;; The invisible string may contain both matching and - ;; non-matching authors. - invisible-string (notmuch-search-author-propertize invisible-string))) + 'face 'notmuch-search-matching-authors)) + ;; The invisible string may contain both matching and + ;; non-matching authors. + (setq invisible-string (notmuch-search-author-propertize invisible-string))) ;; If there is any invisible text, add it as a tooltip to the ;; visible text. (when (not (string= invisible-string "")) From dfb1b8eb89e814f4bf6f6e62b700c72aa1b4659a Mon Sep 17 00:00:00 2001 From: Jonas Bernoulli Date: Sat, 8 Aug 2020 13:49:41 +0200 Subject: [PATCH 341/427] emacs: Use 'and' instead of 'when' when the return value matters Also do so for some 'if' forms that lack an ELSE part. Even go as far as using 'and' and 'not' instead of 'unless'. --- emacs/coolj.el | 12 +++--- emacs/notmuch-address.el | 72 ++++++++++++++++++------------------ emacs/notmuch-crypto.el | 2 +- emacs/notmuch-draft.el | 2 +- emacs/notmuch-lib.el | 37 +++++++++--------- emacs/notmuch-maildir-fcc.el | 4 +- emacs/notmuch-mua.el | 13 +++---- emacs/notmuch-show.el | 45 +++++++++++----------- emacs/notmuch-tree.el | 4 +- emacs/notmuch-wash.el | 8 ++-- emacs/notmuch.el | 28 ++++++++------ 11 files changed, 114 insertions(+), 113 deletions(-) diff --git a/emacs/coolj.el b/emacs/coolj.el index 961db606..39a8de2b 100644 --- a/emacs/coolj.el +++ b/emacs/coolj.el @@ -107,12 +107,12 @@ not need to be wrapped, move point to the next line and return t." If the line should not be broken, return nil; point remains on the line." (move-to-column fill-column) - (if (and (re-search-forward "[^ ]" (line-end-position) 1) - (> (current-column) fill-column)) - ;; This line is too long. Can we break it? - (or (coolj-find-break-backward prefix) - (progn (move-to-column fill-column) - (coolj-find-break-forward))))) + (and (re-search-forward "[^ ]" (line-end-position) 1) + (> (current-column) fill-column) + ;; This line is too long. Can we break it? + (or (coolj-find-break-backward prefix) + (progn (move-to-column fill-column) + (coolj-find-break-forward))))) (defun coolj-find-break-backward (prefix) "Move point backward to the first available breakpoint and return t. diff --git a/emacs/notmuch-address.el b/emacs/notmuch-address.el index 4db7096c..2dd08661 100644 --- a/emacs/notmuch-address.el +++ b/emacs/notmuch-address.el @@ -252,20 +252,20 @@ external commands." (defun notmuch-address-locate-command (command) "Return non-nil if `command' is an executable either on `exec-path' or an absolute pathname." - (when (stringp command) - (if (and (file-name-absolute-p command) - (file-executable-p command)) - command - (setq command (file-name-nondirectory command)) - (catch 'found-command - (let (bin) - (dolist (dir exec-path) - (setq bin (expand-file-name command dir)) - (when (or (and (file-executable-p bin) - (not (file-directory-p bin))) - (and (file-executable-p (setq bin (concat bin ".exe"))) - (not (file-directory-p bin)))) - (throw 'found-command bin)))))))) + (and (stringp command) + (if (and (file-name-absolute-p command) + (file-executable-p command)) + command + (setq command (file-name-nondirectory command)) + (catch 'found-command + (let (bin) + (dolist (dir exec-path) + (setq bin (expand-file-name command dir)) + (when (or (and (file-executable-p bin) + (not (file-directory-p bin))) + (and (file-executable-p (setq bin (concat bin ".exe"))) + (not (file-directory-p bin)))) + (throw 'found-command bin)))))))) (defun notmuch-address-harvest-addr (result) (let ((name-addr (plist-get result :name-addr))) @@ -304,18 +304,20 @@ asynchronously unless SYNCHRONOUS is t. In case of asynchronous execution, CALLBACK is called when harvesting finishes." (let* ((sent (eq (car notmuch-address-internal-completion) 'sent)) (config-query (cadr notmuch-address-internal-completion)) - (prefix-query (when addr-prefix - (format "%s:%s*" (if sent "to" "from") addr-prefix))) + (prefix-query (and addr-prefix + (format "%s:%s*" + (if sent "to" "from") + addr-prefix))) (from-or-to-me-query (mapconcat (lambda (x) (concat (if sent "from:" "to:") x)) (notmuch-user-emails) " or ")) (query (if (or prefix-query config-query) (concat (format "(%s)" from-or-to-me-query) - (when prefix-query - (format " and (%s)" prefix-query)) - (when config-query - (format " and (%s)" config-query))) + (and prefix-query + (format " and (%s)" prefix-query)) + (and config-query + (format " and (%s)" config-query))) from-or-to-me-query)) (args `("address" "--format=sexp" "--format-version=4" ,(if sent "--output=recipients" "--output=sender") @@ -354,21 +356,21 @@ execution, CALLBACK is called when harvesting finishes." Returns nil if the save file does not exist, or it does not seem to be a saved address hash." - (when notmuch-address-save-filename - (condition-case nil - (with-temp-buffer - (insert-file-contents notmuch-address-save-filename) - (let ((name (read (current-buffer))) - (plist (read (current-buffer)))) - ;; We do two simple sanity checks on the loaded file. We just - ;; check a version is specified, not that it is the current - ;; version, as we are allowed to over-write and a save-file with - ;; an older version. - (when (and (string= name "notmuch-address-hash") - (plist-get plist :version)) - plist))) - ;; The error case catches any of the reads failing. - (error nil)))) + (and notmuch-address-save-filename + (condition-case nil + (with-temp-buffer + (insert-file-contents notmuch-address-save-filename) + (let ((name (read (current-buffer))) + (plist (read (current-buffer)))) + ;; We do two simple sanity checks on the loaded file. + ;; We just check a version is specified, not that + ;; it is the current version, as we are allowed to + ;; over-write and a save-file with an older version. + (and (string= name "notmuch-address-hash") + (plist-get plist :version) + plist))) + ;; The error case catches any of the reads failing. + (error nil)))) (defun notmuch-address--load-address-hash () "Read the saved address hash and set the corresponding variables." diff --git a/emacs/notmuch-crypto.el b/emacs/notmuch-crypto.el index 420b008f..e6bf8339 100644 --- a/emacs/notmuch-crypto.el +++ b/emacs/notmuch-crypto.el @@ -253,7 +253,7 @@ corresponding key when the status button is pressed." "Decryption error") (t (concat "Unknown encryption status" - (if status (concat ": " status)))))) + (and status (concat ": " status)))))) " ]") :type 'notmuch-crypto-status-button-type 'face 'notmuch-crypto-decryption diff --git a/emacs/notmuch-draft.el b/emacs/notmuch-draft.el index ea995379..759e6c9e 100644 --- a/emacs/notmuch-draft.el +++ b/emacs/notmuch-draft.el @@ -263,7 +263,7 @@ applied to newly inserted messages)." ;; If the resumed message was a draft then set the draft ;; message-id so that we can delete the current saved draft if the ;; message is resaved or sent. - (setq notmuch-draft-id (when draft id))))) + (setq notmuch-draft-id (and draft id))))) (add-hook 'message-send-hook 'notmuch-draft--mark-deleted) diff --git a/emacs/notmuch-lib.el b/emacs/notmuch-lib.el index 886da99f..98056eb6 100644 --- a/emacs/notmuch-lib.el +++ b/emacs/notmuch-lib.el @@ -608,7 +608,7 @@ the given type." (set-buffer-multibyte nil)) (let ((args `("show" "--format=raw" ,(format "--part=%s" (plist-get part :id)) - ,@(when process-crypto '("--decrypt=true")) + ,@(and process-crypto '("--decrypt=true")) ,(notmuch-id-to-query (plist-get msg :id)))) (coding-system-for-read (if binaryp 'no-conversion @@ -781,8 +781,8 @@ signaled error. This function does not return." (insert extra) (unless (bolp) (newline))))) - (error "%s" (concat msg (when extra - " (see *Notmuch errors* for more details)")))) + (error "%s" + (concat msg (and extra " (see *Notmuch errors* for more details)")))) (defun notmuch-check-async-exit-status (proc msg &optional command err) "If PROC exited abnormally, pop up an error buffer and signal an error. @@ -836,10 +836,8 @@ You may need to restart Emacs or upgrade your notmuch package.")) (if (integerp exit-status) (format "exit status: %s\n" exit-status) (format "exit signal: %s\n" exit-status)) - (when err - (concat "stderr:\n" err)) - (when output - (concat "stdout:\n" output))))) + (and err (concat "stderr:\n" err)) + (and output (concat "stdout:\n" output))))) (if err ;; We have an error message straight from the CLI. (notmuch-logged-error @@ -968,8 +966,8 @@ status." (let* ((err-file (process-get proc 'err-file)) (err-buffer (or (process-get proc 'err-buffer) (find-file-noselect err-file))) - (err (when (not (zerop (buffer-size err-buffer))) - (with-current-buffer err-buffer (buffer-string)))) + (err (and (not (zerop (buffer-size err-buffer))) + (with-current-buffer err-buffer (buffer-string)))) (sub-sentinel (process-get proc 'sub-sentinel)) (real-command (process-get proc 'real-command))) (condition-case err @@ -987,16 +985,17 @@ status." ;; If that didn't signal an error, then any error output was ;; really warning output. Show warnings, if any. (let ((warnings - (when err - (with-current-buffer err-buffer - (goto-char (point-min)) - (end-of-line) - ;; Show first line; stuff remaining lines in the - ;; errors buffer. - (let ((l1 (buffer-substring (point-min) (point)))) - (skip-chars-forward "\n") - (cons l1 (unless (eobp) - (buffer-substring (point) (point-max))))))))) + (and err + (with-current-buffer err-buffer + (goto-char (point-min)) + (end-of-line) + ;; Show first line; stuff remaining lines in the + ;; errors buffer. + (let ((l1 (buffer-substring (point-min) (point)))) + (skip-chars-forward "\n") + (cons l1 (and (not (eobp)) + (buffer-substring (point) + (point-max))))))))) (when warnings (notmuch-logged-error (car warnings) (cdr warnings))))) (error diff --git a/emacs/notmuch-maildir-fcc.el b/emacs/notmuch-maildir-fcc.el index 7d001b2d..1027e1a7 100644 --- a/emacs/notmuch-maildir-fcc.el +++ b/emacs/notmuch-maildir-fcc.el @@ -215,7 +215,7 @@ This inserts the current buffer as a message into the notmuch database in folder FOLDER. If CREATE is non-nil it will supply the --create-folder flag to create the folder if necessary. TAGS should be a list of tag changes to apply to the inserted message." - (let* ((args (append (when create (list "--create-folder")) + (let* ((args (append (and create (list "--create-folder")) (list (concat "--folder=" folder)) tags))) (apply 'notmuch-call-notmuch-process @@ -315,7 +315,7 @@ if successful, nil if not." (defun notmuch-maildir-fcc-move-tmp-to-cur (destdir msg-id &optional mark-seen) (add-name-to-file (concat destdir "/tmp/" msg-id) - (concat destdir "/cur/" msg-id ":2," (when mark-seen "S")))) + (concat destdir "/cur/" msg-id ":2," (and mark-seen "S")))) (defun notmuch-maildir-fcc-file-fcc (fcc-header) "Write the message to the file specified by FCC-HEADER. diff --git a/emacs/notmuch-mua.el b/emacs/notmuch-mua.el index 7f80224f..f6d8ffc5 100644 --- a/emacs/notmuch-mua.el +++ b/emacs/notmuch-mua.el @@ -460,8 +460,8 @@ If PROMPT-FOR-SENDER is non-nil, the user will be prompted for the From: address first." (interactive "P") (let ((other-headers - (when (or prompt-for-sender notmuch-always-prompt-for-sender) - (list (cons 'From (notmuch-mua-prompt-for-sender)))))) + (and (or prompt-for-sender notmuch-always-prompt-for-sender) + (list (cons 'From (notmuch-mua-prompt-for-sender)))))) (notmuch-mua-mail nil nil other-headers nil (notmuch-mua-get-switch-function)))) (defun notmuch-mua-new-forward-messages (messages &optional prompt-for-sender) @@ -470,8 +470,8 @@ the From: address first." If PROMPT-FOR-SENDER is non-nil, the user will be prompteed for the From: address." (let* ((other-headers - (when (or prompt-for-sender notmuch-always-prompt-for-sender) - (list (cons 'From (notmuch-mua-prompt-for-sender))))) + (and (or prompt-for-sender notmuch-always-prompt-for-sender) + (list (cons 'From (notmuch-mua-prompt-for-sender))))) ;; Comes from the first message and is applied later. forward-subject ;; List of accumulated message-references of forwarded messages. @@ -542,9 +542,8 @@ will be addressed to all recipients of the source message." ;; primary selection was previously in a non-emacs window but not if ;; it was in an emacs window. To avoid the problem in the latter case ;; we deactivate mark. - (let ((sender - (when prompt-for-sender - (notmuch-mua-prompt-for-sender))) + (let ((sender (and prompt-for-sender + (notmuch-mua-prompt-for-sender))) (select-active-regions nil)) (notmuch-mua-reply query-string sender reply-all) (deactivate-mark))) diff --git a/emacs/notmuch-show.el b/emacs/notmuch-show.el index e6d7c9ea..531ce1ae 100644 --- a/emacs/notmuch-show.el +++ b/emacs/notmuch-show.el @@ -506,10 +506,10 @@ message at DEPTH in the current thread." (defun notmuch-show-insert-part-header (nth content-type declared-type &optional name comment) (let ((button) - (base-label (concat (when name (concat name ": ")) + (base-label (concat (and name (concat name ": ")) declared-type - (unless (string-equal declared-type content-type) - (concat " (as " content-type ")")) + (and (not (string-equal declared-type content-type)) + (concat " (as " content-type ")")) comment))) (setq button (insert-button @@ -787,18 +787,15 @@ will return nil if the CID is unknown or cannot be retrieved." (defun notmuch-show-get-mime-type-of-application/octet-stream (part) ;; If we can deduce a MIME type from the filename of the attachment, ;; we return that. - (if (plist-get part :filename) - (let ((extension (file-name-extension (plist-get part :filename))) - mime-type) - (if extension - (progn - (mailcap-parse-mimetypes) - (setq mime-type (mailcap-extension-to-mime extension)) - (if (and mime-type - (not (string-equal mime-type "application/octet-stream"))) - mime-type - nil)) - nil)))) + (and (plist-get part :filename) + (let ((extension (file-name-extension (plist-get part :filename)))) + (and extension + (progn + (mailcap-parse-mimetypes) + (let ((mime-type (mailcap-extension-to-mime extension))) + (and mime-type + (not (string-equal mime-type "application/octet-stream")) + mime-type))))))) (defun notmuch-show-insert-part-text/html (msg part content-type nth depth button) (if (eq mm-text-html-renderer 'shr) @@ -997,9 +994,10 @@ is t, hide the part initially and show the button." (beg (point)) ;; This default header-p function omits the part button for ;; the first (or only) part if this is text/plain. - (button (when (funcall notmuch-show-insert-header-p-function part hide) - (notmuch-show-insert-part-header nth mime-type content-type - (plist-get part :filename)))) + (button (and (funcall notmuch-show-insert-header-p-function part hide) + (notmuch-show-insert-part-header + nth mime-type content-type + (plist-get part :filename)))) ;; Hide the part initially if HIDE is t, or if it is too long ;; and we have a button to allow toggling. (show-part (not (or (equal hide t) @@ -1054,9 +1052,8 @@ is t, hide the part initially and show the button." (bare-subject (notmuch-show-strip-re (plist-get headers :Subject)))) (setq message-start (point-marker)) (notmuch-show-insert-headerline headers - (or (if notmuch-show-relative-dates - (plist-get msg :date_relative) - nil) + (or (and notmuch-show-relative-dates + (plist-get msg :date_relative)) (plist-get headers :Date)) (plist-get msg :tags) depth) (setq content-start (point-marker)) @@ -1303,8 +1300,8 @@ first relevant message. If no messages match the query return NIL." (let* ((cli-args (cons "--exclude=false" - (when notmuch-show-elide-non-matching-messages - (list "--entire-thread=false")))) + (and notmuch-show-elide-non-matching-messages + (list "--entire-thread=false")))) (queries (notmuch-show--build-queries notmuch-show-thread-id notmuch-show-query-context)) (forest nil) @@ -2412,7 +2409,7 @@ MIME-TYPE is given then set the handle's mime-type to MIME-TYPE." (buf (notmuch-show-generate-part-buffer msg part)) (computed-type (or mime-type (plist-get part :computed-type))) (filename (plist-get part :filename)) - (disposition (if filename `(attachment (filename . ,filename))))) + (disposition (and filename `(attachment (filename . ,filename))))) (mm-make-handle buf (list computed-type) nil nil disposition))) (defun notmuch-show-apply-to-current-part-handle (fn &optional mime-type) diff --git a/emacs/notmuch-tree.el b/emacs/notmuch-tree.el index 827acebf..37a5d1c8 100644 --- a/emacs/notmuch-tree.el +++ b/emacs/notmuch-tree.el @@ -1020,8 +1020,8 @@ the same as for the function notmuch-tree." (erase-buffer) (goto-char (point-min)) (let* ((search-args (concat basic-query - (if query-context - (concat " and (" query-context ")")))) + (and query-context + (concat " and (" query-context ")")))) (message-arg (if unthreaded "--unthreaded" "--entire-thread"))) (if (equal (car (process-lines notmuch-command "count" search-args)) "0") (setq search-args basic-query)) diff --git a/emacs/notmuch-wash.el b/emacs/notmuch-wash.el index 00ac45b6..31fda61f 100644 --- a/emacs/notmuch-wash.el +++ b/emacs/notmuch-wash.el @@ -370,10 +370,10 @@ filename, before trimming any trailing . and - characters." Return the patch sequence number N from the last \"[PATCH N/M]\" style prefix in SUBJECT, or nil if such a prefix can't be found." - (when (string-match - "^ *\\(\\[[^]]*\\] *\\)*\\[[^]]*?\\([0-9]+\\)/[0-9]+[^]]*\\].*" - subject) - (string-to-number (substring subject (match-beginning 2) (match-end 2))))) + (and (string-match + "^ *\\(\\[[^]]*\\] *\\)*\\[[^]]*?\\([0-9]+\\)/[0-9]+[^]]*\\].*" + subject) + (string-to-number (substring subject (match-beginning 2) (match-end 2))))) (defun notmuch-wash-subject-to-patch-filename (subject) "Convert a patch mail SUBJECT into a filename. diff --git a/emacs/notmuch.el b/emacs/notmuch.el index 6ce2b1f9..41207643 100644 --- a/emacs/notmuch.el +++ b/emacs/notmuch.el @@ -264,7 +264,8 @@ there will be called at other points of notmuch execution." (goto-char (point-max)) (forward-line -2) (let ((beg (notmuch-search-result-beginning))) - (when beg (goto-char beg)))) + (when beg + (goto-char beg)))) (defun notmuch-search-first-thread () "Select the first thread in the search results." @@ -406,11 +407,12 @@ If there is no thread at POS (or point), returns nil." "Return the point at the beginning of the thread at POS (or point). If there is no thread at POS (or point), returns nil." - (when (notmuch-search-get-result pos) - ;; We pass 1+point because previous-single-property-change starts - ;; searching one before the position we give it. - (previous-single-property-change (1+ (or pos (point))) - 'notmuch-search-result nil (point-min)))) + (and (notmuch-search-get-result pos) + ;; We pass 1+point because previous-single-property-change starts + ;; searching one before the position we give it. + (previous-single-property-change (1+ (or pos (point))) + 'notmuch-search-result nil + (point-min)))) (defun notmuch-search-result-end (&optional pos) "Return the point at the end of the thread at POS (or point). @@ -418,9 +420,10 @@ If there is no thread at POS (or point), returns nil." The returned point will be just after the newline character that ends the result line. If there is no thread at POS (or point), returns nil." - (when (notmuch-search-get-result pos) - (next-single-property-change (or pos (point)) 'notmuch-search-result - nil (point-max)))) + (and (notmuch-search-get-result pos) + (next-single-property-change (or pos (point)) + 'notmuch-search-result nil + (point-max)))) (defun notmuch-search-foreach-result (beg end fn) "Invoke FN for each result between BEG and END. @@ -461,7 +464,8 @@ BEG." If BARE is set then do not prefix with \"thread:\"." (let ((thread (plist-get (notmuch-search-get-result) :thread))) - (when thread (concat (unless bare "thread:") thread)))) + (when thread + (concat (and (not bare) "thread:") thread)))) (defun notmuch-search-find-stable-query () "Return the stable queries for the current thread. @@ -482,8 +486,8 @@ no messages in the region then return nil." (push (car queries) query-list)) (when (and all (cadr queries)) (push (cadr queries) query-list))) - (when query-list - (concat "(" (mapconcat 'identity query-list ") or (") ")")))) + (and query-list + (concat "(" (mapconcat 'identity query-list ") or (") ")")))) (defun notmuch-search-find-authors () "Return the authors for the current thread." From 09f6533c3781b61ea634790d4bad38aadf89115c Mon Sep 17 00:00:00 2001 From: Jonas Bernoulli Date: Sat, 8 Aug 2020 13:49:42 +0200 Subject: [PATCH 342/427] emacs: Use 'unless' instead of 'when' and 'not' Also use 'unless' in a few cases where previously 'if' was used with 'not' but without an ELSE part. --- emacs/notmuch-hello.el | 6 +++--- emacs/notmuch-mua.el | 4 ++-- emacs/notmuch-parser.el | 2 +- emacs/notmuch-show.el | 3 +-- emacs/notmuch-tag.el | 4 ++-- emacs/notmuch-tree.el | 8 ++++---- emacs/notmuch.el | 4 ++-- test/test-lib.el | 6 +++--- 8 files changed, 18 insertions(+), 19 deletions(-) diff --git a/emacs/notmuch-hello.el b/emacs/notmuch-hello.el index 89e03c36..1c0b1848 100644 --- a/emacs/notmuch-hello.el +++ b/emacs/notmuch-hello.el @@ -638,7 +638,7 @@ with `notmuch-hello-query-counts'." (dolist (window (window-list)) (let ((last-buf (window-parameter window 'notmuch-hello-last-buffer)) (cur-buf (window-buffer window))) - (when (not (eq last-buf cur-buf)) + (unless (eq last-buf cur-buf) ;; This window changed or is new. Update recorded buffer ;; for next time. (set-window-parameter window 'notmuch-hello-last-buffer cur-buf) @@ -652,7 +652,7 @@ with `notmuch-hello-query-counts'." ;; 24, we can't do it right here because something in this ;; hook's call stack overrides hello's point placement. (run-at-time nil nil #'notmuch-hello t)) - (when (null hello-buf) + (unless hello-buf ;; Clean up hook (remove-hook 'window-configuration-change-hook #'notmuch-hello-window-configuration-change)))) @@ -908,7 +908,7 @@ Supports the following entries in OPTIONS as a plist: (notmuch-hello-update)) "hide")) (widget-insert "\n") - (when (not is-hidden) + (unless is-hidden (let ((searches (apply 'notmuch-hello-query-counts query-list options))) (when (or (not (plist-get options :hide-if-empty)) searches) diff --git a/emacs/notmuch-mua.el b/emacs/notmuch-mua.el index f6d8ffc5..dc1f518c 100644 --- a/emacs/notmuch-mua.el +++ b/emacs/notmuch-mua.el @@ -198,7 +198,7 @@ Typically this is added to `notmuch-mua-send-hook'." (defun notmuch-mua-add-more-hidden-headers () "Add some headers to the list that are hidden by default." (mapc (lambda (header) - (when (not (member header message-hidden-headers)) + (unless (member header message-hidden-headers) (push header message-hidden-headers))) notmuch-mua-hidden-headers)) @@ -368,7 +368,7 @@ modified. This function is notmuch addaptation of (interactive) (when notmuch-mua-user-agent-function (let ((user-agent (funcall notmuch-mua-user-agent-function))) - (when (not (string= "" user-agent)) + (unless (string= "" user-agent) (push (cons 'User-Agent user-agent) other-headers)))) (unless (assq 'From other-headers) (push (cons 'From (message-make-from diff --git a/emacs/notmuch-parser.el b/emacs/notmuch-parser.el index 06e7487b..fbcfc2ef 100644 --- a/emacs/notmuch-parser.el +++ b/emacs/notmuch-parser.el @@ -78,7 +78,7 @@ returns the value." ;; parse, extend the partial parse to figure out when we ;; have a complete list. (catch 'return - (when (null (notmuch-sexp--partial-state sp)) + (unless (notmuch-sexp--partial-state sp) (let ((start (point))) (condition-case nil (throw 'return (read (current-buffer))) diff --git a/emacs/notmuch-show.el b/emacs/notmuch-show.el index 531ce1ae..7a033150 100644 --- a/emacs/notmuch-show.el +++ b/emacs/notmuch-show.el @@ -1066,8 +1066,7 @@ is t, hide the part initially and show the button." ;; If the subject of this message is the same as that of the ;; previous message, don't display it when this message is ;; collapsed. - (when (not (string= notmuch-show-previous-subject - bare-subject)) + (unless (string= notmuch-show-previous-subject bare-subject) (forward-line 1)) (setq headers-start (point-marker))) (setq headers-end (point-marker)) diff --git a/emacs/notmuch-tag.el b/emacs/notmuch-tag.el index 2fcf5a9e..1067f185 100644 --- a/emacs/notmuch-tag.el +++ b/emacs/notmuch-tag.el @@ -382,8 +382,8 @@ the messages that were tagged." "Return a list of tags for messages matching SEARCH-TERMS. Returns all tags if no search terms are given." - (if (null search-terms) - (setq search-terms (list "*"))) + (unless search-terms + (setq search-terms (list "*"))) (split-string (with-output-to-string (with-current-buffer standard-output diff --git a/emacs/notmuch-tree.el b/emacs/notmuch-tree.el index 37a5d1c8..a5ae0edb 100644 --- a/emacs/notmuch-tree.el +++ b/emacs/notmuch-tree.el @@ -1063,10 +1063,10 @@ The arguments are: OPEN-TARGET: If TRUE open the target message in the message pane. UNTHREADED: If TRUE only show matching messages in an unthreaded view." (interactive) - (if (null query) - (setq query (notmuch-read-query (concat "Notmuch " - (if unthreaded "unthreaded " "tree ") - "view search: ")))) + (unless query + (setq query (notmuch-read-query (concat "Notmuch " + (if unthreaded "unthreaded " "tree ") + "view search: ")))) (let ((buffer (get-buffer-create (generate-new-buffer-name (or buffer-name (concat "*notmuch-" diff --git a/emacs/notmuch.el b/emacs/notmuch.el index 41207643..93e92b39 100644 --- a/emacs/notmuch.el +++ b/emacs/notmuch.el @@ -803,13 +803,13 @@ non-authors is found, assume that all of the authors match." (setq invisible-string (notmuch-search-author-propertize invisible-string))) ;; If there is any invisible text, add it as a tooltip to the ;; visible text. - (when (not (string= invisible-string "")) + (unless (string= invisible-string "") (setq visible-string (propertize visible-string 'help-echo (concat "..." invisible-string)))) ;; Insert the visible and, if present, invisible author strings. (insert visible-string) - (when (not (string= invisible-string "")) + (unless (string= invisible-string "") (let ((start (point)) overlay) (insert invisible-string) diff --git a/test/test-lib.el b/test/test-lib.el index aae9e833..a1233390 100644 --- a/test/test-lib.el +++ b/test/test-lib.el @@ -81,7 +81,7 @@ invisible text." (let (str) (while (< start end) (let ((next-pos (next-char-property-change start end))) - (when (not (invisible-p start)) + (unless (invisible-p start) (setq str (concat str (buffer-substring-no-properties start next-pos)))) (setq start next-pos))) @@ -97,8 +97,8 @@ invisible text." (defun orphan-watchdog-check (pid) "Periodically check that the process with id PID is still running, quit if it terminated." - (if (not (test-process-running pid)) - (kill-emacs))) + (unless (test-process-running pid) + (kill-emacs))) (defun orphan-watchdog (pid) "Initiate orphan watchdog check." From e1a700067a22214f54064c281219fbbbef87de06 Mon Sep 17 00:00:00 2001 From: Jonas Bernoulli Date: Sat, 8 Aug 2020 13:49:43 +0200 Subject: [PATCH 343/427] emacs: Use 'when' instead of 'if' when there is no ELSE part --- emacs/notmuch-address.el | 8 ++-- emacs/notmuch-compat.el | 6 +-- emacs/notmuch-hello.el | 8 ++-- emacs/notmuch-jump.el | 54 ++++++++++----------- emacs/notmuch-lib.el | 10 ++-- emacs/notmuch-mua.el | 24 +++++----- emacs/notmuch-query.el | 4 +- emacs/notmuch-show.el | 93 ++++++++++++++++++------------------ emacs/notmuch-tree.el | 30 ++++++------ emacs/notmuch-wash.el | 68 +++++++++++++------------- emacs/notmuch.el | 100 +++++++++++++++++++-------------------- test/test-lib.el | 4 +- 12 files changed, 206 insertions(+), 203 deletions(-) diff --git a/emacs/notmuch-address.el b/emacs/notmuch-address.el index 2dd08661..85531489 100644 --- a/emacs/notmuch-address.el +++ b/emacs/notmuch-address.el @@ -171,10 +171,10 @@ toggles the setting in this buffer." (if (local-variable-p 'notmuch-address-command) (kill-local-variable 'notmuch-address-command) (notmuch-setq-local notmuch-address-command 'internal)) - (if (boundp 'company-idle-delay) - (if (local-variable-p 'company-idle-delay) - (kill-local-variable 'company-idle-delay) - (notmuch-setq-local company-idle-delay nil)))) + (when (boundp 'company-idle-delay) + (if (local-variable-p 'company-idle-delay) + (kill-local-variable 'company-idle-delay) + (notmuch-setq-local company-idle-delay nil)))) (defun notmuch-address-matching (substring) "Returns a list of completion candidates matching SUBSTRING. diff --git a/emacs/notmuch-compat.el b/emacs/notmuch-compat.el index 388ef70f..3340918f 100644 --- a/emacs/notmuch-compat.el +++ b/emacs/notmuch-compat.el @@ -68,9 +68,9 @@ inlined." ;; This is an inlined copy of help-form-show as that ;; was introduced in emacs 24 too. (let ((msg (eval help-form))) - (if (stringp msg) - (with-output-to-temp-buffer " *Char Help*" - (princ msg)))))) + (when (stringp msg) + (with-output-to-temp-buffer " *Char Help*" + (princ msg)))))) ((memq char chars) (setq done t)) ((and executing-kbd-macro (= char -1)) diff --git a/emacs/notmuch-hello.el b/emacs/notmuch-hello.el index 1c0b1848..c17b46bc 100644 --- a/emacs/notmuch-hello.el +++ b/emacs/notmuch-hello.el @@ -589,8 +589,8 @@ with `notmuch-hello-query-counts'." (mapc (lambda (elem) ;; (not elem) indicates an empty slot in the matrix. (when elem - (if (> column-indent 0) - (widget-insert (make-string column-indent ? ))) + (when (> column-indent 0) + (widget-insert (make-string column-indent ? ))) (let* ((name (plist-get elem :name)) (query (plist-get elem :query)) (oldest-first (cl-case (plist-get elem :sort-order) @@ -890,8 +890,8 @@ Supports the following entries in OPTIONS as a plist: the same values as :filter. If :filter and :filter-count are specified, this will be used instead of :filter, not in conjunction with it." (widget-insert title ": ") - (if (and notmuch-hello-first-run (plist-get options :initially-hidden)) - (add-to-list 'notmuch-hello-hidden-sections title)) + (when (and notmuch-hello-first-run (plist-get options :initially-hidden)) + (add-to-list 'notmuch-hello-hidden-sections title)) (let ((is-hidden (member title notmuch-hello-hidden-sections)) (start (point))) (if is-hidden diff --git a/emacs/notmuch-jump.el b/emacs/notmuch-jump.el index adf79650..e302fe00 100644 --- a/emacs/notmuch-jump.el +++ b/emacs/notmuch-jump.el @@ -169,37 +169,37 @@ buffer." (let ((map (make-sparse-keymap))) (set-keymap-parent map notmuch-jump-minibuffer-map) (pcase-dolist (`(,key ,name ,fn) action-map) - (if (= (length key) 1) - (define-key map key - `(lambda () (interactive) - (setq notmuch-jump--action ',fn) - (exit-minibuffer))))) + (when (= (length key) 1) + (define-key map key + `(lambda () (interactive) + (setq notmuch-jump--action ',fn) + (exit-minibuffer))))) ;; By doing this in two passes (and checking if we already have a ;; binding) we avoid problems if the user specifies a binding which ;; is a prefix of another binding. (pcase-dolist (`(,key ,name ,fn) action-map) - (if (> (length key) 1) - (let* ((key (elt key 0)) - (keystr (string key)) - (new-prompt (concat prompt (format-kbd-macro keystr) " ")) - (action-submap nil)) - (unless (lookup-key map keystr) - (pcase-dolist (`(,k ,n ,f) action-map) - (when (= key (elt k 0)) - (push (list (substring k 1) n f) action-submap))) - ;; We deal with backspace specially - (push (list (kbd "DEL") - "Backup" - (apply-partially #'notmuch-jump action-map prompt)) - action-submap) - (setq action-submap (nreverse action-submap)) - (define-key map keystr - `(lambda () (interactive) - (setq notmuch-jump--action - ',(apply-partially #'notmuch-jump - action-submap - new-prompt)) - (exit-minibuffer))))))) + (when (> (length key) 1) + (let* ((key (elt key 0)) + (keystr (string key)) + (new-prompt (concat prompt (format-kbd-macro keystr) " ")) + (action-submap nil)) + (unless (lookup-key map keystr) + (pcase-dolist (`(,k ,n ,f) action-map) + (when (= key (elt k 0)) + (push (list (substring k 1) n f) action-submap))) + ;; We deal with backspace specially + (push (list (kbd "DEL") + "Backup" + (apply-partially #'notmuch-jump action-map prompt)) + action-submap) + (setq action-submap (nreverse action-submap)) + (define-key map keystr + `(lambda () (interactive) + (setq notmuch-jump--action + ',(apply-partially #'notmuch-jump + action-submap + new-prompt)) + (exit-minibuffer))))))) map)) ;; diff --git a/emacs/notmuch-lib.el b/emacs/notmuch-lib.el index 98056eb6..da48bb86 100644 --- a/emacs/notmuch-lib.el +++ b/emacs/notmuch-lib.el @@ -664,11 +664,11 @@ MSG (if it isn't already)." ;; first loading gnus-art, which defines it, resulting in a ;; void-variable error. Hence, we advise `mm-shr' to ensure gnus-art ;; is loaded. -(if (>= emacs-major-version 24) - (defadvice mm-shr (before load-gnus-arts activate) - (require 'gnus-art nil t) - (ad-disable-advice 'mm-shr 'before 'load-gnus-arts) - (ad-activate 'mm-shr))) +(when (>= emacs-major-version 24) + (defadvice mm-shr (before load-gnus-arts activate) + (require 'gnus-art nil t) + (ad-disable-advice 'mm-shr 'before 'load-gnus-arts) + (ad-activate 'mm-shr))) (defun notmuch-mm-display-part-inline (msg part content-type process-crypto) "Use the mm-decode/mm-view functions to display a part in the diff --git a/emacs/notmuch-mua.el b/emacs/notmuch-mua.el index dc1f518c..2910a63e 100644 --- a/emacs/notmuch-mua.el +++ b/emacs/notmuch-mua.el @@ -275,8 +275,8 @@ Typically this is added to `notmuch-mua-send-hook'." (narrow-to-region (point) (point-max)) (goto-char (point-max)) (if (re-search-backward message-signature-separator nil t) - (if message-signature-insert-empty-line - (forward-line -1)) + (when message-signature-insert-empty-line + (forward-line -1)) (goto-char (point-max)))) (let ((from (plist-get original-headers :From)) (date (plist-get original-headers :Date)) @@ -388,7 +388,8 @@ modified. This function is notmuch addaptation of ;; https://lists.gnu.org/archive/html/emacs-devel/2011-01/msg00337.html ;; We need to convert any string input, eg from rmail-start-mail. (dolist (h other-headers other-headers) - (if (stringp (car h)) (setcar h (intern (capitalize (car h)))))))) + (when (stringp (car h)) + (setcar h (intern (capitalize (car h)))))))) (args (list yank-action send-actions)) ;; Cause `message-setup-1' to do things relevant for mail, ;; such as observe `message-default-mail-headers'. @@ -428,14 +429,15 @@ the From: header is already filled in by notmuch." ;; without some explicit initialization fill freeze the operation. ;; Hence, we advice `ido-completing-read' to ensure required initialization ;; is done. -(if (and (= emacs-major-version 23) (< emacs-minor-version 4)) - (defadvice ido-completing-read (before notmuch-ido-mode-init activate) - (ido-init-completion-maps) - (add-hook 'minibuffer-setup-hook 'ido-minibuffer-setup) - (add-hook 'choose-completion-string-functions - 'ido-choose-completion-string) - (ad-disable-advice 'ido-completing-read 'before 'notmuch-ido-mode-init) - (ad-activate 'ido-completing-read))) +(when (and (= emacs-major-version 23) + (< emacs-minor-version 4)) + (defadvice ido-completing-read (before notmuch-ido-mode-init activate) + (ido-init-completion-maps) + (add-hook 'minibuffer-setup-hook 'ido-minibuffer-setup) + (add-hook 'choose-completion-string-functions + 'ido-choose-completion-string) + (ad-disable-advice 'ido-completing-read 'before 'notmuch-ido-mode-init) + (ad-activate 'ido-completing-read))) (defun notmuch-mua-prompt-for-sender () "Prompt for a sender from the user's configured identities." diff --git a/emacs/notmuch-query.el b/emacs/notmuch-query.el index 1db9d7ca..a8e5d11f 100644 --- a/emacs/notmuch-query.el +++ b/emacs/notmuch-query.el @@ -30,8 +30,8 @@ A thread is a forest or list of trees. A tree is a two element list where the first element is a message, and the second element is a possibly empty forest of replies." (let ((args '("show" "--format=sexp" "--format-version=4"))) - (if notmuch-show-process-crypto - (setq args (append args '("--decrypt=true")))) + (when notmuch-show-process-crypto + (setq args (append args '("--decrypt=true")))) (setq args (append args search-terms)) (apply #'notmuch-call-notmuch-sexp args))) diff --git a/emacs/notmuch-show.el b/emacs/notmuch-show.el index 7a033150..f1cd92fe 100644 --- a/emacs/notmuch-show.el +++ b/emacs/notmuch-show.el @@ -346,10 +346,10 @@ operation on the contents of the current buffer." (indenting notmuch-show-indent-content)) (with-temp-buffer (insert all) - (if indenting - (indent-rigidly (point-min) - (point-max) - (- (* notmuch-show-indent-messages-width depth)))) + (when indenting + (indent-rigidly (point-min) + (point-max) + (- (* notmuch-show-indent-messages-width depth)))) ;; Remove the original header. (goto-char (point-min)) (re-search-forward "^$" (point-max) nil) @@ -392,13 +392,13 @@ operation on the contents of the current buffer." "Update the displayed tags of the current message." (save-excursion (goto-char (notmuch-show-message-top)) - (if (re-search-forward "(\\([^()]*\\))$" (line-end-position) t) - (let ((inhibit-read-only t)) - (replace-match (concat "(" - (notmuch-tag-format-tags - tags - (notmuch-show-get-prop :orig-tags)) - ")")))))) + (when (re-search-forward "(\\([^()]*\\))$" (line-end-position) t) + (let ((inhibit-read-only t)) + (replace-match (concat "(" + (notmuch-tag-format-tags + tags + (notmuch-show-get-prop :orig-tags)) + ")")))))) (defun notmuch-clean-address (address) "Try to clean a single email ADDRESS for display. Return a cons @@ -488,9 +488,9 @@ message at DEPTH in the current thread." (mapc (lambda (header) (let* ((header-symbol (intern (concat ":" header))) (header-value (plist-get headers header-symbol))) - (if (and header-value - (not (string-equal "" header-value))) - (notmuch-show-insert-header header header-value)))) + (when (and header-value + (not (string-equal "" header-value))) + (notmuch-show-insert-header header header-value)))) notmuch-message-headers) (save-excursion (save-restriction @@ -606,10 +606,10 @@ will return nil if the CID is unknown or cannot be retrieved." (defun notmuch-show-setup-w3m () "Instruct w3m how to retrieve content from a \"related\" part of a message." (interactive) - (if (boundp 'w3m-cid-retrieve-function-alist) - (unless (assq 'notmuch-show-mode w3m-cid-retrieve-function-alist) - (push (cons 'notmuch-show-mode #'notmuch-show--cid-w3m-retrieve) - w3m-cid-retrieve-function-alist))) + (when (and (boundp 'w3m-cid-retrieve-function-alist) + (not (assq 'notmuch-show-mode w3m-cid-retrieve-function-alist))) + (push (cons 'notmuch-show-mode #'notmuch-show--cid-w3m-retrieve) + w3m-cid-retrieve-function-alist)) (setq mm-html-inhibit-images nil)) (defvar w3m-current-buffer) ;; From `w3m.el'. @@ -767,22 +767,22 @@ will return nil if the CID is unknown or cannot be retrieved." (defun notmuch-show-insert-part-text/x-vcalendar (msg part content-type nth depth button) (notmuch-show-insert-part-text/calendar msg part content-type nth depth button)) -(if (version< emacs-version "25.3") - ;; https://bugs.gnu.org/28350 - ;; - ;; For newer emacs, we fall back to notmuch-show-insert-part-*/* - ;; (see notmuch-show-handlers-for) - (defun notmuch-show-insert-part-text/enriched - (msg part content-type nth depth button) - ;; By requiring enriched below, we ensure that the function - ;; enriched-decode-display-prop is defined before it will be - ;; shadowed by the letf below. Otherwise the version in - ;; enriched.el may be loaded a bit later and used instead (for - ;; the first time). - (require 'enriched) - (cl-letf (((symbol-function 'enriched-decode-display-prop) - (lambda (start end &optional param) (list start end)))) - (notmuch-show-insert-part-*/* msg part content-type nth depth button)))) +(when (version< emacs-version "25.3") + ;; https://bugs.gnu.org/28350 + ;; + ;; For newer emacs, we fall back to notmuch-show-insert-part-*/* + ;; (see notmuch-show-handlers-for) + (defun notmuch-show-insert-part-text/enriched + (msg part content-type nth depth button) + ;; By requiring enriched below, we ensure that the function + ;; enriched-decode-display-prop is defined before it will be + ;; shadowed by the letf below. Otherwise the version in + ;; enriched.el may be loaded a bit later and used instead (for + ;; the first time). + (require 'enriched) + (cl-letf (((symbol-function 'enriched-decode-display-prop) + (lambda (start end &optional param) (list start end)))) + (notmuch-show-insert-part-*/* msg part content-type nth depth button)))) (defun notmuch-show-get-mime-type-of-application/octet-stream (part) ;; If we can deduce a MIME type from the filename of the attachment, @@ -849,8 +849,8 @@ will return nil if the CID is unknown or cannot be retrieved." "Return a list of content handlers for a part of type CONTENT-TYPE." (let (result) (mapc (lambda (func) - (if (functionp func) - (push func result))) + (when (functionp func) + (push func result))) ;; Reverse order of prefrence. (list (intern (concat "notmuch-show-insert-part-*/*")) (intern (concat @@ -1080,10 +1080,10 @@ is t, hide the part initially and show the button." (insert "\n")) (setq content-end (point-marker)) ;; Indent according to the depth in the thread. - (if notmuch-show-indent-content - (indent-rigidly content-start - content-end - (* notmuch-show-indent-messages-width depth))) + (when notmuch-show-indent-content + (indent-rigidly content-start + content-end + (* notmuch-show-indent-messages-width depth))) (setq message-end (point-max-marker)) ;; Save the extents of this message over the whole text of the ;; message. @@ -1288,7 +1288,8 @@ and THREAD. The next query is THREAD alone, and serves as a fallback if the prior matches no messages." (let (queries) (push (list thread) queries) - (if context (push (list thread "and (" context ")") queries)) + (when context + (push (list thread "and (" context ")") queries)) queries)) (defun notmuch-show--build-buffer (&optional state) @@ -1785,9 +1786,9 @@ Reshows the current thread with matches defined by the new query-string." (let (message-ids done) (goto-char (point-min)) (while (not done) - (if (notmuch-show-message-visible-p) - (setq message-ids - (append message-ids (list (notmuch-show-get-message-id))))) + (when (notmuch-show-message-visible-p) + (setq message-ids + (append message-ids (list (notmuch-show-get-message-id))))) (setq done (not (notmuch-show-goto-message-next)))) message-ids))) @@ -1842,8 +1843,8 @@ archives the entire current thread, (apply changes in thread from the search from which this thread was originally shown." (interactive) - (if (notmuch-show-advance) - (notmuch-show-archive-thread-then-next))) + (when (notmuch-show-advance) + (notmuch-show-archive-thread-then-next))) (defun notmuch-show-rewind () "Backup through the thread (reverse scrolling compared to \ diff --git a/emacs/notmuch-tree.el b/emacs/notmuch-tree.el index a5ae0edb..6a619ec2 100644 --- a/emacs/notmuch-tree.el +++ b/emacs/notmuch-tree.el @@ -967,19 +967,19 @@ Complete list of currently available key bindings: (never-found-target-thread nil)) (when (memq status '(exit signal)) (kill-buffer (process-get proc 'parse-buf)) - (if (buffer-live-p buffer) - (with-current-buffer buffer - (save-excursion - (let ((inhibit-read-only t) - (atbob (bobp))) - (goto-char (point-max)) - (if (eq status 'signal) - (insert "Incomplete search results (tree view process was killed).\n")) - (when (eq status 'exit) - (insert "End of search results.") - (unless (= exit-status 0) - (insert (format " (process returned %d)" exit-status))) - (insert "\n"))))))))) + (when (buffer-live-p buffer) + (with-current-buffer buffer + (save-excursion + (let ((inhibit-read-only t) + (atbob (bobp))) + (goto-char (point-max)) + (when (eq status 'signal) + (insert "Incomplete search results (tree view process was killed).\n")) + (when (eq status 'exit) + (insert "End of search results.") + (unless (= exit-status 0) + (insert (format " (process returned %d)" exit-status))) + (insert "\n"))))))))) (defun notmuch-tree-process-filter (proc string) "Process and filter the output of \"notmuch show\" for tree view." @@ -1023,8 +1023,8 @@ the same as for the function notmuch-tree." (and query-context (concat " and (" query-context ")")))) (message-arg (if unthreaded "--unthreaded" "--entire-thread"))) - (if (equal (car (process-lines notmuch-command "count" search-args)) "0") - (setq search-args basic-query)) + (when (equal (car (process-lines notmuch-command "count" search-args)) "0") + (setq search-args basic-query)) (notmuch-tag-clear-cache) (let ((proc (notmuch-start-notmuch "notmuch-tree" (current-buffer) #'notmuch-tree-process-sentinel diff --git a/emacs/notmuch-wash.el b/emacs/notmuch-wash.el index 31fda61f..ce4b9637 100644 --- a/emacs/notmuch-wash.el +++ b/emacs/notmuch-wash.el @@ -216,8 +216,8 @@ that PREFIX should not include a newline." (goto-char (1+ end)) (save-excursion (goto-char beg) - (if prefix - (insert-before-markers prefix)) + (when prefix + (insert-before-markers prefix)) (let ((button-beg (point))) (insert-before-markers (notmuch-wash-button-label overlay) "\n") (let ((button (make-button button-beg (1- (point)) @@ -229,13 +229,13 @@ that PREFIX should not include a newline." "Excerpt citations and up to one signature." (goto-char (point-min)) (beginning-of-line) - (if (and (< (point) (point-max)) - (re-search-forward notmuch-wash-original-regexp nil t)) - (let* ((msg-start (match-beginning 0)) - (msg-end (point-max)) - (msg-lines (count-lines msg-start msg-end))) - (notmuch-wash-region-to-button - msg msg-start msg-end "original"))) + (when (and (< (point) (point-max)) + (re-search-forward notmuch-wash-original-regexp nil t)) + (let* ((msg-start (match-beginning 0)) + (msg-end (point-max)) + (msg-lines (count-lines msg-start msg-end))) + (notmuch-wash-region-to-button + msg msg-start msg-end "original"))) (while (and (< (point) (point-max)) (re-search-forward notmuch-wash-citation-regexp nil t)) (let* ((cite-start (match-beginning 0)) @@ -254,21 +254,21 @@ that PREFIX should not include a newline." (notmuch-wash-region-to-button msg hidden-start (point-marker) "citation"))))) - (if (and (not (eobp)) - (re-search-forward notmuch-wash-signature-regexp nil t)) - (let* ((sig-start (match-beginning 0)) - (sig-end (match-end 0)) - (sig-lines (count-lines sig-start (point-max)))) - (if (<= sig-lines notmuch-wash-signature-lines-max) - (let ((sig-start-marker (make-marker)) - (sig-end-marker (make-marker))) - (set-marker sig-start-marker sig-start) - (set-marker sig-end-marker (point-max)) - (overlay-put (make-overlay sig-start-marker sig-end-marker) - 'face 'message-cited-text) - (notmuch-wash-region-to-button - msg sig-start-marker sig-end-marker - "signature")))))) + (when (and (not (eobp)) + (re-search-forward notmuch-wash-signature-regexp nil t)) + (let* ((sig-start (match-beginning 0)) + (sig-end (match-end 0)) + (sig-lines (count-lines sig-start (point-max)))) + (when (<= sig-lines notmuch-wash-signature-lines-max) + (let ((sig-start-marker (make-marker)) + (sig-end-marker (make-marker))) + (set-marker sig-start-marker sig-start) + (set-marker sig-end-marker (point-max)) + (overlay-put (make-overlay sig-start-marker sig-end-marker) + 'face 'message-cited-text) + (notmuch-wash-region-to-button + msg sig-start-marker sig-end-marker + "signature")))))) ;; @@ -286,12 +286,12 @@ that PREFIX should not include a newline." (delete-region (match-beginning 1) (match-end 1))) ;; Remove a leading blank line. (goto-char (point-min)) - (if (looking-at "\n") - (delete-region (match-beginning 0) (match-end 0))) + (when (looking-at "\n") + (delete-region (match-beginning 0) (match-end 0))) ;; Remove a trailing blank line. (goto-char (point-max)) - (if (looking-at "\n") - (delete-region (match-beginning 0) (match-end 0)))) + (when (looking-at "\n") + (delete-region (match-beginning 0) (match-end 0)))) ;; @@ -399,12 +399,12 @@ for error." (patch-end (point-max)) part) (goto-char patch-start) - (if (or - ;; Patch ends with signature. - (re-search-forward notmuch-wash-signature-regexp nil t) - ;; Patch ends with bugtraq comment. - (re-search-forward "^\\*\\*\\* " nil t)) - (setq patch-end (match-beginning 0))) + (when (or + ;; Patch ends with signature. + (re-search-forward notmuch-wash-signature-regexp nil t) + ;; Patch ends with bugtraq comment. + (re-search-forward "^\\*\\*\\* " nil t)) + (setq patch-end (match-beginning 0))) (save-restriction (narrow-to-region patch-start patch-end) (setq part (plist-put part :content-type "inline patch")) diff --git a/emacs/notmuch.el b/emacs/notmuch.el index 93e92b39..4fc338e2 100644 --- a/emacs/notmuch.el +++ b/emacs/notmuch.el @@ -674,28 +674,28 @@ of the result." (when (memq status '(exit signal)) (catch 'return (kill-buffer (process-get proc 'parse-buf)) - (if (buffer-live-p buffer) - (with-current-buffer buffer - (save-excursion - (let ((inhibit-read-only t) - (atbob (bobp))) - (goto-char (point-max)) - (if (eq status 'signal) - (insert "Incomplete search results (search process was killed).\n")) - (when (eq status 'exit) - (insert "End of search results.\n") - ;; For version mismatch, there's no point in - ;; showing the search buffer - (when (or (= exit-status 20) (= exit-status 21)) - (kill-buffer) - (throw 'return nil)) - (if (and atbob + (when (buffer-live-p buffer) + (with-current-buffer buffer + (save-excursion + (let ((inhibit-read-only t) + (atbob (bobp))) + (goto-char (point-max)) + (when (eq status 'signal) + (insert "Incomplete search results (search process was killed).\n")) + (when (eq status 'exit) + (insert "End of search results.\n") + ;; For version mismatch, there's no point in + ;; showing the search buffer + (when (or (= exit-status 20) (= exit-status 21)) + (kill-buffer) + (throw 'return nil)) + (when (and atbob (not (string= notmuch-search-target-thread "found"))) - (set 'never-found-target-thread t))))) - (when (and never-found-target-thread - notmuch-search-target-line) - (goto-char (point-min)) - (forward-line (1- notmuch-search-target-line))))))))) + (set 'never-found-target-thread t))))) + (when (and never-found-target-thread + notmuch-search-target-line) + (goto-char (point-min)) + (forward-line (1- notmuch-search-target-line))))))))) (define-widget 'notmuch--custom-face-edit 'lazy "Custom face edit with a tag Edit Face" @@ -760,31 +760,31 @@ non-authors is found, assume that all of the authors match." (invisible-string "") (padding "")) ;; Truncate the author string to fit the specification. - (if (> (length formatted-authors) - (length formatted-sample)) - (let ((visible-length (- (length formatted-sample) - (length "... ")))) - ;; Truncate the visible string according to the width of - ;; the display string. - (setq visible-string (substring formatted-authors 0 visible-length)) - (setq invisible-string (substring formatted-authors visible-length)) - ;; If possible, truncate the visible string at a natural - ;; break (comma or pipe), as incremental search doesn't - ;; match across the visible/invisible border. - (when (string-match "\\(.*\\)\\([,|] \\)\\([^,|]*\\)" visible-string) - ;; Second clause is destructive on `visible-string', so - ;; order is important. - (setq invisible-string (concat (match-string 3 visible-string) - invisible-string)) - (setq visible-string (concat (match-string 1 visible-string) - (match-string 2 visible-string)))) - ;; `visible-string' may be shorter than the space allowed - ;; by `format-string'. If so we must insert some padding - ;; after `invisible-string'. - (setq padding (make-string (- (length formatted-sample) - (length visible-string) - (length "...")) - ? )))) + (when (> (length formatted-authors) + (length formatted-sample)) + (let ((visible-length (- (length formatted-sample) + (length "... ")))) + ;; Truncate the visible string according to the width of + ;; the display string. + (setq visible-string (substring formatted-authors 0 visible-length)) + (setq invisible-string (substring formatted-authors visible-length)) + ;; If possible, truncate the visible string at a natural + ;; break (comma or pipe), as incremental search doesn't + ;; match across the visible/invisible border. + (when (string-match "\\(.*\\)\\([,|] \\)\\([^,|]*\\)" visible-string) + ;; Second clause is destructive on `visible-string', so + ;; order is important. + (setq invisible-string (concat (match-string 3 visible-string) + invisible-string)) + (setq visible-string (concat (match-string 1 visible-string) + (match-string 2 visible-string)))) + ;; `visible-string' may be shorter than the space allowed + ;; by `format-string'. If so we must insert some padding + ;; after `invisible-string'. + (setq padding (make-string (- (length formatted-sample) + (length visible-string) + (length "...")) + ? )))) ;; Use different faces to show matching and non-matching authors. (if (string-match "\\(.*\\)|\\(.*\\)" visible-string) ;; The visible string contains both matching and @@ -1006,8 +1006,8 @@ the configured default sort order." (notmuch-tag-clear-cache) (let ((proc (get-buffer-process (current-buffer))) (inhibit-read-only t)) - (if proc - (error "notmuch search process already running for query `%s'" query)) + (when proc + (error "notmuch search process already running for query `%s'" query)) (erase-buffer) (goto-char (point-min)) (save-excursion @@ -1156,7 +1156,7 @@ beginning of the line." (provide 'notmuch) ;; After provide to avoid loops if notmuch was require'd via notmuch-init-file. -(if init-file-user ; don't load init file if the -q option was used. - (load notmuch-init-file t t nil t)) +(when init-file-user ; don't load init file if the -q option was used. + (load notmuch-init-file t t nil t)) ;;; notmuch.el ends here diff --git a/test/test-lib.el b/test/test-lib.el index a1233390..6a39bbe2 100644 --- a/test/test-lib.el +++ b/test/test-lib.el @@ -36,7 +36,7 @@ ;; Work around a bug in emacs 23.1 and emacs 23.2 which prevents ;; noninteractive (kill-emacs) from emacsclient. -(if (and (= emacs-major-version 23) (< emacs-minor-version 3)) +(when (and (= emacs-major-version 23) (< emacs-minor-version 3)) (defadvice kill-emacs (before disable-yes-or-no-p activate) "Disable yes-or-no-p before executing kill-emacs" (defun yes-or-no-p (prompt) t))) @@ -46,7 +46,7 @@ ;; seems to be present in Emacs 23.1. ;; Running `list-processes' after `accept-process-output' seems to work ;; around this problem. -(if (and (= emacs-major-version 23) (= emacs-minor-version 1)) +(when (and (= emacs-major-version 23) (= emacs-minor-version 1)) (defadvice accept-process-output (after run-list-processes activate) "run list-processes after executing accept-process-output" (list-processes))) From 99b6e780c8714f90f476e989e2d1cd973cf5bbb9 Mon Sep 17 00:00:00 2001 From: Jonas Bernoulli Date: Sat, 8 Aug 2020 13:49:44 +0200 Subject: [PATCH 344/427] emacs: Use one or three lines for 'if' forms Putting the COND and THEN parts on the same line but ELSE on a separate line makes it harder to determine if there actually is an ELSE part. --- emacs/notmuch-lib.el | 6 ++++-- emacs/notmuch-tag.el | 3 ++- emacs/notmuch-tree.el | 3 ++- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/emacs/notmuch-lib.el b/emacs/notmuch-lib.el index da48bb86..4496ecd2 100644 --- a/emacs/notmuch-lib.el +++ b/emacs/notmuch-lib.el @@ -611,7 +611,8 @@ the given type." ,@(and process-crypto '("--decrypt=true")) ,(notmuch-id-to-query (plist-get msg :id)))) (coding-system-for-read - (if binaryp 'no-conversion + (if binaryp + 'no-conversion (let ((coding-system (mm-charset-to-coding-system (plist-get part :content-charset)))) @@ -680,7 +681,8 @@ current buffer, if possible." ;; `gnus-decoded' charset. Otherwise, we'll fetch the binary ;; part content and let mm-* decode it. (let* ((have-content (plist-member part :content)) - (charset (if have-content 'gnus-decoded + (charset (if have-content + 'gnus-decoded (plist-get part :content-charset))) (handle (mm-make-handle (current-buffer) `(,content-type (charset . ,charset))))) diff --git a/emacs/notmuch-tag.el b/emacs/notmuch-tag.el index 1067f185..e71de041 100644 --- a/emacs/notmuch-tag.el +++ b/emacs/notmuch-tag.el @@ -532,7 +532,8 @@ and vice versa." (and (symbolp name) (symbol-name name)))) (name-string (if name - (if reverse (concat "Reverse " name) + (if reverse + (concat "Reverse " name) name) (mapconcat #'identity tag-change " ")))) (push (list key name-string diff --git a/emacs/notmuch-tree.el b/emacs/notmuch-tree.el index 6a619ec2..b498db07 100644 --- a/emacs/notmuch-tree.el +++ b/emacs/notmuch-tree.el @@ -1009,7 +1009,8 @@ the same as for the function notmuch-tree." (setq notmuch-tree-basic-query basic-query) (setq notmuch-tree-query-context (if (or (string= query-context "") (string= query-context "*")) - nil query-context)) + nil + query-context)) (setq notmuch-tree-target-msg target) (setq notmuch-tree-open-target open-target) ;; Set the default value for `notmuch-show-process-crypto' in this From a2388bc56e55da5d5695816818274f8a84b0ed92 Mon Sep 17 00:00:00 2001 From: Jonas Bernoulli Date: Sat, 8 Aug 2020 13:49:45 +0200 Subject: [PATCH 345/427] emacs: Extend face to window edge again Since Emacs 27 each face has to be explicitly configured to "extend to the edge of the window". Without doing that the face used for the newline character only has an effect that spans "one character" (i.e. it looks like there is a single trailing space character). We don't want that so extend the face in Emacs 27, so that it looks the same as it did in older Emacs releases. We have to do this conditionally, otherwise older Emacsen would choke on it. --- emacs/notmuch.el | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/emacs/notmuch.el b/emacs/notmuch.el index 4fc338e2..5562ad10 100644 --- a/emacs/notmuch.el +++ b/emacs/notmuch.el @@ -273,8 +273,12 @@ there will be called at other points of notmuch execution." (goto-char (point-min))) (defface notmuch-message-summary-face - '((((class color) (background light)) (:background "#f0f0f0")) - (((class color) (background dark)) (:background "#303030"))) + `((((class color) (background light)) + ,@(and (>= emacs-major-version 27) '(:extend t)) + (:background "#f0f0f0")) + (((class color) (background dark)) + ,@(and (>= emacs-major-version 27) '(:extend t)) + (:background "#303030"))) "Face for the single-line message summary in notmuch-show-mode." :group 'notmuch-show :group 'notmuch-faces) From 82390b2807b14c1ab1eabc7ff0e68ccf1dda736e Mon Sep 17 00:00:00 2001 From: Jonas Bernoulli Date: Sat, 8 Aug 2020 13:49:46 +0200 Subject: [PATCH 346/427] emacs: Fix some function declarations --- emacs/notmuch-tag.el | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/emacs/notmuch-tag.el b/emacs/notmuch-tag.el index e71de041..1cef17e1 100644 --- a/emacs/notmuch-tag.el +++ b/emacs/notmuch-tag.el @@ -32,9 +32,10 @@ (require 'notmuch-lib) -(declare-function notmuch-search-tag "notmuch" tag-changes) -(declare-function notmuch-show-tag "notmuch-show" tag-changes) -(declare-function notmuch-tree-tag "notmuch-tree" tag-changes) +(declare-function notmuch-search-tag "notmuch" + (tag-changes &optional beg end only-matched)) +(declare-function notmuch-show-tag "notmuch-show" (tag-changes)) +(declare-function notmuch-tree-tag "notmuch-tree" (tag-changes)) (autoload 'notmuch-jump "notmuch-jump") From 14c4533c43ce319a8ba353f41e8c2f6be31164aa Mon Sep 17 00:00:00 2001 From: Jonas Bernoulli Date: Sat, 8 Aug 2020 13:49:47 +0200 Subject: [PATCH 347/427] emacs: No longer define notmuch-hello-mode-map as a function It was defined as such for a decade; ever since a56010ac8b89a2489eee5c78469f05cee85ec858 but there wasn't a reason to do that then nor is there now. --- emacs/notmuch-hello.el | 1 - 1 file changed, 1 deletion(-) diff --git a/emacs/notmuch-hello.el b/emacs/notmuch-hello.el index c17b46bc..876d8ef1 100644 --- a/emacs/notmuch-hello.el +++ b/emacs/notmuch-hello.el @@ -687,7 +687,6 @@ with `notmuch-hello-query-counts'." (define-key map (kbd "") 'widget-backward) map) "Keymap for \"notmuch hello\" buffers.") -(fset 'notmuch-hello-mode-map notmuch-hello-mode-map) (define-derived-mode notmuch-hello-mode fundamental-mode "notmuch-hello" "Major mode for convenient notmuch navigation. This is your entry portal into notmuch. From 177cd31fbdff93254d576b737b55656d3a6086b5 Mon Sep 17 00:00:00 2001 From: Jonas Bernoulli Date: Sat, 8 Aug 2020 13:49:48 +0200 Subject: [PATCH 348/427] emacs: notmuch-poll: Let the user know we are polling It is done synchronously and it can take a while, so we should let the user know what is going on. --- emacs/notmuch-lib.el | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/emacs/notmuch-lib.el b/emacs/notmuch-lib.el index 4496ecd2..6ff351d7 100644 --- a/emacs/notmuch-lib.el +++ b/emacs/notmuch-lib.el @@ -259,11 +259,13 @@ on the command line, and then retry your notmuch command"))) Invokes `notmuch-poll-script', \"notmuch new\", or does nothing depending on the value of `notmuch-poll-script'." (interactive) + (message "Polling mail...") (if (stringp notmuch-poll-script) (unless (string= notmuch-poll-script "") (unless (equal (call-process notmuch-poll-script nil nil) 0) (error "Notmuch: poll script `%s' failed!" notmuch-poll-script))) - (notmuch-call-notmuch-process "new"))) + (notmuch-call-notmuch-process "new")) + (message "Polling mail...done")) (defun notmuch-bury-or-kill-this-buffer () "Undisplay the current buffer. From c4541353765dec837c1c2f912b1bf6661827429c Mon Sep 17 00:00:00 2001 From: Jonas Bernoulli Date: Sat, 8 Aug 2020 13:49:49 +0200 Subject: [PATCH 349/427] emacs: Use makefile-gmake-mode in Makefile*s Use `makefile-gmake-mode' instead of `makefile-mode' because the former also highlights ifdef et al. while the latter does not. "./Makefile.global" and one "Makefile.local" failed to specify any major mode at all but doing so is necessary because Emacs does not automatically figure out that these are Makefiles (of any flavor). --- Makefile.global | 1 + Makefile.local | 2 +- bindings/Makefile.local | 2 +- compat/Makefile.local | 2 +- completion/Makefile.local | 2 +- doc/Makefile.local | 2 +- emacs/Makefile.local | 2 +- lib/Makefile.local | 2 +- parse-time-string/Makefile.local | 2 ++ performance-test/Makefile.local | 2 +- test/Makefile.local | 2 +- util/Makefile.local | 2 +- 12 files changed, 13 insertions(+), 10 deletions(-) diff --git a/Makefile.global b/Makefile.global index 4fd796e3..cd489ef2 100644 --- a/Makefile.global +++ b/Makefile.global @@ -1,3 +1,4 @@ +# -*- makefile-gmake -*- # Here's the (hopefully simple) versioning scheme. # # Releases of notmuch have a two-digit version (0.1, 0.2, etc.). We diff --git a/Makefile.local b/Makefile.local index 39f36d50..156c8ce1 100644 --- a/Makefile.local +++ b/Makefile.local @@ -1,4 +1,4 @@ -# -*- makefile -*- +# -*- makefile-gmake -*- .PHONY: all all: notmuch notmuch-shared build-man build-info ruby-bindings python-cffi-bindings diff --git a/bindings/Makefile.local b/bindings/Makefile.local index 19ddd6ea..bc960bbc 100644 --- a/bindings/Makefile.local +++ b/bindings/Makefile.local @@ -1,4 +1,4 @@ -# -*- makefile -*- +# -*- makefile-gmake -*- dir := bindings diff --git a/compat/Makefile.local b/compat/Makefile.local index bcb9f0ec..2ee1b399 100644 --- a/compat/Makefile.local +++ b/compat/Makefile.local @@ -1,4 +1,4 @@ -# -*- makefile -*- +# -*- makefile-gmake -*- dir := compat extra_cflags += -I$(srcdir)/$(dir) diff --git a/completion/Makefile.local b/completion/Makefile.local index 8e86c9d2..54df463c 100644 --- a/completion/Makefile.local +++ b/completion/Makefile.local @@ -1,4 +1,4 @@ -# -*- makefile -*- +# -*- makefile-gmake -*- dir := completion diff --git a/doc/Makefile.local b/doc/Makefile.local index 30411341..60bd7184 100644 --- a/doc/Makefile.local +++ b/doc/Makefile.local @@ -1,4 +1,4 @@ -# -*- makefile -*- +# -*- makefile-gmake -*- dir := doc diff --git a/emacs/Makefile.local b/emacs/Makefile.local index 141f5868..d1b320c3 100644 --- a/emacs/Makefile.local +++ b/emacs/Makefile.local @@ -1,4 +1,4 @@ -# -*- makefile -*- +# -*- makefile-gmake -*- dir := emacs emacs_sources := \ diff --git a/lib/Makefile.local b/lib/Makefile.local index 5dc057c0..a6400126 100644 --- a/lib/Makefile.local +++ b/lib/Makefile.local @@ -1,4 +1,4 @@ -# -*- makefile -*- +# -*- makefile-gmake -*- dir := lib diff --git a/parse-time-string/Makefile.local b/parse-time-string/Makefile.local index 53534f3e..ee8030cc 100644 --- a/parse-time-string/Makefile.local +++ b/parse-time-string/Makefile.local @@ -1,3 +1,5 @@ +# -*- makefile-gmake -*- + dir := parse-time-string extra_cflags += -I$(srcdir)/$(dir) diff --git a/performance-test/Makefile.local b/performance-test/Makefile.local index 9dc260e3..b9f580c7 100644 --- a/performance-test/Makefile.local +++ b/performance-test/Makefile.local @@ -1,4 +1,4 @@ -# -*- makefile -*- +# -*- makefile-gmake -*- dir := performance-test diff --git a/test/Makefile.local b/test/Makefile.local index 47244e8f..40574739 100644 --- a/test/Makefile.local +++ b/test/Makefile.local @@ -1,4 +1,4 @@ -# -*- makefile -*- +# -*- makefile-gmake -*- dir := test diff --git a/util/Makefile.local b/util/Makefile.local index f5d72f79..7ef029a5 100644 --- a/util/Makefile.local +++ b/util/Makefile.local @@ -1,4 +1,4 @@ -# -*- makefile -*- +# -*- makefile-gmake -*- dir := util extra_cflags += -I$(srcdir)/$(dir) From e63f37a4a90c5d2a399a99a0566b6e1dfea263aa Mon Sep 17 00:00:00 2001 From: Jonas Bernoulli Date: Sat, 8 Aug 2020 13:49:50 +0200 Subject: [PATCH 350/427] emacs: Improve doc-strings - The first sentence should fit on the first line in full. This is even the case when that causes the line to get a bit long. If it gets very long, then it should be made shorter. - Even even the second sentence would fit on the first line, if it just provides some details, then it shouldn't be done. - Symbols are quoted like `so'. - There is no clear rule on how to (not) quote non-atomic s-expressions, but quoting like '(this) is definitely weird. - It is a good idea to remember that \" becomes " and to take that in mind when adjusting the automatic filling by hand. - Use the imperative form. - Arguments are written in all uppercase. --- emacs/notmuch-address.el | 42 +++++++++++++++++++++------------------- emacs/notmuch-query.el | 19 +++++++++--------- emacs/notmuch-show.el | 4 ++-- emacs/notmuch-tree.el | 5 +++-- emacs/notmuch.el | 9 ++++----- emacs/rstdoc.el | 6 +++--- 6 files changed, 43 insertions(+), 42 deletions(-) diff --git a/emacs/notmuch-address.el b/emacs/notmuch-address.el index 85531489..cd0ffb67 100644 --- a/emacs/notmuch-address.el +++ b/emacs/notmuch-address.el @@ -36,9 +36,9 @@ This variable is set by calling `notmuch-address-harvest'.") (defvar notmuch-address-full-harvest-finished nil - "t indicates that full completion address harvesting has been -finished. Use notmuch-address--harvest-ready to access as that -will load a saved hash if necessary (and available).") + "t indicates that full completion address harvesting has been finished. +Use notmuch-address--harvest-ready to access as that will load a +saved hash if necessary (and available).") (defun notmuch-address--harvest-ready () "Return t if there is a full address hash available. @@ -54,9 +54,9 @@ If it is a string then that string should be an external program which must take a single argument (searched string) and output a list of completion candidates, one per line. -Alternatively, it can be the symbol 'internal, in which case +Alternatively, it can be the symbol `internal', in which case internal completion is used; the variable -`notmuch-address-internal-completion` can be used to customize +`notmuch-address-internal-completion' can be used to customize this case. Finally, if this variable is nil then address completion is @@ -72,12 +72,12 @@ disabled." (defcustom notmuch-address-internal-completion '(sent nil) "Determines how internal address completion generates candidates. -This should be a list of the form '(DIRECTION FILTER), where +This should be a list of the form (DIRECTION FILTER), where DIRECTION is either sent or received and specifies whether the candidates are searched in messages sent by the user or received by the user (note received by is much faster), and FILTER is -either nil or a filter-string, such as \"date:1y..\" to append -to the query." +either nil or a filter-string, such as \"date:1y..\" to append to +the query." :type '(list :tag "Use internal address completion" (radio :tag "Base completion on messages you have" @@ -101,8 +101,8 @@ to the query." "Filename to save the cached completion addresses. All the addresses notmuch uses for address completion will be -cached in this file. This has obvious privacy implications so you -should make sure it is not somewhere publicly readable." +cached in this file. This has obvious privacy implications so +you should make sure it is not somewhere publicly readable." :type '(choice (const :tag "Off" nil) (file :tag "Filename")) :group 'notmuch-send @@ -110,12 +110,14 @@ should make sure it is not somewhere publicly readable." :group 'notmuch-external) (defcustom notmuch-address-selection-function 'notmuch-address-selection-function - "The function to select address from given list. The function is -called with PROMPT, COLLECTION, and INITIAL-INPUT as arguments -(subset of what `completing-read' can be called with). -While executed the value of `completion-ignore-case' is t. -See documentation of function `notmuch-address-selection-function' -to know how address selection is made by default." + "The function to select address from given list. + +The function is called with PROMPT, COLLECTION, and INITIAL-INPUT +as arguments (subset of what `completing-read' can be called +with). While executed the value of `completion-ignore-case' +is t. See documentation of function +`notmuch-address-selection-function' to know how address +selection is made by default." :type 'function :group 'notmuch-send :group 'notmuch-address @@ -188,9 +190,9 @@ The candidates are taken from `notmuch-address-completions'." candidates)) (defun notmuch-address-options (original) - "Returns a list of completion candidates. Uses either -elisp-based implementation or older implementation requiring -external commands." + "Return a list of completion candidates. +Use either elisp-based implementation or older implementation +requiring external commands." (cond ((eq notmuch-address-command 'internal) (unless (notmuch-address--harvest-ready) @@ -352,7 +354,7 @@ execution, CALLBACK is called when harvesting finishes." "Version format of the save hash.") (defun notmuch-address--get-address-hash () - "Returns the saved address hash as a plist. + "Return the saved address hash as a plist. Returns nil if the save file does not exist, or it does not seem to be a saved address hash." diff --git a/emacs/notmuch-query.el b/emacs/notmuch-query.el index a8e5d11f..3cfccbc3 100644 --- a/emacs/notmuch-query.el +++ b/emacs/notmuch-query.el @@ -47,22 +47,21 @@ is a possibly empty forest of replies." seq))) (defun notmuch-query-map-threads (fn threads) - "Apply FN to every thread in THREADS. Flatten results to a list. - -See the function notmuch-query-get-threads for more information." + "Apply function FN to every thread in THREADS. +Flatten results to a list. See the function +`notmuch-query-get-threads' for more information." (notmuch-query-map-aux 'notmuch-query-map-forest fn threads)) (defun notmuch-query-map-forest (fn forest) - "Apply function to every message in a forest. Flatten results to a list. - -See the function notmuch-query-get-threads for more information. -" + "Apply function FN to every message in FOREST. +Flatten results to a list. See the function +`notmuch-query-get-threads' for more information." (notmuch-query-map-aux 'notmuch-query-map-tree fn forest)) (defun notmuch-query-map-tree (fn tree) - "Apply function FN to every message in TREE. Flatten results to a list. - -See the function notmuch-query-get-threads for more information." + "Apply function FN to every message in TREE. +Flatten results to a list. See the function +`notmuch-query-get-threads' for more information." (cons (funcall fn (car tree)) (notmuch-query-map-forest fn (cadr tree)))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; diff --git a/emacs/notmuch-show.el b/emacs/notmuch-show.el index f1cd92fe..a210898b 100644 --- a/emacs/notmuch-show.el +++ b/emacs/notmuch-show.el @@ -446,8 +446,8 @@ parsing fails." (error (cons address nil)))) (defun notmuch-show-clean-address (address) - "Try to clean a single email ADDRESS for display. Return -unchanged ADDRESS if parsing fails." + "Try to clean a single email ADDRESS for display. +Return unchanged ADDRESS if parsing fails." (let* ((clean-address (notmuch-clean-address address)) (p-address (car clean-address)) (p-name (cdr clean-address))) diff --git a/emacs/notmuch-tree.el b/emacs/notmuch-tree.el index b498db07..364da240 100644 --- a/emacs/notmuch-tree.el +++ b/emacs/notmuch-tree.el @@ -587,7 +587,7 @@ Shows in split pane or whole window according to value of (scroll-up))))) (defun notmuch-tree-scroll-message-window-back () - "Scroll the message window back(if it exists)." + "Scroll the message window back (if it exists)." (interactive) (when (window-live-p notmuch-tree-message-window) (with-selected-window notmuch-tree-message-window @@ -596,7 +596,8 @@ Shows in split pane or whole window according to value of (scroll-down))))) (defun notmuch-tree-scroll-or-next () - "Scroll the message window. If it at end go to next message." + "Scroll the message window. +If it at end go to next message." (interactive) (when (notmuch-tree-scroll-message-window) (notmuch-tree-next-matching-message))) diff --git a/emacs/notmuch.el b/emacs/notmuch.el index 5562ad10..75fe6900 100644 --- a/emacs/notmuch.el +++ b/emacs/notmuch.el @@ -432,11 +432,10 @@ returns nil." (defun notmuch-search-foreach-result (beg end fn) "Invoke FN for each result between BEG and END. -FN should take one argument. It will be applied to the -character position of the beginning of each result that overlaps -the region between points BEG and END. As a special case, if (= -BEG END), FN will be applied to the result containing point -BEG." +FN should take one argument. It will be applied to the character +position of the beginning of each result that overlaps the region +between points BEG and END. As a special case, if (= BEG END), +FN will be applied to the result containing point BEG." (let ((pos (notmuch-search-result-beginning beg)) ;; End must be a marker in case fn changes the ;; text. diff --git a/emacs/rstdoc.el b/emacs/rstdoc.el index 2af91186..92a337c8 100644 --- a/emacs/rstdoc.el +++ b/emacs/rstdoc.el @@ -36,11 +36,11 @@ (provide 'rstdoc) (defun rstdoc-batch-extract () - "Extract docstrings to and from the files on the command line" + "Extract docstrings to and from the files on the command line." (apply #'rstdoc-extract command-line-args-left)) (defun rstdoc-extract (in-file out-file) - "Write docstrings from IN-FILE to OUT-FILE" + "Write docstrings from IN-FILE to OUT-FILE." (load-file in-file) (let* ((definitions (cdr (assoc (expand-file-name in-file) load-history))) (doc-hash (make-hash-table :test 'eq))) @@ -72,7 +72,7 @@ ("\\([^\\]\\)'" . "\\1`") ("^[[:space:]\t]*$" . "|br|") ("^[[:space:]\t]" . "|indent| ")) - "list of (regex . replacement) pairs") + "List of (regex . replacement) pairs.") (defun rstdoc--rst-quote-string (str) (with-temp-buffer From c2e9ec17fd46e67c7cf1f06d4c6da2ed59da34d9 Mon Sep 17 00:00:00 2001 From: Jonas Bernoulli Date: Sat, 8 Aug 2020 13:49:51 +0200 Subject: [PATCH 351/427] emacs: Autoload notmuch-jump-search only once This function is being autoloaded using an autoload cookie, so it shouldn't additionally be autoloaded using an `autoload' form. When building libraries we don't actually load the autoloads file and dropping the `autoload' form results in an error, which reveals a so far unspecified dependency: `notmuch-tree' needs `notmuch-jump'. Before this commit compiling (or even just loading) `notmuch-tree' resulted in `notmuch-jump' being loaded because the former requires `notmuch-lib', which autoloaded `notmuch-jump-search'. The bug was that this dependency was not explicitly specified, which we fix by adding the respective `require' form. --- emacs/notmuch-lib.el | 3 --- emacs/notmuch-tree.el | 1 + 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/emacs/notmuch-lib.el b/emacs/notmuch-lib.el index 6ff351d7..b86c44ed 100644 --- a/emacs/notmuch-lib.el +++ b/emacs/notmuch-lib.el @@ -35,9 +35,6 @@ (defconst notmuch-emacs-version "unknown" "Placeholder variable when notmuch-version.el[c] is not available.")) -(autoload 'notmuch-jump-search "notmuch-jump" - "Jump to a saved search by shortcut key." t) - (defgroup notmuch nil "Notmuch mail reader for Emacs." :group 'mail) diff --git a/emacs/notmuch-tree.el b/emacs/notmuch-tree.el index 364da240..b538cef9 100644 --- a/emacs/notmuch-tree.el +++ b/emacs/notmuch-tree.el @@ -33,6 +33,7 @@ (require 'notmuch-show) (require 'notmuch-tag) (require 'notmuch-parser) +(require 'notmuch-jump) (declare-function notmuch-search "notmuch" (&optional query oldest-first target-thread target-line)) From 73cc4105aa27f13464fcfdfe958e553842395789 Mon Sep 17 00:00:00 2001 From: Jonas Bernoulli Date: Sat, 8 Aug 2020 13:49:52 +0200 Subject: [PATCH 352/427] emacs: Autoload notmuch-jump using an autoload cookie Doing that is better than using an `autoload' form because the latter may result in dependencies getting hidden and indeed it turns out we have to declare `notmuch-jump' in "notmuch-tag.el". --- emacs/notmuch-jump.el | 1 + emacs/notmuch-tag.el | 3 +-- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/emacs/notmuch-jump.el b/emacs/notmuch-jump.el index e302fe00..1e2d0497 100644 --- a/emacs/notmuch-jump.el +++ b/emacs/notmuch-jump.el @@ -73,6 +73,7 @@ please customize shortcut keys in notmuch-saved-searches.")))) (defvar notmuch-jump--action nil) +;;;###autoload (defun notmuch-jump (action-map prompt) "Interactively prompt for one of the keys in ACTION-MAP. diff --git a/emacs/notmuch-tag.el b/emacs/notmuch-tag.el index 1cef17e1..ccc1321f 100644 --- a/emacs/notmuch-tag.el +++ b/emacs/notmuch-tag.el @@ -36,8 +36,7 @@ (tag-changes &optional beg end only-matched)) (declare-function notmuch-show-tag "notmuch-show" (tag-changes)) (declare-function notmuch-tree-tag "notmuch-tree" (tag-changes)) - -(autoload 'notmuch-jump "notmuch-jump") +(declare-function notmuch-jump "notmuch-jump" (action-map prompt)) (define-widget 'notmuch-tag-key-type 'list "A single key tagging binding." From 73b8f0b8d71af395667022395b6d6bb692c3aaf2 Mon Sep 17 00:00:00 2001 From: Jonas Bernoulli Date: Sat, 8 Aug 2020 13:49:53 +0200 Subject: [PATCH 353/427] emacs: Various cosmetic changes --- emacs/make-deps.el | 3 ++- emacs/notmuch-lib.el | 8 +++----- emacs/notmuch-parser.el | 9 +++------ emacs/notmuch-show.el | 18 +++++++----------- emacs/rstdoc.el | 9 ++++----- test/test-lib.el | 31 +++++++++++++++---------------- 6 files changed, 34 insertions(+), 44 deletions(-) diff --git a/emacs/make-deps.el b/emacs/make-deps.el index dcac319c..91f4ef3d 100644 --- a/emacs/make-deps.el +++ b/emacs/make-deps.el @@ -36,7 +36,8 @@ This prints make dependencies to `standard-output' based on the top-level `require' expressions in the current buffer. Paths in rules will be given relative to DIR, or `default-directory'." - (setq dir (or dir default-directory)) + (unless dir + (setq dir default-directory)) (save-excursion (goto-char (point-min)) (condition-case nil diff --git a/emacs/notmuch-lib.el b/emacs/notmuch-lib.el index b86c44ed..05d86ea1 100644 --- a/emacs/notmuch-lib.el +++ b/emacs/notmuch-lib.el @@ -53,9 +53,8 @@ (defgroup notmuch-send nil "Sending messages from Notmuch." - :group 'notmuch) - -(custom-add-to-group 'notmuch-send 'message 'custom-group) + :group 'notmuch + :group 'message) (defgroup notmuch-tag nil "Tags and tagging in Notmuch." @@ -782,8 +781,7 @@ signaled error. This function does not return." (insert extra) (unless (bolp) (newline))))) - (error "%s" - (concat msg (and extra " (see *Notmuch errors* for more details)")))) + (error "%s%s" msg (if extra " (see *Notmuch errors* for more details)" ""))) (defun notmuch-check-async-exit-status (proc msg &optional command err) "If PROC exited abnormally, pop up an error buffer and signal an error. diff --git a/emacs/notmuch-parser.el b/emacs/notmuch-parser.el index fbcfc2ef..3aa5bd8f 100644 --- a/emacs/notmuch-parser.el +++ b/emacs/notmuch-parser.el @@ -39,12 +39,9 @@ The parser always consumes input from point in the current buffer. Hence, the caller is allowed to delete any data before point and may resynchronize after an error by moving point." (vector 'notmuch-sexp-parser - ;; List depth - 0 - ;; Partial parse position marker - nil - ;; Partial parse state - nil)) + 0 ; List depth + nil ; Partial parse position marker + nil)) ; Partial parse state (defmacro notmuch-sexp--depth (sp) `(aref ,sp 1)) (defmacro notmuch-sexp--partial-pos (sp) `(aref ,sp 2)) diff --git a/emacs/notmuch-show.el b/emacs/notmuch-show.el index a210898b..eb07e450 100644 --- a/emacs/notmuch-show.el +++ b/emacs/notmuch-show.el @@ -505,21 +505,17 @@ message at DEPTH in the current thread." (defun notmuch-show-insert-part-header (nth content-type declared-type &optional name comment) - (let ((button) - (base-label (concat (and name (concat name ": ")) + (let ((base-label (concat (and name (concat name ": ")) declared-type (and (not (string-equal declared-type content-type)) (concat " (as " content-type ")")) comment))) - (setq button - (insert-button - (concat "[ " base-label " ]") - :base-label base-label - :type 'notmuch-show-part-button-type - :notmuch-part-hidden nil)) - (insert "\n") - ;; return button - button)) + (prog1 (insert-button + (concat "[ " base-label " ]") + :base-label base-label + :type 'notmuch-show-part-button-type + :notmuch-part-hidden nil) + (insert "\n")))) (defun notmuch-show-toggle-part-invisibility (&optional button) (interactive) diff --git a/emacs/rstdoc.el b/emacs/rstdoc.el index 92a337c8..41390bbe 100644 --- a/emacs/rstdoc.el +++ b/emacs/rstdoc.el @@ -24,7 +24,6 @@ ;; ;;; Commentary: -;; ;; Rstdoc provides a facility to extract all of the docstrings defined in ;; an elisp source file. Usage: @@ -68,10 +67,10 @@ (insert "\n")) (defvar rst--escape-alist - '( ("\\\\='" . "\\\\'") - ("\\([^\\]\\)'" . "\\1`") - ("^[[:space:]\t]*$" . "|br|") - ("^[[:space:]\t]" . "|indent| ")) + '(("\\\\='" . "\\\\'") + ("\\([^\\]\\)'" . "\\1`") + ("^[[:space:]\t]*$" . "|br|") + ("^[[:space:]\t]" . "|indent| ")) "List of (regex . replacement) pairs.") (defun rstdoc--rst-quote-string (str) diff --git a/test/test-lib.el b/test/test-lib.el index 6a39bbe2..e9e7c379 100644 --- a/test/test-lib.el +++ b/test/test-lib.el @@ -154,22 +154,21 @@ running, quit if it terminated." (defun notmuch-test-expect-equal (output expected) "Compare OUTPUT with EXPECTED. Report any discrepencies." - (if (equal output expected) - t - (cond - ((and (listp output) - (listp expected)) - ;; Reporting the difference between two lists is done by - ;; reporting differing elements of OUTPUT and EXPECTED - ;; pairwise. This is expected to make analysis of failures - ;; simpler. - (apply #'concat (cl-loop for o in output - for e in expected - if (not (equal o e)) - collect (notmuch-test-report-unexpected o e)))) - - (t - (notmuch-test-report-unexpected output expected))))) + (cond + ((equal output expected) + t) + ((and (listp output) + (listp expected)) + ;; Reporting the difference between two lists is done by + ;; reporting differing elements of OUTPUT and EXPECTED + ;; pairwise. This is expected to make analysis of failures + ;; simpler. + (apply #'concat (cl-loop for o in output + for e in expected + if (not (equal o e)) + collect (notmuch-test-report-unexpected o e)))) + (t + (notmuch-test-report-unexpected output expected)))) (defun notmuch-post-command () (run-hooks 'post-command-hook)) From df3fab18fe70ea750f6f06da30291c67de7e74f2 Mon Sep 17 00:00:00 2001 From: Jonas Bernoulli Date: Sat, 8 Aug 2020 13:49:54 +0200 Subject: [PATCH 354/427] emacs: Increase consistency of library headers --- emacs/make-deps.el | 2 +- emacs/notmuch-company.el | 37 ++++++++++++++++++++--------------- emacs/notmuch-compat.el | 22 ++++++++++++++++++--- emacs/notmuch-crypto.el | 2 +- emacs/notmuch-lib.el | 2 -- emacs/notmuch-maildir-fcc.el | 37 +++++++++++++++++------------------ emacs/notmuch-print.el | 2 +- emacs/notmuch-show.el | 2 +- emacs/notmuch-tag.el | 3 +-- emacs/notmuch-tree.el | 2 +- emacs/notmuch-version.el.tmpl | 3 +-- emacs/notmuch.el | 4 ++-- 12 files changed, 67 insertions(+), 51 deletions(-) diff --git a/emacs/make-deps.el b/emacs/make-deps.el index 91f4ef3d..a7699fb1 100644 --- a/emacs/make-deps.el +++ b/emacs/make-deps.el @@ -1,4 +1,4 @@ -;; make-deps.el --- compute make dependencies for Elisp sources +;;; make-deps.el --- compute make dependencies for Elisp sources ;; ;; Copyright © Austin Clements ;; diff --git a/emacs/notmuch-company.el b/emacs/notmuch-company.el index c1f3594e..24e7446c 100644 --- a/emacs/notmuch-company.el +++ b/emacs/notmuch-company.el @@ -1,29 +1,34 @@ ;;; notmuch-company.el --- Mail address completion for notmuch via company-mode -*- lexical-binding: t -*- - -;; Authors: Trevor Jim -;; Michal Sojka ;; -;; Keywords: mail, completion - -;; This program is free software; you can redistribute it and/or modify -;; it under the terms of the GNU General Public License as published by +;; Copyright © Trevor Jim +;; Copyright © Michal Sojka +;; +;; This file is part of Notmuch. +;; +;; Notmuch is free software: you can redistribute it and/or modify it +;; under the terms of the GNU General Public License as published by ;; the Free Software Foundation, either version 3 of the License, or ;; (at your option) any later version. - -;; This program is distributed in the hope that it will be useful, -;; but WITHOUT ANY WARRANTY; without even the implied warranty of -;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -;; GNU General Public License for more details. - +;; +;; Notmuch is distributed in the hope that it will be useful, but +;; WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +;; General Public License for more details. +;; ;; You should have received a copy of the GNU General Public License -;; along with this program. If not, see . +;; along with Notmuch. If not, see . +;; +;; Authors: Trevor Jim +;; Michal Sojka +;; Keywords: mail, completion ;;; Commentary: -;; To enable this, install company mode (https://company-mode.github.io/) +;; Mail address completion for notmuch via company-mode. To enable +;; this, install company mode from . ;; ;; NB company-minimum-prefix-length defaults to 3 so you don't get -;; completion unless you type 3 characters +;; completion unless you type 3 characters. ;;; Code: diff --git a/emacs/notmuch-compat.el b/emacs/notmuch-compat.el index 3340918f..9d82a729 100644 --- a/emacs/notmuch-compat.el +++ b/emacs/notmuch-compat.el @@ -1,10 +1,26 @@ -;; Compatibility functions for earlier versions of emacs - +;;; notmuch-compat.el --- compatibility functions for earlier versions of emacs +;; ;; The functions in this file are copied from more modern versions of ;; emacs and are Copyright (C) 1985-1986, 1992, 1994-1995, 1999-2017 ;; Free Software Foundation, Inc. +;; +;; This file is part of Notmuch. +;; +;; Notmuch is free software: you can redistribute it and/or modify it +;; under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. +;; +;; Notmuch is distributed in the hope that it will be useful, but +;; WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +;; General Public License for more details. +;; +;; You should have received a copy of the GNU General Public License +;; along with Notmuch. If not, see . + +;;; Code: -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; emacs master has a bugfix for folding long headers when sending ;; messages. Include the fix for earlier versions of emacs. To avoid ;; interfering with gnus we only run the hook when called from diff --git a/emacs/notmuch-crypto.el b/emacs/notmuch-crypto.el index e6bf8339..6df1dd64 100644 --- a/emacs/notmuch-crypto.el +++ b/emacs/notmuch-crypto.el @@ -1,4 +1,4 @@ -;;; notmuch-crypto.el --- functions for handling display of cryptographic metadata. +;;; notmuch-crypto.el --- functions for handling display of cryptographic metadata ;; ;; Copyright © Jameson Rollins ;; diff --git a/emacs/notmuch-lib.el b/emacs/notmuch-lib.el index 05d86ea1..fded2d7b 100644 --- a/emacs/notmuch-lib.el +++ b/emacs/notmuch-lib.el @@ -19,8 +19,6 @@ ;; ;; Authors: Carl Worth -;; This is an part of an emacs-based interface to the notmuch mail system. - ;;; Code: (require 'cl-lib) diff --git a/emacs/notmuch-maildir-fcc.el b/emacs/notmuch-maildir-fcc.el index 1027e1a7..aa07b26a 100644 --- a/emacs/notmuch-maildir-fcc.el +++ b/emacs/notmuch-maildir-fcc.el @@ -1,24 +1,23 @@ -;;; notmuch-maildir-fcc.el --- - -;; This file is free software; you can redistribute it and/or modify -;; it under the terms of the GNU General Public License as published -;; by the Free Software Foundation; either version 2, or (at your -;; option) any later version. - -;; This program is distributed in the hope that it will be useful, -;; but WITHOUT ANY WARRANTY; without even the implied warranty of -;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -;; GNU General Public License for more details. +;;; notmuch-maildir-fcc.el --- inserting using a fcc handler +;; Copyright © Jesse Rosenthal +;; +;; This file is part of Notmuch. +;; +;; Notmuch is free software: you can redistribute it and/or modify it +;; under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. +;; +;; Notmuch is distributed in the hope that it will be useful, but +;; WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +;; General Public License for more details. +;; ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs; see the file COPYING. If not, write to the -;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, -;; Boston, MA 02110-1301, USA. - -;;; Commentary: - -;; To use this as the fcc handler for message-mode, -;; customize the notmuch-fcc-dirs variable +;; along with Notmuch. If not, see . +;; +;; Authors: Jesse Rosenthal ;;; Code: diff --git a/emacs/notmuch-print.el b/emacs/notmuch-print.el index d9b3d449..6dd9f775 100644 --- a/emacs/notmuch-print.el +++ b/emacs/notmuch-print.el @@ -1,4 +1,4 @@ -;;; notmuch-print.el --- printing messages from notmuch. +;;; notmuch-print.el --- printing messages from notmuch ;; ;; Copyright © David Edmondson ;; diff --git a/emacs/notmuch-show.el b/emacs/notmuch-show.el index eb07e450..b0f2d28b 100644 --- a/emacs/notmuch-show.el +++ b/emacs/notmuch-show.el @@ -1,4 +1,4 @@ -;;; notmuch-show.el --- displaying notmuch forests. +;;; notmuch-show.el --- displaying notmuch forests ;; ;; Copyright © Carl Worth ;; Copyright © David Edmondson diff --git a/emacs/notmuch-tag.el b/emacs/notmuch-tag.el index ccc1321f..c7a1e322 100644 --- a/emacs/notmuch-tag.el +++ b/emacs/notmuch-tag.el @@ -20,9 +20,8 @@ ;; ;; Authors: Carl Worth ;; Damien Cassou -;; + ;;; Code: -;; (require 'cl-lib) (eval-when-compile diff --git a/emacs/notmuch-tree.el b/emacs/notmuch-tree.el index b538cef9..fbba4bb3 100644 --- a/emacs/notmuch-tree.el +++ b/emacs/notmuch-tree.el @@ -1,4 +1,4 @@ -;;; notmuch-tree.el --- displaying notmuch forests. +;;; notmuch-tree.el --- displaying notmuch forests ;; ;; Copyright © Carl Worth ;; Copyright © David Edmondson diff --git a/emacs/notmuch-version.el.tmpl b/emacs/notmuch-version.el.tmpl index abf52f17..97308295 100644 --- a/emacs/notmuch-version.el.tmpl +++ b/emacs/notmuch-version.el.tmpl @@ -1,5 +1,4 @@ -;;; notmuch-version.el --- Version of notmuch -;; -*- emacs-lisp -*- +;;; notmuch-version.el --- version of notmuch -*- emacs-lisp -*- ;; ;; %AG% ;; diff --git a/emacs/notmuch.el b/emacs/notmuch.el index 75fe6900..babddbb6 100644 --- a/emacs/notmuch.el +++ b/emacs/notmuch.el @@ -18,7 +18,7 @@ ;; along with Notmuch. If not, see . ;; ;; Authors: Carl Worth -;; Homepage: https://notmuchmail.org/ +;; Homepage: https://notmuchmail.org ;;; Commentary: @@ -62,7 +62,7 @@ ;; ;; TL;DR: notmuch-emacs from MELPA and notmuch from distro packages is ;; NOT SUPPORTED. -;; + ;;; Code: (eval-when-compile (require 'cl-lib)) From 6c84dee53193a78cf797b44d3febcc14488ea6b1 Mon Sep 17 00:00:00 2001 From: Jonas Bernoulli Date: Sat, 8 Aug 2020 13:49:55 +0200 Subject: [PATCH 355/427] Fix typos --- NEWS | 2 +- bindings/python-cffi/notmuch2/__init__.py | 2 +- bindings/python-cffi/notmuch2/_base.py | 6 +++--- bindings/python-cffi/notmuch2/_database.py | 8 ++++---- bindings/python-cffi/notmuch2/_message.py | 4 ++-- bindings/python-cffi/notmuch2/_tags.py | 8 ++++---- bindings/python-cffi/tests/conftest.py | 2 +- bindings/python/notmuch/database.py | 12 ++++++------ bindings/python/notmuch/query.py | 2 +- emacs/notmuch-crypto.el | 2 +- emacs/notmuch-lib.el | 2 +- emacs/notmuch-mua.el | 2 +- lib/notmuch.h | 4 ++-- tag-util.c | 2 +- tag-util.h | 2 +- test/T610-message-property.sh | 2 +- test/T710-message-id.sh | 2 +- test/random-corpus.c | 2 +- test/test-lib.el | 2 +- 19 files changed, 34 insertions(+), 34 deletions(-) diff --git a/NEWS b/NEWS index 0d8b930f..9f0db031 100644 --- a/NEWS +++ b/NEWS @@ -116,7 +116,7 @@ information about cryptographic protections for the Subject header. Emacs ----- -Optionally check for missing attachements in outgoing messages (see +Optionally check for missing attachments in outgoing messages (see function `notmuch-mua-attachment-check`). Bind `B` to browse URLs in current message. diff --git a/bindings/python-cffi/notmuch2/__init__.py b/bindings/python-cffi/notmuch2/__init__.py index 613317e0..f281edc1 100644 --- a/bindings/python-cffi/notmuch2/__init__.py +++ b/bindings/python-cffi/notmuch2/__init__.py @@ -10,7 +10,7 @@ should consider their signatures implementation details. Errors ====== -All errors occuring due to errors from the underlying notmuch database +All errors occurring due to errors from the underlying notmuch database are subclasses of the :exc:`NotmuchError`. Due to memory management it is possible to try and use an object after it has been freed. In this case a :exc:`ObjectDestroyedError` will be raised. diff --git a/bindings/python-cffi/notmuch2/_base.py b/bindings/python-cffi/notmuch2/_base.py index 31258149..1cf03c88 100644 --- a/bindings/python-cffi/notmuch2/_base.py +++ b/bindings/python-cffi/notmuch2/_base.py @@ -29,7 +29,7 @@ class NotmuchObject(metaclass=abc.ABCMeta): However during some peculiar situations, e.g. interpreter shutdown, it is possible for the :meth:`__del__` method to have been called, whele there are still references to an object. This - could result in child objects asking their memeory to be freed + could result in child objects asking their memory to be freed after the parent has already freed the memory, making things rather unhappy as double frees are not taken lightly in C. To handle this case all objects need to follow the same protocol to @@ -73,7 +73,7 @@ class NotmuchObject(metaclass=abc.ABCMeta): def _destroy(self): """Destroy the object, freeing all memory. - This method needs to destory the object on the + This method needs to destroy the object on the libnotmuch-level. It must ensure it's not been destroyed by it's parent object yet before doing so. It also must be idempotent. @@ -134,7 +134,7 @@ class BinString(str): Most data in libnotmuch should be valid ASCII or valid UTF-8. However since it is a C library these are represented as - bytestrings intead which means on an API level we can not + bytestrings instead which means on an API level we can not guarantee that decoding this to UTF-8 will both succeed and be lossless. This string type converts bytes to unicode in a lossy way, but also makes the raw bytes available. diff --git a/bindings/python-cffi/notmuch2/_database.py b/bindings/python-cffi/notmuch2/_database.py index 7db5a7f8..5ab0f20a 100644 --- a/bindings/python-cffi/notmuch2/_database.py +++ b/bindings/python-cffi/notmuch2/_database.py @@ -422,7 +422,7 @@ class Database(base.NotmuchObject): of it as ``dup = db.remove_message(name); if dup: ...``. :rtype: bool - :raises XapianError: A Xapian exception ocurred. + :raises XapianError: A Xapian exception occurred. :raises ReadOnlyDatabaseError: The database is opened in READ_ONLY mode. :raises UpgradeRequiredError: The database must be upgraded @@ -458,7 +458,7 @@ class Database(base.NotmuchObject): :raises LookupError: If no message was found. :raises OutOfMemoryError: When there is no memory to allocate the message instance. - :raises XapianError: A Xapian exception ocurred. + :raises XapianError: A Xapian exception occurred. :raises ObjectDestroyedError: if used after destroyed. """ msg_pp = capi.ffi.new('notmuch_message_t **') @@ -489,7 +489,7 @@ class Database(base.NotmuchObject): a subclass of :exc:`KeyError`. :raises OutOfMemoryError: When there is no memory to allocate the message instance. - :raises XapianError: A Xapian exception ocurred. + :raises XapianError: A Xapian exception occurred. :raises ObjectDestroyedError: if used after destroyed. """ if not hasattr(os, 'PathLike') and isinstance(filename, pathlib.Path): @@ -695,7 +695,7 @@ class AtomicContext: """Force ending the atomic section. This can only be called once __exit__ has been called. It - will attept to close the atomic section (again). This is + will attempt to close the atomic section (again). This is useful if the original exit raised an exception and the atomic section is still open. But things are pretty ugly by now. diff --git a/bindings/python-cffi/notmuch2/_message.py b/bindings/python-cffi/notmuch2/_message.py index 02de50ad..2f232076 100644 --- a/bindings/python-cffi/notmuch2/_message.py +++ b/bindings/python-cffi/notmuch2/_message.py @@ -147,7 +147,7 @@ class Message(base.NotmuchObject): """Return an iterator of all files for this message. If multiple files contained the same message ID they will all - be returned here. The files are returned as intances of + be returned here. The files are returned as instances of :class:`pathlib.Path`. :returns: Iterator yielding :class:`pathlib.Path` instances. @@ -431,7 +431,7 @@ class PropertiesMap(base.NotmuchObject, collections.abc.MutableMapping): means the former will yield duplicate keys while the latter won't. It also means ``len(list(iter(this_map)))`` could be different than ``len(this_map.keys())``. ``len(this_map)`` will correspond - with the lenght of the default iterator. + with the length of the default iterator. Be aware that libnotmuch exposes all of this as iterators, so quite a few operations have O(n) performance instead of the usual diff --git a/bindings/python-cffi/notmuch2/_tags.py b/bindings/python-cffi/notmuch2/_tags.py index 3b14c981..ee5d2a34 100644 --- a/bindings/python-cffi/notmuch2/_tags.py +++ b/bindings/python-cffi/notmuch2/_tags.py @@ -161,7 +161,7 @@ class MutableTagSet(ImmutableTagSet, collections.abc.MutableSet): """ # Since we subclass ImmutableTagSet we inherit a __hash__. But we - # are mutable, setting it to None will make the Python machinary + # are mutable, setting it to None will make the Python machinery # recognise us as unhashable. __hash__ = None @@ -179,7 +179,7 @@ class MutableTagSet(ImmutableTagSet, collections.abc.MutableSet): :raises TypeError: If the tag is not a valid type. :raises TagTooLongError: If the added tag exceeds the maximum - lenght, see ``notmuch_cffi.NOTMUCH_TAG_MAX``. + length, see ``notmuch_cffi.NOTMUCH_TAG_MAX``. :raises ReadOnlyDatabaseError: If the database is opened in read-only mode. """ @@ -204,7 +204,7 @@ class MutableTagSet(ImmutableTagSet, collections.abc.MutableSet): :raises TypeError: If the tag is not a valid type. :raises TagTooLongError: If the tag exceeds the maximum - lenght, see ``notmuch_cffi.NOTMUCH_TAG_MAX``. + length, see ``notmuch_cffi.NOTMUCH_TAG_MAX``. :raises ReadOnlyDatabaseError: If the database is opened in read-only mode. """ @@ -286,7 +286,7 @@ class MutableTagSet(ImmutableTagSet, collections.abc.MutableSet): class TagsIter(base.NotmuchObject, collections.abc.Iterator): """Iterator over tags. - This is only an interator, not a container so calling + This is only an iterator, not a container so calling :meth:`__iter__` does not return a new, replenished iterator but only itself. diff --git a/bindings/python-cffi/tests/conftest.py b/bindings/python-cffi/tests/conftest.py index de7db8e7..6835fd30 100644 --- a/bindings/python-cffi/tests/conftest.py +++ b/bindings/python-cffi/tests/conftest.py @@ -31,7 +31,7 @@ def notmuch(maildir): fixture. """ def run(*args): - """Run a notmuch comand. + """Run a notmuch command. This function runs with a timeout error as many notmuch commands may block if multiple processes are trying to open diff --git a/bindings/python/notmuch/database.py b/bindings/python/notmuch/database.py index 88ca836e..8fb507fa 100644 --- a/bindings/python/notmuch/database.py +++ b/bindings/python/notmuch/database.py @@ -65,7 +65,7 @@ class Database(object): .. note:: Any function in this class can and will throw an - :exc:`NotInitializedError` if the database was not intitialized + :exc:`NotInitializedError` if the database was not initialized properly. """ _std_db_path = None @@ -273,9 +273,9 @@ class Database(object): return Database._get_version(self._db) def get_revision (self): - """Returns the committed database revison and UUID + """Returns the committed database revision and UUID - :returns: (revison, uuid) The database revision as a positive integer + :returns: (revision, uuid) The database revision as a positive integer and the UUID of the database. """ self._assert_db_is_initialized() @@ -574,7 +574,7 @@ class Database(object): in the meantime. In this case, you should close and reopen the database and retry. :exc:`NotInitializedError` if - the database was not intitialized. + the database was not initialized. """ self._assert_db_is_initialized() msg_p = NotmuchMessageP() @@ -600,7 +600,7 @@ class Database(object): case, you should close and reopen the database and retry. :raises: :exc:`NotInitializedError` if the database was not - intitialized. + initialized. *Added in notmuch 0.9*""" self._assert_db_is_initialized() @@ -616,7 +616,7 @@ class Database(object): """Returns :class:`Tags` with a list of all tags found in the database :returns: :class:`Tags` - :execption: :exc:`NotmuchError` with :attr:`STATUS`.NULL_POINTER + :exception: :exc:`NotmuchError` with :attr:`STATUS`.NULL_POINTER on error """ self._assert_db_is_initialized() diff --git a/bindings/python/notmuch/query.py b/bindings/python/notmuch/query.py index bdc18790..ffb86df1 100644 --- a/bindings/python/notmuch/query.py +++ b/bindings/python/notmuch/query.py @@ -95,7 +95,7 @@ class Query(object): :exc:`NullPointerError` if the query creation failed (e.g. too little memory). :exc:`NotInitializedError` if the underlying db was not - intitialized. + initialized. """ db._assert_db_is_initialized() # create reference to parent db to keep it alive diff --git a/emacs/notmuch-crypto.el b/emacs/notmuch-crypto.el index 6df1dd64..276c9859 100644 --- a/emacs/notmuch-crypto.el +++ b/emacs/notmuch-crypto.el @@ -128,7 +128,7 @@ by user FROM." (setq label (concat "Unknown key ID " keyid " or unsupported algorithm")) (setq button-action 'notmuch-crypto-sigstatus-error-callback) (setq help-msg (concat "Click to retrieve key ID " keyid - " from keyserver."))) + " from key server."))) ((string= status "bad") (setq label (concat "Bad signature (claimed key ID " keyid ")")) (setq face 'notmuch-crypto-signature-bad)) diff --git a/emacs/notmuch-lib.el b/emacs/notmuch-lib.el index fded2d7b..11f6858e 100644 --- a/emacs/notmuch-lib.el +++ b/emacs/notmuch-lib.el @@ -656,7 +656,7 @@ MSG (if it isn't already)." ;; Workaround: The call to `mm-display-part' below triggers a bug in ;; Emacs 24 if it attempts to use the shr renderer to display an HTML ;; part with images in it (demonstrated in 24.1 and 24.2 on Debian and -;; Fedora 17, though unreproducable in other configurations). +;; Fedora 17, though unreproducible in other configurations). ;; `mm-shr' references the variable `gnus-inhibit-images' without ;; first loading gnus-art, which defines it, resulting in a ;; void-variable error. Hence, we advise `mm-shr' to ensure gnus-art diff --git a/emacs/notmuch-mua.el b/emacs/notmuch-mua.el index 2910a63e..f321e0c6 100644 --- a/emacs/notmuch-mua.el +++ b/emacs/notmuch-mua.el @@ -337,7 +337,7 @@ Typically this is added to `notmuch-mua-send-hook'." (defun notmuch-mua-pop-to-buffer (name switch-function) "Pop to buffer NAME, and warn if it already exists and is -modified. This function is notmuch addaptation of +modified. This function is notmuch adaptation of `message-pop-to-buffer'." (let ((buffer (get-buffer name))) (if (and buffer diff --git a/lib/notmuch.h b/lib/notmuch.h index f3cb0fe2..c66e78b1 100644 --- a/lib/notmuch.h +++ b/lib/notmuch.h @@ -1642,7 +1642,7 @@ notmuch_message_remove_tag (notmuch_message_t *message, const char *tag); * * @retval #NOTMUCH_STATUS_READ_ONLY_DATABASE: Database was opened in * read-only mode so message cannot be modified. - * @retval #NOTMUCH_STATUS_XAPIAN_EXCEPTION: an execption was thrown + * @retval #NOTMUCH_STATUS_XAPIAN_EXCEPTION: an exception was thrown * accessing the database. */ notmuch_status_t @@ -1874,7 +1874,7 @@ notmuch_message_add_property (notmuch_message_t *message, const char *key, const /** * Remove a (key,value) pair from a message. * - * It is not an error to remove a non-existant (key,value) pair + * It is not an error to remove a non-existent (key,value) pair * * @returns * - NOTMUCH_STATUS_ILLEGAL_ARGUMENT: *key* may not contain an '=' character. diff --git a/tag-util.c b/tag-util.c index 1837b1ae..accf299e 100644 --- a/tag-util.c +++ b/tag-util.c @@ -323,7 +323,7 @@ tag_op_list_apply (notmuch_message_t *message, if (flags & TAG_FLAG_MAILDIR_SYNC) { status = notmuch_message_tags_to_maildir_flags (message); if (status) { - message_error (message, status, "synching tags to maildir"); + message_error (message, status, "syncing tags to maildir"); return status; } } diff --git a/tag-util.h b/tag-util.h index bbe54d99..411e8cae 100644 --- a/tag-util.h +++ b/tag-util.h @@ -123,7 +123,7 @@ tag_op_list_append (tag_op_list_t *list, /* * Apply a list of tag operations, in order, to a given message. * - * Flags can be bitwise ORed; see enum above for possibilies. + * Flags can be bitwise ORed; see enum above for possibilities. */ notmuch_status_t diff --git a/test/T610-message-property.sh b/test/T610-message-property.sh index b8774230..d0e52f4a 100755 --- a/test/T610-message-property.sh +++ b/test/T610-message-property.sh @@ -65,7 +65,7 @@ cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR} EXPECT0(notmuch_message_get_property (message, "testkey2", &val)); printf("testkey2 = %s\n", val); - /* remove non-existant value for key */ + /* remove non-existent value for key */ EXPECT0(notmuch_message_remove_property (message, "testkey2", "this value has spaces and = sign")); EXPECT0(notmuch_message_get_property (message, "testkey2", &val)); printf("testkey2 = %s\n", val); diff --git a/test/T710-message-id.sh b/test/T710-message-id.sh index e73d6ba9..5129d84c 100755 --- a/test/T710-message-id.sh +++ b/test/T710-message-id.sh @@ -29,7 +29,7 @@ GOOD: 1258787708-21121-2-git-send-email-keithp@keithp.com EOF test_expect_equal_file EXPECTED OUTPUT -test_begin_subtest "<> delimeters are required" +test_begin_subtest "<> delimiters are required" ${TEST_DIRECTORY}/message-id-parse <OUTPUT 018b1a8f2d1df62e804ce88b65401304832dfbbf.1346614915.git.jani@nikula.org> <1530507300.raoomurnbf.astroid@strange.none diff --git a/test/random-corpus.c b/test/random-corpus.c index 8ed7ff76..ff413252 100644 --- a/test/random-corpus.c +++ b/test/random-corpus.c @@ -50,7 +50,7 @@ typedef struct { /* * Choose about half ascii as test characters, as ascii - * punctation and whitespace is the main cause of problems for + * punctuation and whitespace is the main cause of problems for * the (old) restore parser. * * We then favour code points with 2 byte encodings. Note that diff --git a/test/test-lib.el b/test/test-lib.el index e9e7c379..2def7ffe 100644 --- a/test/test-lib.el +++ b/test/test-lib.el @@ -153,7 +153,7 @@ running, quit if it terminated." "Output:\t" (prin1-to-string output) "\n")) (defun notmuch-test-expect-equal (output expected) - "Compare OUTPUT with EXPECTED. Report any discrepencies." + "Compare OUTPUT with EXPECTED. Report any discrepancies." (cond ((equal output expected) t) From 8cf6af449e90aa62344a2f8ef9bbef1a16a5ead6 Mon Sep 17 00:00:00 2001 From: Jonas Bernoulli Date: Sat, 8 Aug 2020 13:49:56 +0200 Subject: [PATCH 356/427] .dir-locals.el: Set variables for correct "shell" mode The major mode used for shell scripts is named 'sh-mode'. 'shell-mode' on the other hand implements an interactive shell in emacs-lisp. --- .dir-locals.el | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.dir-locals.el b/.dir-locals.el index fc75ae61..b3ddffe8 100644 --- a/.dir-locals.el +++ b/.dir-locals.el @@ -15,7 +15,7 @@ (emacs-lisp-mode (indent-tabs-mode . t) (tab-width . 8)) - (shell-mode + (sh-mode (indent-tabs-mode . t) (tab-width . 8) (sh-basic-offset . 4) From bb15524c12a73e2cd6cdffe0561ba493706d65de Mon Sep 17 00:00:00 2001 From: Jonas Bernoulli Date: Sat, 8 Aug 2020 13:49:57 +0200 Subject: [PATCH 357/427] test: Fix indentation Fix it to consistently match the style we have configured in ".dir-locals.el". --- test/test-lib.sh | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/test/test-lib.sh b/test/test-lib.sh index 527c9e8b..c23a0d20 100644 --- a/test/test-lib.sh +++ b/test/test-lib.sh @@ -136,16 +136,16 @@ add_gpgsm_home () at_exit_function _gnupg_exit mkdir -p -m 0700 "$GNUPGHOME" openssl pkcs12 -export -passout pass: -inkey "$NOTMUCH_SRCDIR/test/smime/key+cert.pem" \ - < "$NOTMUCH_SRCDIR/test/smime/test.crt" | \ - gpgsm --batch --no-tty --no-common-certs-import --pinentry-mode=loopback --passphrase-fd 3 \ - --disable-dirmngr --import >"$GNUPGHOME"/import.log 2>&1 3<<<'' + < "$NOTMUCH_SRCDIR/test/smime/test.crt" | \ + gpgsm --batch --no-tty --no-common-certs-import --pinentry-mode=loopback --passphrase-fd 3 \ + --disable-dirmngr --import >"$GNUPGHOME"/import.log 2>&1 3<<<'' fpr=$(gpgsm --batch --list-key test_suite@notmuchmail.org | sed -n 's/.*fingerprint: //p') echo "$fpr S relax" >> "$GNUPGHOME/trustlist.txt" gpgsm --quiet --batch --no-tty --no-common-certs-import --disable-dirmngr --import < $NOTMUCH_SRCDIR/test/smime/ca.crt echo "4D:E0:FF:63:C0:E9:EC:01:29:11:C8:7A:EE:DA:3A:9A:7F:6E:C1:0D S" >> "$GNUPGHOME/trustlist.txt" printf '%s::1\n' include-certs disable-crl-checks | gpgconf --output /dev/null --change-options gpgsm gpgsm --batch --no-tty --no-common-certs-import --pinentry-mode=loopback --passphrase-fd 3 \ - --disable-dirmngr --import "$NOTMUCH_SRCDIR/test/smime/bob.p12" >>"$GNUPGHOME"/import.log 2>&1 3<<<'' + --disable-dirmngr --import "$NOTMUCH_SRCDIR/test/smime/bob.p12" >>"$GNUPGHOME"/import.log 2>&1 3<<<'' test_debug "cat $GNUPGHOME/import.log" } @@ -394,8 +394,8 @@ emacs_fcc_message () local nmn_args subject body nmn_args='' while [[ "$1" =~ ^-- ]]; do - nmn_args="$nmn_args $1" - shift + nmn_args="$nmn_args $1" + shift done subject="$1" body="$2" @@ -405,7 +405,7 @@ emacs_fcc_message () test_emacs \ "(let ((message-send-mail-function (lambda () t)) - (mail-host-address \"example.com\")) + (mail-host-address \"example.com\")) (notmuch-mua-mail) (message-goto-to) (insert \"test_suite@notmuchmail.org\nDate: 01 Jan 2000 12:00:00 -0000\") @@ -524,9 +524,9 @@ test_expect_equal_json () { # override Python's stdio encoding defaults. script='import json, sys; json.dump(json.load(sys.stdin), sys.stdout, sort_keys=True, indent=4)' output=$(echo "$1" | PYTHONIOENCODING=utf-8 $NOTMUCH_PYTHON -c "$script" \ - || echo "$1") + || echo "$1") expected=$(echo "$2" | PYTHONIOENCODING=utf-8 $NOTMUCH_PYTHON -c "$script" \ - || echo "$2") + || echo "$2") shift 2 test_expect_equal "$output" "$expected" "$@" } @@ -540,15 +540,15 @@ test_valid_json () { # Sort the top-level list of JSON data from stdin. test_sort_json () { PYTHONIOENCODING=utf-8 $NOTMUCH_PYTHON -c \ - "import sys, json; json.dump(sorted(json.load(sys.stdin)),sys.stdout)" + "import sys, json; json.dump(sorted(json.load(sys.stdin)),sys.stdout)" } # test for json objects: # read the source of test/json_check_nodes.py (or the output when # invoking it without arguments) for an explanation of the syntax. test_json_nodes () { - local output - exec 1>&6 2>&7 # Restore stdout and stderr + local output + exec 1>&6 2>&7 # Restore stdout and stderr if [ -z "$inside_subtest" ]; then error "bug in the test script: test_json_eval without test_begin_subtest" fi @@ -662,7 +662,7 @@ notmuch_json_show_sanitize () -e 's|"filename": "signature.asc",||g' \ -e 's|"filename": \["/[^"]*"\],|"filename": \["YYYYY"\],|g' \ -e 's|"timestamp": 97.......|"timestamp": 42|g' \ - -e 's|"content-length": [1-9][0-9]*|"content-length": "NONZERO"|g' + -e 's|"content-length": [1-9][0-9]*|"content-length": "NONZERO"|g' } notmuch_emacs_error_sanitize () @@ -673,7 +673,7 @@ notmuch_emacs_error_sanitize () for file in "$@"; do echo "=== $file ===" cat "$file" - done | sed \ + done | sed \ -e 's/^\[.*\]$/[XXX]/' \ -e "s|^\(command: \)\{0,1\}/.*/$command|\1YYY/$command|" } @@ -929,8 +929,8 @@ test_expect_code () { # but is a prefix that can be used in the test script, like: # # test_expect_success 'complain and die' ' -# do something && -# do something else && +# do something && +# do something else && # test_must_fail git checkout ../outerspace # ' # @@ -1020,8 +1020,8 @@ export NOTMUCH_CONFIG=$NOTMUCH_CONFIG # Here's what we are using here: # -# --quick Use minimal customization. This implies --no-init-file, -# --no-site-file and (emacs 24) --no-site-lisp +# --quick Use minimal customization. This implies --no-init-file, +# --no-site-file and (emacs 24) --no-site-lisp # # --directory Ensure that the local elisp sources are found # From 2a0d4c59659276a14b6857699d27453bcbd39c9b Mon Sep 17 00:00:00 2001 From: Jonas Bernoulli Date: Sat, 8 Aug 2020 13:49:58 +0200 Subject: [PATCH 358/427] .gitignore: Sort using sort-lines --- .gitignore | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/.gitignore b/.gitignore index 8f3ebec0..3edd1768 100644 --- a/.gitignore +++ b/.gitignore @@ -1,20 +1,20 @@ +*.[ao] +*.stamp +*cscope* +*~ +.*.swp +/.deps /.first-build-message +/.stamps /Makefile.config +/bindings/python-cffi/build/ +/lib/libnotmuch*.dylib +/lib/libnotmuch.so* +/notmuch +/notmuch-shared +/releases /sh.config +/sphinx.config /version.stamp TAGS tags -*cscope* -/.deps -/notmuch -/notmuch-shared -/lib/libnotmuch.so* -/lib/libnotmuch*.dylib -*.[ao] -*~ -.*.swp -/releases -/.stamps -*.stamp -/bindings/python-cffi/build/ -/sphinx.config From 254d0f951541b7e347f32127c6b31f342e4016eb Mon Sep 17 00:00:00 2001 From: Jonas Bernoulli Date: Sat, 8 Aug 2020 13:49:59 +0200 Subject: [PATCH 359/427] emacs: Provide 'rstdoc' feature at end of file Features should nearly always be provided at the very end of their libraries. This feature isn't one of the rare exceptions. --- emacs/rstdoc.el | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/emacs/rstdoc.el b/emacs/rstdoc.el index 41390bbe..63fa2794 100644 --- a/emacs/rstdoc.el +++ b/emacs/rstdoc.el @@ -32,8 +32,6 @@ ;;; Code: -(provide 'rstdoc) - (defun rstdoc-batch-extract () "Extract docstrings to and from the files on the command line." (apply #'rstdoc-extract command-line-args-left)) @@ -82,4 +80,6 @@ (replace-match (cdr pair)))) (buffer-substring (point-min) (point-max)))) +(provide 'rstdoc) + ;;; rstdoc.el ends here From a1b757c1ca111b25d3ce981942cdace04ee90429 Mon Sep 17 00:00:00 2001 From: Jonas Bernoulli Date: Sat, 8 Aug 2020 13:50:00 +0200 Subject: [PATCH 360/427] emacs: Add end-of-file line to libraries that lack it --- emacs/notmuch-compat.el | 2 ++ emacs/notmuch-tag.el | 2 ++ 2 files changed, 4 insertions(+) diff --git a/emacs/notmuch-compat.el b/emacs/notmuch-compat.el index 9d82a729..370cafa1 100644 --- a/emacs/notmuch-compat.el +++ b/emacs/notmuch-compat.el @@ -107,3 +107,5 @@ inlined." ;; End of compatibility functions (provide 'notmuch-compat) + +;;; notmuch-compat.el ends here diff --git a/emacs/notmuch-tag.el b/emacs/notmuch-tag.el index c7a1e322..5d4a6865 100644 --- a/emacs/notmuch-tag.el +++ b/emacs/notmuch-tag.el @@ -550,3 +550,5 @@ and vice versa." ;; (provide 'notmuch-tag) + +;;; notmuch-tag.el ends here From b614d3cc0bb5f7c30cd28576360891853416e0b2 Mon Sep 17 00:00:00 2001 From: Jonas Bernoulli Date: Sat, 8 Aug 2020 13:50:01 +0200 Subject: [PATCH 361/427] NEWS: Add stub for 0.31 --- NEWS | 3 +++ 1 file changed, 3 insertions(+) diff --git a/NEWS b/NEWS index 9f0db031..3790204b 100644 --- a/NEWS +++ b/NEWS @@ -1,3 +1,6 @@ +Notmuch 0.31 (UNRELEASED) +========================= + Notmuch 0.30 (2020-07-10) ========================= From 42781f18214a4adf277c4bf52dd191ccf74e1b3b Mon Sep 17 00:00:00 2001 From: Jonas Bernoulli Date: Sat, 8 Aug 2020 13:50:02 +0200 Subject: [PATCH 362/427] NEWS: At least Emacs 25.1 is required now Some backward incompatible changes follow in the next few commits and going forward contributors don't have to worry about Emacs 24 at all anymore. --- NEWS | 5 +++++ emacs/notmuch-pkg.el.tmpl | 3 +-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/NEWS b/NEWS index 3790204b..4160e39c 100644 --- a/NEWS +++ b/NEWS @@ -1,6 +1,11 @@ Notmuch 0.31 (UNRELEASED) ========================= +Emacs +----- + +The minimum supported major version of GNU Emacs is now 25.1. + Notmuch 0.30 (2020-07-10) ========================= diff --git a/emacs/notmuch-pkg.el.tmpl b/emacs/notmuch-pkg.el.tmpl index 9d0999c1..85c631de 100644 --- a/emacs/notmuch-pkg.el.tmpl +++ b/emacs/notmuch-pkg.el.tmpl @@ -3,5 +3,4 @@ "notmuch" %VERSION% "Emacs based front-end (MUA) for notmuch" - '((emacs "24") - (cl-lib "0.6.1"))) + '((emacs "25.1"))) From 9946380e47ffcffea7fb9793a9fe4944b510110f Mon Sep 17 00:00:00 2001 From: Jonas Bernoulli Date: Sat, 8 Aug 2020 13:50:03 +0200 Subject: [PATCH 363/427] emacs: Use cl-incf where appropriate It's shorter. That's it pretty much. --- emacs/notmuch-hello.el | 2 +- emacs/notmuch-lib.el | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/emacs/notmuch-hello.el b/emacs/notmuch-hello.el index 876d8ef1..c127bba9 100644 --- a/emacs/notmuch-hello.el +++ b/emacs/notmuch-hello.el @@ -609,7 +609,7 @@ with `notmuch-hello-query-counts'." name) (setq column-indent (1+ (max 0 (- column-width (length name))))))) - (setq count (1+ count)) + (cl-incf count) (when (eq (% count tags-per-line) 0) (setq column-indent 0) (widget-insert "\n"))) diff --git a/emacs/notmuch-lib.el b/emacs/notmuch-lib.el index 11f6858e..91c94781 100644 --- a/emacs/notmuch-lib.el +++ b/emacs/notmuch-lib.el @@ -413,7 +413,7 @@ of its command symbol." (i 0)) (while (< i (length prefix)) (aset prefix i (aref key i)) - (setq i (1+ i))) + (cl-incf i)) (let* ((subkeymap (key-binding prefix)) (ua-keys (where-is-internal 'universal-argument nil t)) (prefix-string (notmuch-prefix-key-description prefix)) From 2156517d902e6013463a5f5f79225741e7f54307 Mon Sep 17 00:00:00 2001 From: Jonas Bernoulli Date: Sat, 8 Aug 2020 13:50:04 +0200 Subject: [PATCH 364/427] emacs: Remove notmuch-setq-local Just use setq-local, which existed since Emacs 24.3. --- emacs/notmuch-address.el | 4 ++-- emacs/notmuch-company.el | 2 +- emacs/notmuch-compat.el | 8 -------- 3 files changed, 3 insertions(+), 11 deletions(-) diff --git a/emacs/notmuch-address.el b/emacs/notmuch-address.el index cd0ffb67..8a6d299c 100644 --- a/emacs/notmuch-address.el +++ b/emacs/notmuch-address.el @@ -172,11 +172,11 @@ toggles the setting in this buffer." (interactive) (if (local-variable-p 'notmuch-address-command) (kill-local-variable 'notmuch-address-command) - (notmuch-setq-local notmuch-address-command 'internal)) + (setq-local notmuch-address-command 'internal)) (when (boundp 'company-idle-delay) (if (local-variable-p 'company-idle-delay) (kill-local-variable 'company-idle-delay) - (notmuch-setq-local company-idle-delay nil)))) + (setq-local company-idle-delay nil)))) (defun notmuch-address-matching (substring) "Returns a list of completion candidates matching SUBSTRING. diff --git a/emacs/notmuch-company.el b/emacs/notmuch-company.el index 24e7446c..9ee8ceca 100644 --- a/emacs/notmuch-company.el +++ b/emacs/notmuch-company.el @@ -62,7 +62,7 @@ ;; internal completion) can still be accessed via standard company ;; functions, e.g., company-complete. (unless (eq notmuch-address-command 'internal) - (notmuch-setq-local company-idle-delay nil))) + (setq-local company-idle-delay nil))) ;;;###autoload (defun notmuch-company (command &optional arg &rest _ignore) diff --git a/emacs/notmuch-compat.el b/emacs/notmuch-compat.el index 370cafa1..fafc64f3 100644 --- a/emacs/notmuch-compat.el +++ b/emacs/notmuch-compat.el @@ -40,14 +40,6 @@ (unless (fboundp 'message--fold-long-headers) (add-hook 'message-header-hook 'notmuch-message--fold-long-headers)) -(if (fboundp 'setq-local) - (defalias 'notmuch-setq-local 'setq-local) - (defmacro notmuch-setq-local (var val) - "Set variable VAR to value VAL in current buffer. - -Backport of setq-local for emacs without setq-local (pre 24.3)." - `(set (make-local-variable ',var) ,val))) - (if (fboundp 'read-char-choice) (defalias 'notmuch-read-char-choice 'read-char-choice) (defun notmuch-read-char-choice (prompt chars &optional inhibit-keyboard-quit) From 08b26f449dffd1b0c6611350f975c1c62d2cfc74 Mon Sep 17 00:00:00 2001 From: Jonas Bernoulli Date: Sat, 8 Aug 2020 13:50:05 +0200 Subject: [PATCH 365/427] emacs: Remove notmuch-read-char-choice Just use `read-char-choice', which existed since Emacs 24.1. --- emacs/notmuch-compat.el | 56 ------------------------------------ emacs/notmuch-maildir-fcc.el | 4 +-- 2 files changed, 2 insertions(+), 58 deletions(-) diff --git a/emacs/notmuch-compat.el b/emacs/notmuch-compat.el index fafc64f3..3ede6b36 100644 --- a/emacs/notmuch-compat.el +++ b/emacs/notmuch-compat.el @@ -40,62 +40,6 @@ (unless (fboundp 'message--fold-long-headers) (add-hook 'message-header-hook 'notmuch-message--fold-long-headers)) -(if (fboundp 'read-char-choice) - (defalias 'notmuch-read-char-choice 'read-char-choice) - (defun notmuch-read-char-choice (prompt chars &optional inhibit-keyboard-quit) - "Read and return one of CHARS, prompting for PROMPT. -Any input that is not one of CHARS is ignored. - -If optional argument INHIBIT-KEYBOARD-QUIT is non-nil, ignore -keyboard-quit events while waiting for a valid input. - -This is an exact copy of this function from emacs 24 for use on -emacs 23, except with the one emacs 24 only function it calls -inlined." - (unless (consp chars) - (error "Called `read-char-choice' without valid char choices")) - (let (char done show-help (helpbuf " *Char Help*")) - (let ((cursor-in-echo-area t) - (executing-kbd-macro executing-kbd-macro) - (esc-flag nil)) - (save-window-excursion ; in case we call help-form-show - (while (not done) - (unless (get-text-property 0 'face prompt) - (setq prompt (propertize prompt 'face 'minibuffer-prompt))) - (setq char (let ((inhibit-quit inhibit-keyboard-quit)) - (read-key prompt))) - (and show-help (buffer-live-p (get-buffer helpbuf)) - (kill-buffer helpbuf)) - (cond - ((not (numberp char))) - ;; If caller has set help-form, that's enough. - ;; They don't explicitly have to add help-char to chars. - ((and help-form - (eq char help-char) - (setq show-help t) - ;; This is an inlined copy of help-form-show as that - ;; was introduced in emacs 24 too. - (let ((msg (eval help-form))) - (when (stringp msg) - (with-output-to-temp-buffer " *Char Help*" - (princ msg)))))) - ((memq char chars) - (setq done t)) - ((and executing-kbd-macro (= char -1)) - ;; read-event returns -1 if we are in a kbd macro and - ;; there are no more events in the macro. Attempt to - ;; get an event interactively. - (setq executing-kbd-macro nil)) - ((not inhibit-keyboard-quit) - (cond - ((and (null esc-flag) (eq char ?\e)) - (setq esc-flag t)) - ((memq char '(?\C-g ?\e)) - (keyboard-quit)))))))) - ;; Display the question with the answer. But without cursor-in-echo-area. - (message "%s%s" prompt (char-to-string char)) - char))) - ;; End of compatibility functions (provide 'notmuch-compat) diff --git a/emacs/notmuch-maildir-fcc.el b/emacs/notmuch-maildir-fcc.el index aa07b26a..a9103a20 100644 --- a/emacs/notmuch-maildir-fcc.el +++ b/emacs/notmuch-maildir-fcc.el @@ -242,7 +242,7 @@ If CREATE is non-nil then create the folder if necessary." ;; typo, or just the user want a new folder, let the user decide ;; how to deal with it. (error - (let ((response (notmuch-read-char-choice "Insert failed: \ + (let ((response (read-char-choice "Insert failed: \ \(r)etry, (c)reate folder, (i)gnore, or (e)dit the header? " '(?r ?c ?i ?e)))) (cl-case response (?r (notmuch-maildir-fcc-with-notmuch-insert fcc-header)) @@ -327,7 +327,7 @@ if needed." ;; fix it in some way. (let* ((prompt (format "Fcc %s is not a maildir: \ \(r)etry, (c)reate folder, (i)gnore, or (e)dit the header? " fcc-header)) - (response (notmuch-read-char-choice prompt '(?r ?c ?i ?e)))) + (response (read-char-choice prompt '(?r ?c ?i ?e)))) (cl-case response (?r (notmuch-maildir-fcc-file-fcc fcc-header)) (?c (if (file-writable-p fcc-header) From 96baa2231882e9a9025797b1f9945ba6b2751dd4 Mon Sep 17 00:00:00 2001 From: Jonas Bernoulli Date: Sat, 8 Aug 2020 13:50:06 +0200 Subject: [PATCH 366/427] emacs: Drop old advices that were only need for Emacs 23 --- emacs/notmuch-mua.el | 14 -------------- test/test-lib.el | 17 ----------------- 2 files changed, 31 deletions(-) diff --git a/emacs/notmuch-mua.el b/emacs/notmuch-mua.el index f321e0c6..dcddca76 100644 --- a/emacs/notmuch-mua.el +++ b/emacs/notmuch-mua.el @@ -425,20 +425,6 @@ the From: header is already filled in by notmuch." (defvar notmuch-mua-sender-history nil) -;; Workaround: Running `ido-completing-read' in emacs 23.1, 23.2 and 23.3 -;; without some explicit initialization fill freeze the operation. -;; Hence, we advice `ido-completing-read' to ensure required initialization -;; is done. -(when (and (= emacs-major-version 23) - (< emacs-minor-version 4)) - (defadvice ido-completing-read (before notmuch-ido-mode-init activate) - (ido-init-completion-maps) - (add-hook 'minibuffer-setup-hook 'ido-minibuffer-setup) - (add-hook 'choose-completion-string-functions - 'ido-choose-completion-string) - (ad-disable-advice 'ido-completing-read 'before 'notmuch-ido-mode-init) - (ad-activate 'ido-completing-read))) - (defun notmuch-mua-prompt-for-sender () "Prompt for a sender from the user's configured identities." (if notmuch-identities diff --git a/test/test-lib.el b/test/test-lib.el index 2def7ffe..044c2da4 100644 --- a/test/test-lib.el +++ b/test/test-lib.el @@ -34,23 +34,6 @@ ;; `read' call. (setq read-file-name-function (lambda (&rest _) (read))) -;; Work around a bug in emacs 23.1 and emacs 23.2 which prevents -;; noninteractive (kill-emacs) from emacsclient. -(when (and (= emacs-major-version 23) (< emacs-minor-version 3)) - (defadvice kill-emacs (before disable-yes-or-no-p activate) - "Disable yes-or-no-p before executing kill-emacs" - (defun yes-or-no-p (prompt) t))) - -;; Emacs bug #2930: -;; 23.0.92; `accept-process-output' and `sleep-for' do not run sentinels -;; seems to be present in Emacs 23.1. -;; Running `list-processes' after `accept-process-output' seems to work -;; around this problem. -(when (and (= emacs-major-version 23) (= emacs-minor-version 1)) - (defadvice accept-process-output (after run-list-processes activate) - "run list-processes after executing accept-process-output" - (list-processes))) - (defun notmuch-test-wait () "Wait for process completion." (while (get-buffer-process (current-buffer)) From 3665914f71fc7be2f537ec0ae2647f768c7f5bbe Mon Sep 17 00:00:00 2001 From: Jonas Bernoulli Date: Sat, 8 Aug 2020 13:50:07 +0200 Subject: [PATCH 367/427] emacs: Do not abuse advice to monkey patch while testing Use `cl-letf*' instead. --- test/T310-emacs.sh | 14 ++++++++------ test/test-lib.el | 8 -------- 2 files changed, 8 insertions(+), 14 deletions(-) diff --git a/test/T310-emacs.sh b/test/T310-emacs.sh index 5f74305d..78ac19a8 100755 --- a/test/T310-emacs.sh +++ b/test/T310-emacs.sh @@ -40,12 +40,14 @@ test_emacs '(notmuch-search "tag:inbox") test_expect_equal_file $EXPECTED/notmuch-search-tag-inbox OUTPUT test_begin_subtest "Incremental parsing of search results" -test_emacs "(ad-enable-advice 'notmuch-search-process-filter 'around 'pessimal) - (ad-activate 'notmuch-search-process-filter) - (notmuch-search \"tag:inbox\") - (notmuch-test-wait) - (ad-disable-advice 'notmuch-search-process-filter 'around 'pessimal) - (ad-activate 'notmuch-search-process-filter) +test_emacs "(cl-letf* (((symbol-function 'orig) + (symbol-function 'notmuch-search-process-filter)) + ((symbol-function 'notmuch-search-process-filter) + (lambda (proc string) + (cl-loop for char across string + do (orig proc (char-to-string char)))))) + (notmuch-search \"tag:inbox\") + (notmuch-test-wait)) (test-output)" test_expect_equal_file $EXPECTED/notmuch-search-tag-inbox OUTPUT diff --git a/test/test-lib.el b/test/test-lib.el index 044c2da4..ec16c59c 100644 --- a/test/test-lib.el +++ b/test/test-lib.el @@ -97,14 +97,6 @@ running, quit if it terminated." (add-hook 'notmuch-hello-refresh-hook (lambda () (cl-incf notmuch-hello-refresh-hook-counter))) -(defadvice notmuch-search-process-filter (around pessimal activate disable) - "Feed notmuch-search-process-filter one character at a time." - (let ((string (ad-get-arg 1))) - (cl-loop for char across string - do (progn - (ad-set-arg 1 (char-to-string char)) - ad-do-it)))) - (defun notmuch-test-mark-links () "Enclose links in the current buffer with << and >>." ;; Links are often created by jit-lock functions From 6336c26d2341b390c995b5579bf590415db29420 Mon Sep 17 00:00:00 2001 From: Jonas Bernoulli Date: Sat, 8 Aug 2020 13:50:08 +0200 Subject: [PATCH 368/427] emacs: Use new advice mechanism do advice mm-shr Also because we now only support Emacs >= 25, we can remove the check for Emacs >= 24. --- emacs/notmuch-lib.el | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/emacs/notmuch-lib.el b/emacs/notmuch-lib.el index 91c94781..118faf1e 100644 --- a/emacs/notmuch-lib.el +++ b/emacs/notmuch-lib.el @@ -661,11 +661,9 @@ MSG (if it isn't already)." ;; first loading gnus-art, which defines it, resulting in a ;; void-variable error. Hence, we advise `mm-shr' to ensure gnus-art ;; is loaded. -(when (>= emacs-major-version 24) - (defadvice mm-shr (before load-gnus-arts activate) - (require 'gnus-art nil t) - (ad-disable-advice 'mm-shr 'before 'load-gnus-arts) - (ad-activate 'mm-shr))) +(define-advice mm-shr (:before (_handle) notmuch--load-gnus-args) + "Require `gnus-art' since we use its variables." + (require 'gnus-art nil t)) (defun notmuch-mm-display-part-inline (msg part content-type process-crypto) "Use the mm-decode/mm-view functions to display a part in the From 1c80020e701c7323de137c0616fc8864443d7bd3 Mon Sep 17 00:00:00 2001 From: Jonas Bernoulli Date: Sat, 8 Aug 2020 13:50:09 +0200 Subject: [PATCH 369/427] try-emacs-mua: Trim `require' advice for Emacs 25 - Since Emacs 25 comes with `load-prefer-newer' we can remove the complicated variant of the advice, which implemented a poorman's version of that. - Since Emacs 25 comes with the new advice mechanism, we can use that now for the simple variant of the advice, which just informs about the library that is being required. --- devel/try-emacs-mua | 26 ++++---------------------- 1 file changed, 4 insertions(+), 22 deletions(-) diff --git a/devel/try-emacs-mua b/devel/try-emacs-mua index 041f6216..585d6242 100755 --- a/devel/try-emacs-mua +++ b/devel/try-emacs-mua @@ -44,28 +44,10 @@ while looking at: " pdir "emacs\n\nexit emacs (y or n)? ") try-notmuch-emacs-directory (concat pdir "emacs/") load-path (cons try-notmuch-emacs-directory load-path))) -;; they say advice doesn't work for primitives (functions from c source) -;; well, these 'before' advice works for emacs 23.1 - 24.5 (at least) -;; ...and for our purposes 24.3 is enough (there is no load-prefer-newer there) -;; note also that the old, "obsolete" defadvice mechanism was used, but that -;; is the only one available for emacs 23 and 24 up to 24.3. - -(if (boundp 'load-prefer-newer) - (defadvice require (before before-require activate) - (unless (featurep feature) - (message "require: %s" feature))) - ;; else: special require "short-circuit"; after load feature is provided... - ;; ... in notmuch sources we always use require and there are no loops - (defadvice require (before before-require activate) - (unless (featurep feature) - (message "require: %s" feature) - (let ((name (symbol-name feature))) - (if (and (string-match "^notmuch" name) - (file-newer-than-file-p - (concat try-notmuch-emacs-directory name ".el") - (concat try-notmuch-emacs-directory name ".elc"))) - (load (concat try-notmuch-emacs-directory name ".el") nil nil t t) - ))))) +(define-advice require + (:before (feature &optional _filename _noerror) notmuch) + (unless (featurep feature) + (message "require: %s" feature))) (insert "Found notmuch emacs client in " try-notmuch-emacs-directory "\n") From 00dc5dd82435f9130df771dd3b9ac54283bdd495 Mon Sep 17 00:00:00 2001 From: Tomi Ollila Date: Tue, 11 Aug 2020 10:38:19 +0300 Subject: [PATCH 370/427] configure: Check if emacs >= 25 (instead of >= 24) is available "The minimum supported major version of GNU Emacs is now 25.1." 25.1 is the first "released" version of Emacs 25. --- configure | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/configure b/configure index a357eb91..40e8b255 100755 --- a/configure +++ b/configure @@ -829,8 +829,8 @@ if [ -z "${EMACSETCDIR-}" ]; then fi if [ $WITH_EMACS = "1" ]; then - printf "Checking if emacs (>= 24) is available... " - if emacs --quick --batch --eval '(if (< emacs-major-version 24) (kill-emacs 1))' > /dev/null 2>&1; then + printf "Checking if emacs (>= 25) is available... " + if emacs --quick --batch --eval '(if (< emacs-major-version 25) (kill-emacs 1))' > /dev/null 2>&1; then printf "Yes.\n" else printf "No (disabling emacs related parts of build)\n" From 0d4a3c71850f6440f33b10ebfd2c798611bffdcb Mon Sep 17 00:00:00 2001 From: Tomi Ollila Date: Mon, 27 Jul 2020 22:38:33 +0300 Subject: [PATCH 371/427] notmuch-mutt: replace shell pipeline with internal pipe processing The shell pipeline used to symlink files based in search results to "cache" directory for mutt(1) to use was prone to portability problems (due to /bin/sh differences). The replacement executes `notmuch search` without intermediate shell (so shell_quote was removed in this case), reads the filenames from piped output and symlinks files internally. --- contrib/notmuch-mutt/notmuch-mutt | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/contrib/notmuch-mutt/notmuch-mutt b/contrib/notmuch-mutt/notmuch-mutt index d33223bd..d1e2c084 100755 --- a/contrib/notmuch-mutt/notmuch-mutt +++ b/contrib/notmuch-mutt/notmuch-mutt @@ -12,6 +12,7 @@ use strict; use warnings; use File::Path; +use File::Basename; use Getopt::Long qw(:config no_getopt_compat); use Mail::Header; use Mail::Box::Maildir; @@ -41,16 +42,17 @@ sub search($$$) { my ($maildir, $remove_dups, $query) = @_; my $dup_option = ""; - $query = shell_quote($query); - - if ($remove_dups) { - $dup_option = "--duplicate=1"; - } + my @args = qw/notmuch search --output=files/; + push @args, "--duplicate=1" if $remove_dups; + push @args, $query; empty_maildir($maildir); - system("notmuch search --output=files $dup_option $query" - . " | sed -e 's: :\\\\ :g'" - . " | while IFS= read -r searchoutput; do ln -s \$searchoutput $maildir/cur/; done"); + open my $pipe, '-|', @args or die "Running @args failed: $!\n"; + while (<$pipe>) { + chomp; + my $ln = "$maildir/cur/" . basename $_; + symlink $_, "$ln" or warn "Failed to symlink '$_', '$ln': $!\n"; + } } sub prompt($$) { From adb90b9bb633c82f41a6c04c07f6becc12f332aa Mon Sep 17 00:00:00 2001 From: Teemu Likonen Date: Sat, 15 Aug 2020 09:28:29 +0300 Subject: [PATCH 372/427] Emacs: Indent first header line only when indentation is turned on Previously in message-show mode message's first header line (From header) was always indented, even if user had turned thread indentation off with "<" (notmuch-show-toggle-thread-indentation) command. This change modifies notmuch-show-insert-headerline function so that it doesn't indent the first header line if notmuch-show-indent-content variable is nil. This change also modifies tests so that they expect this new output format: test/emacs-show.expected-output/notmuch-show-indent-thread-content-off --- emacs/notmuch-show.el | 5 ++++- .../notmuch-show-indent-thread-content-off | 12 ++++++------ 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/emacs/notmuch-show.el b/emacs/notmuch-show.el index b0f2d28b..5015d2ae 100644 --- a/emacs/notmuch-show.el +++ b/emacs/notmuch-show.el @@ -468,7 +468,10 @@ message at DEPTH in the current thread." ;; invisible U+200E LEFT-TO-RIGHT MARK character which forces ;; the header paragraph as left-to-right text. (insert (propertize (string ?\x200e) 'invisible t))) - (insert (notmuch-show-spaces-n (* notmuch-show-indent-messages-width depth)) + (insert (if notmuch-show-indent-content + (notmuch-show-spaces-n (* notmuch-show-indent-messages-width + depth)) + "") from " (" date diff --git a/test/emacs-show.expected-output/notmuch-show-indent-thread-content-off b/test/emacs-show.expected-output/notmuch-show-indent-thread-content-off index 1a06374d..0bb58330 100644 --- a/test/emacs-show.expected-output/notmuch-show-indent-thread-content-off +++ b/test/emacs-show.expected-output/notmuch-show-indent-thread-content-off @@ -31,8 +31,8 @@ Cheers, [ application/pgp-signature ] [ text/plain ] [ 4-line signature. Click/Enter to show. ] - Mikhail Gusarov (2009-11-17) (inbox signed unread) - Lars Kellogg-Stedman (2009-11-17) (inbox signed) +Mikhail Gusarov (2009-11-17) (inbox signed unread) +Lars Kellogg-Stedman (2009-11-17) (inbox signed) Subject: Re: [notmuch] Working with Maildir storage? To: Mikhail Gusarov Cc: notmuch@notmuchmail.org @@ -57,9 +57,9 @@ It doesn't look like the patch is in git yet. [ application/pgp-signature ] [ text/plain ] [ 4-line signature. Click/Enter to show. ] - Mikhail Gusarov (2009-11-17) (inbox unread) - Keith Packard (2009-11-17) (inbox unread) - Lars Kellogg-Stedman (2009-11-18) (inbox signed unread) +Mikhail Gusarov (2009-11-17) (inbox unread) +Keith Packard (2009-11-17) (inbox unread) +Lars Kellogg-Stedman (2009-11-18) (inbox signed unread) Subject: Re: [notmuch] Working with Maildir storage? To: Keith Packard Cc: notmuch@notmuchmail.org @@ -79,4 +79,4 @@ missing "#include " (for the uint32_t on line 466). [ application/pgp-signature ] [ text/plain ] [ 4-line signature. Click/Enter to show. ] - Carl Worth (2009-11-18) (inbox unread) +Carl Worth (2009-11-18) (inbox unread) From 25f9a42287a7a2ad58a1dff5ccfe75fe7e26d8db Mon Sep 17 00:00:00 2001 From: David Bremner Date: Sat, 15 Aug 2020 09:15:05 -0300 Subject: [PATCH 373/427] test: update README to reflect dropping upgrade tests These test databases have been unneeded since ee897cab8. --- test/README | 8 -------- 1 file changed, 8 deletions(-) diff --git a/test/README b/test/README index 3f54af58..11eaf18f 100644 --- a/test/README +++ b/test/README @@ -79,14 +79,6 @@ The following command-line options are available when running tests: As the names depend on the tests' file names, it is safe to run the tests with this option in parallel. -Certain tests require precomputed databases to complete. You can fetch these -databases with - - make download-test-databases - -If you do not download the test databases, the relevant tests will be -skipped. - When invoking the test suite via "make test" any of the above options can be specified as follows: From 189175ecd6fde78ded219a4bc6dd7b649564c30f Mon Sep 17 00:00:00 2001 From: William Casarin Date: Tue, 11 Aug 2020 10:36:51 -0700 Subject: [PATCH 374/427] emacs/tree: introduce notmuch-tree-parent-buffer variable This variable will be used in a similar fashion to notmuch-show-parent-buffer. It will be used to navigate between threads from the parent search buffer. Signed-off-by: William Casarin --- emacs/notmuch-tree.el | 6 +++++- emacs/notmuch.el | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/emacs/notmuch-tree.el b/emacs/notmuch-tree.el index fbba4bb3..29f64851 100644 --- a/emacs/notmuch-tree.el +++ b/emacs/notmuch-tree.el @@ -222,6 +222,9 @@ For example: "A buffer local copy of argument open-target to the function notmuch-tree.") (make-variable-buffer-local 'notmuch-tree-open-target) +(defvar notmuch-tree-parent-buffer nil) +(make-variable-buffer-local 'notmuch-tree-parent-buffer) + (defvar notmuch-tree-message-window nil "The window of the message pane. @@ -1050,7 +1053,7 @@ the same as for the function notmuch-tree." ")") notmuch-tree-basic-query)) -(defun notmuch-tree (&optional query query-context target buffer-name open-target unthreaded) +(defun notmuch-tree (&optional query query-context target buffer-name open-target unthreaded parent-buffer) "Display threads matching QUERY in tree view. The arguments are: @@ -1080,6 +1083,7 @@ The arguments are: ;; Don't track undo information for this buffer (set 'buffer-undo-list t) (notmuch-tree-worker query query-context target open-target unthreaded) + (setq notmuch-tree-parent-buffer parent-buffer) (setq truncate-lines t)) (defun notmuch-unthreaded (&optional query query-context target buffer-name open-target) diff --git a/emacs/notmuch.el b/emacs/notmuch.el index babddbb6..8132cea6 100644 --- a/emacs/notmuch.el +++ b/emacs/notmuch.el @@ -545,7 +545,7 @@ thread." notmuch-search-query-string nil (notmuch-prettify-subject (notmuch-search-find-subject)) - t)) + t nil (current-buffer))) (defun notmuch-search-reply-to-thread (&optional prompt-for-sender) "Begin composing a reply-all to the entire current thread in a new buffer." From 874f14ec2b25f7593edf872c254db344195fce84 Mon Sep 17 00:00:00 2001 From: William Casarin Date: Tue, 11 Aug 2020 10:36:52 -0700 Subject: [PATCH 375/427] emacs/tree: enable moving to next thread in search results This introduces a new function called notmuch-tree-next-thread-from-search which is analogous to notmuch-show-next-thread. It will switch to the next or previous thread from the parent search results. We rename notmuch-tree-{prev,next}-thread to a more descriptive notmuch-tree-{prev,next}-thread-in-tree to reflect the fact that it only moves to the next thread in the current tree. notmuch-tree-next-thread now switches to the next thread in the current tree first, but if there are none, it looks for the next tree in the search results. This makes notmuch-tree feel more like notmuch-show when using the M-Enter, M-n and M-p bindings. Signed-off-by: William Casarin --- emacs/notmuch-tree.el | 40 +++++++++++++++++++++++++++++++++++++--- 1 file changed, 37 insertions(+), 3 deletions(-) diff --git a/emacs/notmuch-tree.el b/emacs/notmuch-tree.el index 29f64851..bf500b60 100644 --- a/emacs/notmuch-tree.el +++ b/emacs/notmuch-tree.el @@ -725,12 +725,14 @@ nil otherwise." (while (not (or (notmuch-tree-get-prop :first) (eobp))) (forward-line -1)))) -(defun notmuch-tree-prev-thread () +(defun notmuch-tree-prev-thread-in-tree () + "Move to the previous thread in the current tree" (interactive) (forward-line -1) - (notmuch-tree-thread-top)) + (notmuch-tree-thread-top) + (not (bobp))) -(defun notmuch-tree-next-thread () +(defun notmuch-tree-next-thread-in-tree () "Get the next thread in the current tree. Returns t if a thread was found or nil if not." (interactive) @@ -739,6 +741,38 @@ found or nil if not." (forward-line 1)) (not (eobp))) +(defun notmuch-tree-next-thread-from-search (&optional previous) + "Move to the next thread in the parent search results, if any. + +If PREVIOUS is non-nil, move to the previous item in the +search results instead." + (interactive "P") + (let ((parent-buffer notmuch-tree-parent-buffer)) + (notmuch-tree-quit t) + (when (buffer-live-p parent-buffer) + (switch-to-buffer parent-buffer) + (if previous + (notmuch-search-previous-thread) + (notmuch-search-next-thread)) + (notmuch-tree-from-search-thread)))) + +(defun notmuch-tree-next-thread (&optional previous) + "Move to the next thread in the current tree or parent search +results + +If PREVIOUS is non-nil, move to the previous thread in the tree or +search results instead." + (interactive) + (unless (if previous (notmuch-tree-prev-thread-in-tree) + (notmuch-tree-next-thread-in-tree)) + (notmuch-tree-next-thread-from-search previous))) + +(defun notmuch-tree-prev-thread () + "Move to the previous thread in the current tree or parent search +results" + (interactive) + (notmuch-tree-next-thread t)) + (defun notmuch-tree-thread-mapcar (function) "Iterate through all messages in the current thread and call FUNCTION for side effects." From bcfd8575e5d3040a6b536928e9c163592e689da9 Mon Sep 17 00:00:00 2001 From: William Casarin Date: Tue, 11 Aug 2020 10:36:53 -0700 Subject: [PATCH 376/427] emacs/tree: add notmuch-tree-archive-thread-then-next Now that notmuch-tree-next-thread acts more like its notmuch-show counterpart, let's update the binding to move to the next thread after archiving. Signed-off-by: William Casarin --- emacs/notmuch-tree.el | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/emacs/notmuch-tree.el b/emacs/notmuch-tree.el index bf500b60..2bb7c80f 100644 --- a/emacs/notmuch-tree.el +++ b/emacs/notmuch-tree.el @@ -338,7 +338,7 @@ FUNC." (define-key map [mouse-1] 'notmuch-tree-show-message) (define-key map "x" 'notmuch-tree-archive-message-then-next-or-exit) (define-key map "X" 'notmuch-tree-archive-thread-then-exit) - (define-key map "A" 'notmuch-tree-archive-thread) + (define-key map "A" 'notmuch-tree-archive-thread-then-next) (define-key map "a" 'notmuch-tree-archive-message-then-next) (define-key map "z" 'notmuch-tree-to-tree) (define-key map "n" 'notmuch-tree-next-matching-message) @@ -497,6 +497,12 @@ NOT change the database." (notmuch-tree-close-message-window) (notmuch-tree query))) +(defun notmuch-tree-archive-thread-then-next () + "Archive all messages in the current buffer, then show next thread from search." + (interactive) + (notmuch-tree-archive-thread) + (notmuch-tree-next-thread)) + (defun notmuch-unthreaded-from-tree-current-query () "Switch from tree view to unthreaded view." (interactive) From efb135bed1ad3e7d15b42dcf07b202962cca9d9f Mon Sep 17 00:00:00 2001 From: David Bremner Date: Sun, 16 Aug 2020 11:06:13 -0300 Subject: [PATCH 377/427] version: bump to 0.31~rc0 Start the release process for 0.31 --- bindings/python-cffi/version | 2 +- bindings/python/notmuch/version.py | 2 +- version | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/bindings/python-cffi/version b/bindings/python-cffi/version index f7c6c31b..62d9e333 100644 --- a/bindings/python-cffi/version +++ b/bindings/python-cffi/version @@ -1 +1 @@ -0.30 +0.31~rc0 diff --git a/bindings/python/notmuch/version.py b/bindings/python/notmuch/version.py index 36b4cfca..a09a8b72 100644 --- a/bindings/python/notmuch/version.py +++ b/bindings/python/notmuch/version.py @@ -1,3 +1,3 @@ # this file should be kept in sync with ../../../version -__VERSION__ = '0.30' +__VERSION__ = '0.31~rc0' SOVERSION = '5' diff --git a/version b/version index f7c6c31b..62d9e333 100644 --- a/version +++ b/version @@ -1 +1 @@ -0.30 +0.31~rc0 From e86b3e230e4e2251936bf13c73ef333896b10b2b Mon Sep 17 00:00:00 2001 From: David Bremner Date: Sun, 16 Aug 2020 11:09:18 -0300 Subject: [PATCH 378/427] debian: start changelog for 0.31~rc0-1 --- debian/changelog | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/debian/changelog b/debian/changelog index b6117ec3..4be5e640 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,10 @@ +notmuch (0.31~rc0-1) experimental; urgency=medium + + * New upstream release candidate. + * Update notmuch-emacs for compatibility with GNU Emacs 27.1. + + -- David Bremner Sun, 16 Aug 2020 11:08:14 -0300 + notmuch (0.30-1) unstable; urgency=medium * New upstream release From 557aa2d75f05b3f9b8a40083fb020b770fc9eee1 Mon Sep 17 00:00:00 2001 From: David Bremner Date: Sun, 16 Aug 2020 11:12:59 -0300 Subject: [PATCH 379/427] debian: update symbols for 0.31 Two new API entries for better error handling --- debian/libnotmuch5.symbols | 2 ++ 1 file changed, 2 insertions(+) diff --git a/debian/libnotmuch5.symbols b/debian/libnotmuch5.symbols index 59c4fde5..ec01593b 100644 --- a/debian/libnotmuch5.symbols +++ b/debian/libnotmuch5.symbols @@ -56,6 +56,7 @@ libnotmuch.so.5 libnotmuch5 #MINVER# notmuch_message_get_filename@Base 0.3 notmuch_message_get_filenames@Base 0.5 notmuch_message_get_flag@Base 0.3 + notmuch_message_get_flag_st@Base 0.31~rc0 notmuch_message_get_header@Base 0.3 notmuch_message_get_message_id@Base 0.3 notmuch_message_get_properties@Base 0.23~rc0 @@ -64,6 +65,7 @@ libnotmuch.so.5 libnotmuch5 #MINVER# notmuch_message_get_tags@Base 0.3 notmuch_message_get_thread_id@Base 0.3 notmuch_message_has_maildir_flag@Base 0.26~rc0 + notmuch_message_has_maildir_flag_st@Base 0.31~rc0 notmuch_message_maildir_flags_to_tags@Base 0.5 notmuch_message_properties_destroy@Base 0.23~rc0 notmuch_message_properties_key@Base 0.23~rc0 From 8776faf6d5118e8152ecfacf94accf35ccebf1d2 Mon Sep 17 00:00:00 2001 From: David Bremner Date: Sun, 16 Aug 2020 11:32:28 -0300 Subject: [PATCH 380/427] devel/release-checks.sh: use grep to find copyright year. This is quite fragile, but it works for now, unlike the python version. In general it seems conf.py is not intended to be evaluated outside of sphinx, as it assumes certain global names (in particular "tags") are defined. --- devel/release-checks.sh | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/devel/release-checks.sh b/devel/release-checks.sh index 7ba94822..cfa208d5 100755 --- a/devel/release-checks.sh +++ b/devel/release-checks.sh @@ -178,10 +178,7 @@ esac year=`exec date +%Y` echo -n "Checking that copyright in documentation contains 2009-$year... " # Read the value of variable `copyright' defined in 'doc/conf.py'. -# As __file__ is not defined when python command is given from command line, -# it is defined before contents of 'doc/conf.py' (which dereferences __file__) -# is executed. -copyrightline=`exec python -c "with open('doc/conf.py') as cf: __file__ = ''; exec(cf.read()); print(copyright)"` +copyrightline=$(grep ^copyright doc/conf.py) case $copyrightline in *2009-$year*) echo Yes. ;; From a1b1fe85c2fe17dcce635b36f3bcb8cf514e3636 Mon Sep 17 00:00:00 2001 From: David Bremner Date: Sun, 16 Aug 2020 12:27:11 -0300 Subject: [PATCH 381/427] build: clean up sphinx.config Follow the existing practice and remove it under "distclean", same as sh.config and Makefile.config --- Makefile.local | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile.local b/Makefile.local index 156c8ce1..c65cee7c 100644 --- a/Makefile.local +++ b/Makefile.local @@ -294,7 +294,7 @@ CLEAN := $(CLEAN) notmuch notmuch-shared $(notmuch_client_modules) CLEAN := $(CLEAN) version.stamp notmuch-*.tar.gz.tmp CLEAN := $(CLEAN) .deps -DISTCLEAN := $(DISTCLEAN) .first-build-message Makefile.config sh.config +DISTCLEAN := $(DISTCLEAN) .first-build-message Makefile.config sh.config sphinx.config CPPCHECK_STAMPS := $(SRCS:%=.stamps/cppcheck/%) .PHONY: cppcheck From fe449f779d2a5ab5243a315a12f4a85b625dc078 Mon Sep 17 00:00:00 2001 From: David Bremner Date: Sun, 16 Aug 2020 14:04:36 -0300 Subject: [PATCH 382/427] test: fix uninitialized variable use in T562-lib-database Fix a copy paste error of using the boolean ret as a notmuch_status_t, and uninitialized. --- test/T562-lib-database.sh | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/test/T562-lib-database.sh b/test/T562-lib-database.sh index d6097418..dd4f2566 100755 --- a/test/T562-lib-database.sh +++ b/test/T562-lib-database.sh @@ -154,11 +154,9 @@ test_expect_equal_file EXPECTED OUTPUT test_begin_subtest "upgrade a closed db" cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR} { - notmuch_bool_t ret; - EXPECT0(notmuch_database_close (db)); stat = notmuch_database_upgrade (db, NULL, NULL); - printf ("%d\n", ret == NOTMUCH_STATUS_SUCCESS); + printf ("%d\n", stat == NOTMUCH_STATUS_SUCCESS); } EOF cat < EXPECTED From f41148f010fdc69091be46be7a1e43b1270cb656 Mon Sep 17 00:00:00 2001 From: Tomi Ollila Date: Mon, 17 Aug 2020 23:39:22 +0300 Subject: [PATCH 383/427] NEWS: notmuch-mutt: system(shell pipeline) replaced internally --- NEWS | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/NEWS b/NEWS index 4160e39c..b25bfaa3 100644 --- a/NEWS +++ b/NEWS @@ -6,6 +6,14 @@ Emacs The minimum supported major version of GNU Emacs is now 25.1. +Notmuch-Mutt +------------ + +The shell pipeline executed by notmuch-mutt, which symlinked matched +files to a maildir for mutt to access is replaced with internal perl +processing. This search operation is now more portable, and somewhat +faster. + Notmuch 0.30 (2020-07-10) ========================= From e6f95910b32c19d4f99c6dfdc265d74ccd5f404b Mon Sep 17 00:00:00 2001 From: David Bremner Date: Mon, 17 Aug 2020 21:02:30 -0300 Subject: [PATCH 384/427] version: bump to 0.31~rc1 --- bindings/python-cffi/version | 2 +- bindings/python/notmuch/version.py | 2 +- version | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/bindings/python-cffi/version b/bindings/python-cffi/version index 62d9e333..811b2d53 100644 --- a/bindings/python-cffi/version +++ b/bindings/python-cffi/version @@ -1 +1 @@ -0.31~rc0 +0.31~rc1 diff --git a/bindings/python/notmuch/version.py b/bindings/python/notmuch/version.py index a09a8b72..05c7fa7f 100644 --- a/bindings/python/notmuch/version.py +++ b/bindings/python/notmuch/version.py @@ -1,3 +1,3 @@ # this file should be kept in sync with ../../../version -__VERSION__ = '0.31~rc0' +__VERSION__ = '0.31~rc1' SOVERSION = '5' diff --git a/version b/version index 62d9e333..811b2d53 100644 --- a/version +++ b/version @@ -1 +1 @@ -0.31~rc0 +0.31~rc1 From 75ec89dfb4c254ba0b88ddb479e6ede6bfb7d0c7 Mon Sep 17 00:00:00 2001 From: David Bremner Date: Tue, 18 Aug 2020 07:58:28 -0300 Subject: [PATCH 385/427] update changelog for 0.31~rc1-1 --- debian/changelog | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/debian/changelog b/debian/changelog index 4be5e640..9ef48d98 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,10 @@ +notmuch (0.31~rc1-1) experimental; urgency=medium + + * Fix buggy test in T590-config + * Clean up generated file in source package. + + -- David Bremner Mon, 17 Aug 2020 21:05:46 -0300 + notmuch (0.31~rc0-1) experimental; urgency=medium * New upstream release candidate. From d7732b2b6aa0a62262061b3b330221d2fdf772e0 Mon Sep 17 00:00:00 2001 From: David Bremner Date: Tue, 18 Aug 2020 08:48:02 -0300 Subject: [PATCH 386/427] debian/changelog: fix typo --- debian/changelog | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/debian/changelog b/debian/changelog index 9ef48d98..c645dd12 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,6 +1,6 @@ notmuch (0.31~rc1-1) experimental; urgency=medium - * Fix buggy test in T590-config + * Fix buggy test in T562-lib-database * Clean up generated file in source package. -- David Bremner Mon, 17 Aug 2020 21:05:46 -0300 From 88ae4f02511c4dd61ea90b28794f469819d543be Mon Sep 17 00:00:00 2001 From: Sean Whitton Date: Tue, 11 Aug 2020 09:59:13 -0700 Subject: [PATCH 387/427] emacs: Use pop-to-buffer-same-window rather than switch-to-buffer This means that notmuch commands obey display-buffer-alist so the user can customize how buffers show up. It also permits the use of C-x 4 4, C-x 5 5 and C-x t t, available in Emacs 28. For example, one can use C-x 4 4 M-x notmuch-jump-search RET to open a saved search in another window rather than the current window. Or in notmuch-search mode, C-x 5 5 RET to view the message at point in a new frame. notmuch-tree has custom buffer display logic, so bind display-buffer-overriding-action to make pop-to-buffer-same-window behave exactly as switch-to-buffer while that function is running. --- emacs/notmuch-draft.el | 3 ++- emacs/notmuch-hello.el | 2 +- emacs/notmuch-show.el | 8 ++++---- emacs/notmuch-tree.el | 13 +++++++++---- emacs/notmuch.el | 4 ++-- 5 files changed, 18 insertions(+), 12 deletions(-) diff --git a/emacs/notmuch-draft.el b/emacs/notmuch-draft.el index 759e6c9e..283830ad 100644 --- a/emacs/notmuch-draft.el +++ b/emacs/notmuch-draft.el @@ -232,7 +232,8 @@ applied to newly inserted messages)." (draft (equal tags (notmuch-update-tags tags notmuch-draft-tags)))) (when (or draft (yes-or-no-p "Message does not appear to be a draft: edit as new? ")) - (switch-to-buffer (get-buffer-create (concat "*notmuch-draft-" id "*"))) + (pop-to-buffer-same-window + (get-buffer-create (concat "*notmuch-draft-" id "*"))) (setq buffer-read-only nil) (erase-buffer) (let ((coding-system-for-read 'no-conversion)) diff --git a/emacs/notmuch-hello.el b/emacs/notmuch-hello.el index c127bba9..bb60a890 100644 --- a/emacs/notmuch-hello.el +++ b/emacs/notmuch-hello.el @@ -973,7 +973,7 @@ following: (let ((notmuch-hello-auto-refresh nil)) (if no-display (set-buffer "*notmuch-hello*") - (switch-to-buffer "*notmuch-hello*"))) + (pop-to-buffer-same-window "*notmuch-hello*"))) ;; Install auto-refresh hook (when notmuch-hello-auto-refresh (add-hook 'window-configuration-change-hook diff --git a/emacs/notmuch-show.el b/emacs/notmuch-show.el index 5015d2ae..b08ceb97 100644 --- a/emacs/notmuch-show.el +++ b/emacs/notmuch-show.el @@ -1244,7 +1244,7 @@ matched." (eval (car (get 'mm-inline-override-types 'standard-value)))) (cons "application/*" mm-inline-override-types) mm-inline-override-types))) - (switch-to-buffer (get-buffer-create buffer-name)) + (pop-to-buffer-same-window (get-buffer-create buffer-name)) ;; No need to track undo information for this buffer. (setq buffer-undo-list t) (notmuch-show-mode) @@ -2001,7 +2001,7 @@ to show, nil otherwise." (let* ((id (notmuch-show-get-message-id)) (buf (get-buffer-create (concat "*notmuch-raw-" id "*"))) (inhibit-read-only t)) - (switch-to-buffer buf) + (pop-to-buffer-same-window buf) (erase-buffer) (let ((coding-system-for-read 'no-conversion)) (call-process notmuch-command nil t nil "show" "--format=raw" id)) @@ -2060,7 +2060,7 @@ message." (set-buffer-modified-p nil) (setq buffer-read-only t) (unless (zerop exit-code) - (switch-to-buffer-other-window buf) + (pop-to-buffer buf) (message (format "Command '%s' exited abnormally with code %d" shell-command exit-code)))))))) @@ -2468,7 +2468,7 @@ If the part is displayed in an external application then close the new buffer." (let ((buf (get-buffer-create (generate-new-buffer-name (concat " *notmuch-internal-part*"))))) - (switch-to-buffer buf) + (pop-to-buffer-same-window buf) (if (eq (mm-display-part handle) 'external) (kill-buffer buf) (goto-char (point-min)) diff --git a/emacs/notmuch-tree.el b/emacs/notmuch-tree.el index 2bb7c80f..f342f85a 100644 --- a/emacs/notmuch-tree.el +++ b/emacs/notmuch-tree.el @@ -552,9 +552,14 @@ NOT change the database." (setq notmuch-tree-message-window (split-window-vertically (/ (window-height) 4))) (with-selected-window notmuch-tree-message-window - ;; Since we are only displaying one message do not indent. - (let ((notmuch-show-indent-messages-width 0) - (notmuch-show-only-matching-messages t)) + (let (;; Since we are only displaying one message do not indent. + (notmuch-show-indent-messages-width 0) + (notmuch-show-only-matching-messages t) + ;; Ensure that `pop-to-buffer-same-window' uses the + ;; window we want it to use. + (display-buffer-overriding-action + '((display-buffer-same-window) + (inhibit-same-window . nil)))) (setq buffer (notmuch-show id)))) ;; We need the `let' as notmuch-tree-message-window is buffer local. (let ((window notmuch-tree-message-window)) @@ -1119,7 +1124,7 @@ The arguments are: (if unthreaded "unthreaded-" "tree-") query "*"))))) (inhibit-read-only t)) - (switch-to-buffer buffer)) + (pop-to-buffer-same-window buffer)) ;; Don't track undo information for this buffer (set 'buffer-undo-list t) (notmuch-tree-worker query query-context target open-target unthreaded) diff --git a/emacs/notmuch.el b/emacs/notmuch.el index 8132cea6..193a1255 100644 --- a/emacs/notmuch.el +++ b/emacs/notmuch.el @@ -994,7 +994,7 @@ the configured default sort order." (buffer (get-buffer-create (notmuch-search-buffer-title query)))) (if no-display (set-buffer buffer) - (switch-to-buffer buffer)) + (pop-to-buffer-same-window buffer)) ;; avoid wiping out third party buffer-local variables in the case ;; where we're just refreshing or changing the sort order of an ;; existing search results buffer @@ -1134,7 +1134,7 @@ notmuch buffers exist, run `notmuch'." ;; If the first one we found is any other than the starting ;; buffer, switch to it. (unless (eq first start) - (switch-to-buffer first)) + (pop-to-buffer-same-window first)) (notmuch)))) ;;;; Imenu Support From d6f3694188e6101a2a4ab84e6d203d2d4dcc241a Mon Sep 17 00:00:00 2001 From: David Bremner Date: Tue, 18 Aug 2020 08:43:06 -0300 Subject: [PATCH 388/427] AUTHORS: update for 0.31 Just shuffles existing authors around, mainly due to Jonas's enthusiastic cleanup work. --- AUTHORS | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/AUTHORS b/AUTHORS index 83113f4c..6e872084 100644 --- a/AUTHORS +++ b/AUTHORS @@ -23,7 +23,7 @@ system libraries, compilers, and the kernel that make it all work (thanks GNU, thanks Linux). Thanks to everyone who has played a part! The following list of people have at least 15 lines of code in the -Notmuch 0.30 release (calculated by devel/author-scan.sh). +Notmuch 0.31 release (calculated by devel/author-scan.sh). David Bremner Carl Worth @@ -42,12 +42,13 @@ Notmuch 0.30 release (calculated by devel/author-scan.sh). W. Trevor King Jameson Graef Rollins Felipe Contreras + Jonas Bernoulli Pieter Praet Peter Feigl Dmitry Kurochkin Peter Wang - Daniel Schoepe Gregor Zattler + Daniel Schoepe Keith Packard Adam Wolfe Gordon Stefano Zacchiroli @@ -55,27 +56,26 @@ Notmuch 0.30 release (calculated by devel/author-scan.sh). laochailan Ben Gamari Aaron Ecay - Jesse Rosenthal l-m-h@web.de Thomas Jost + Jesse Rosenthal Dirk Hohndel Blake Jones - Jonas Bernoulli Damien Cassou - Vladimir Panteleev Anton Khirnov Matt Armstrong + Vladimir Panteleev + William Casarin Örjan Ekeberg Jan Janak Patrick Totzke - Chunyang Xu - rhn Ruben Pollan + rhn Ioan-Adrian Ratiu Ethan Glasser-Camp + Chunyang Xu Todd Chris Wilson - William Casarin Yuri Volchkov Cédric Cabessa Mark Anderson @@ -84,17 +84,17 @@ Notmuch 0.30 release (calculated by devel/author-scan.sh). Ludovic LANGE Sebastian Poeplau Mikhail - Gaute Hope Keith Amidon + Gaute Hope martin f. krafft Jeffrey C. Ollie - Bart Trojanowski Jameson Rollins Scott Henson + Bart Trojanowski Vladimir Marek Servilio Afre Puentes - Kevin McCarthy Tomas Carnecky + Kevin McCarthy Kevin J. McCarthy Scott Robinson Wael M. Nasreddine @@ -111,9 +111,9 @@ Notmuch 0.30 release (calculated by devel/author-scan.sh). Luis Ressel Sergei Shilovsky Trevor Jim - Jinwoo Lee Uli Scholler Matthew Lear + Jinwoo Lee Amadeusz Żołnowski Here is an incomplete list of other people that have made From 3512e2bc83b226a6fddff2940f3929b16ffea67c Mon Sep 17 00:00:00 2001 From: Teemu Likonen Date: Sun, 16 Aug 2020 20:13:23 +0300 Subject: [PATCH 389/427] Emacs: Fix notmuch-message-summary-face definition Emacs face definition forms are either ((DISPLAY . PLIST) (DISPLAY . PLIST)) or ((DISPLAY PLIST) ;For backward compatibility. (DISPLAY PLIST)) Commit a2388bc56e55da5d5695816818274f8a84b0ed92 (2020-08-08) follows neither of the correct formats. It defines: `((((class color) (background light)) ,@(and (>= emacs-major-version 27) '(:extend t)) (:background "#f0f0f0")) (((class color) (background dark)) ,@(and (>= emacs-major-version 27) '(:extend t)) (:background "#303030"))) which produces: ((DISPLAY :extend t (:background "#f0f0f0")) (DISPLAY :extend t (:background "#303030"))) And that is wrong format. This change fixes the face definition form to produce: ((DISPLAY :extend t :background "#f0f0f0") (DISPLAY :extend t :background "#303030")) which follows the (DISPLAY . PLIST) format (see above). --- emacs/notmuch.el | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/emacs/notmuch.el b/emacs/notmuch.el index 193a1255..83bcee57 100644 --- a/emacs/notmuch.el +++ b/emacs/notmuch.el @@ -275,10 +275,10 @@ there will be called at other points of notmuch execution." (defface notmuch-message-summary-face `((((class color) (background light)) ,@(and (>= emacs-major-version 27) '(:extend t)) - (:background "#f0f0f0")) + :background "#f0f0f0") (((class color) (background dark)) ,@(and (>= emacs-major-version 27) '(:extend t)) - (:background "#303030"))) + :background "#303030")) "Face for the single-line message summary in notmuch-show-mode." :group 'notmuch-show :group 'notmuch-faces) From 68bff37dd75eacdc6c041a7d40260d2be1289571 Mon Sep 17 00:00:00 2001 From: William Casarin Date: Sat, 22 Aug 2020 11:05:44 -0700 Subject: [PATCH 390/427] NEWS: add news entry for tree navigation changes Signed-off-by: William Casarin Amended-by: db, add verb --- NEWS | 2 ++ 1 file changed, 2 insertions(+) diff --git a/NEWS b/NEWS index b25bfaa3..040df4fd 100644 --- a/NEWS +++ b/NEWS @@ -6,6 +6,8 @@ Emacs The minimum supported major version of GNU Emacs is now 25.1. +Add support for moving between threads after notmuch-tree-from-search-thread. + Notmuch-Mutt ------------ From 1d7406595505f849f1284c64d09fefaa7410f107 Mon Sep 17 00:00:00 2001 From: David Bremner Date: Sun, 23 Aug 2020 08:53:37 -0300 Subject: [PATCH 391/427] NEWS: mention merging of documentation for python bindings --- NEWS | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/NEWS b/NEWS index 040df4fd..c1f09b18 100644 --- a/NEWS +++ b/NEWS @@ -16,6 +16,13 @@ files to a maildir for mutt to access is replaced with internal perl processing. This search operation is now more portable, and somewhat faster. +Python Bindings +--------------- + +Documentation for the python bindings is merged into the main +sphinx-doc documentation tree. The merged documentation can be built +with e.g. `make sphinx-html` + Notmuch 0.30 (2020-07-10) ========================= From 2fd1a878c7a968c23210d93e4fad566646479161 Mon Sep 17 00:00:00 2001 From: David Bremner Date: Sun, 23 Aug 2020 09:02:18 -0300 Subject: [PATCH 392/427] NEWS: mention exception handling changes --- NEWS | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/NEWS b/NEWS index c1f09b18..1045a317 100644 --- a/NEWS +++ b/NEWS @@ -16,6 +16,14 @@ files to a maildir for mutt to access is replaced with internal perl processing. This search operation is now more portable, and somewhat faster. +Library +------- + +Improve exception handling in the library. This should +largely eliminate terminations inside the library due to uncaught +exceptions or internal errors. No doubt there are a few uncovered +code paths still; please report them as bugs. + Python Bindings --------------- From 16ac5dbaaf21dac37d782b6efa7f3cc879b73861 Mon Sep 17 00:00:00 2001 From: David Bremner Date: Sun, 23 Aug 2020 09:16:16 -0300 Subject: [PATCH 393/427] NEWS: mention port to Xapian 1.5 --- NEWS | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/NEWS b/NEWS index 1045a317..abfd42fc 100644 --- a/NEWS +++ b/NEWS @@ -31,6 +31,12 @@ Documentation for the python bindings is merged into the main sphinx-doc documentation tree. The merged documentation can be built with e.g. `make sphinx-html` +Dependencies +------------ + +We now support building notmuch against Xapian 1.5 (the current +development version). + Notmuch 0.30 (2020-07-10) ========================= From 0f90b2ecebc2b07b2b000e6a11730e1c48f52305 Mon Sep 17 00:00:00 2001 From: David Bremner Date: Sun, 23 Aug 2020 09:19:28 -0300 Subject: [PATCH 394/427] NEWS: mention new API entries --- NEWS | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/NEWS b/NEWS index abfd42fc..cd7fde71 100644 --- a/NEWS +++ b/NEWS @@ -24,6 +24,10 @@ largely eliminate terminations inside the library due to uncaught exceptions or internal errors. No doubt there are a few uncovered code paths still; please report them as bugs. +Add `notmuch_message_get_flag_st` and +`notmuch_message_has_maildir_flag_st`, and deprecate the existing +non-status providing versions. + Python Bindings --------------- From a623f6b50a8c794fc99964bbbc27d8c70536a102 Mon Sep 17 00:00:00 2001 From: David Bremner Date: Sun, 23 Aug 2020 09:22:45 -0300 Subject: [PATCH 395/427] NEWS: remaining user visible library changes These could both cause / fix crashes for user code. --- NEWS | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/NEWS b/NEWS index cd7fde71..16a5b116 100644 --- a/NEWS +++ b/NEWS @@ -28,6 +28,12 @@ Add `notmuch_message_get_flag_st` and `notmuch_message_has_maildir_flag_st`, and deprecate the existing non-status providing versions. +Move memory de-allocation from `notmuch_database_close` to +`notmuch_database_destroy`. + +Handle relative filenames in `notmuch_database_index_file`, as +promised in the documentation. + Python Bindings --------------- From c0d340778c67deef0e414c22e33f80e59b281062 Mon Sep 17 00:00:00 2001 From: David Bremner Date: Tue, 25 Aug 2020 08:03:24 -0300 Subject: [PATCH 396/427] debian: suggest elpa-mailscripts These contain some useful functions for notmuch users, mainly wrappers for scripts from mailscripts. --- debian/changelog | 8 ++++++++ debian/control | 1 + 2 files changed, 9 insertions(+) diff --git a/debian/changelog b/debian/changelog index c645dd12..41e036e7 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,11 @@ +notmuch (0.31~rc2-1) experimental; urgency=medium + + * New upstream release candidate + * Bug fix: "suggest elpa-mailscripts", thanks to Sean Whitton (Closes: + #944269). + + -- David Bremner Tue, 25 Aug 2020 07:36:00 -0300 + notmuch (0.31~rc1-1) experimental; urgency=medium * Fix buggy test in T562-lib-database diff --git a/debian/control b/debian/control index e2bb166f..6e5a2750 100644 --- a/debian/control +++ b/debian/control @@ -160,6 +160,7 @@ Architecture: all Depends: ${elpa:Depends}, ${misc:Depends}, +Suggests: elpa-mailscripts Description: thread-based email index, search and tagging (emacs interface) Notmuch is a system for indexing, searching, reading, and tagging large collections of email messages in maildir or mh format. It uses From d54b7ae0495a534b3f02d0785bbb1385237a8aba Mon Sep 17 00:00:00 2001 From: David Bremner Date: Tue, 25 Aug 2020 08:03:25 -0300 Subject: [PATCH 397/427] debian: suggest mailscripts These contain several useful tools for the notmuch user, particularly on Debian. --- debian/changelog | 4 +++- debian/control | 2 ++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/debian/changelog b/debian/changelog index 41e036e7..b2da56c5 100644 --- a/debian/changelog +++ b/debian/changelog @@ -3,8 +3,10 @@ notmuch (0.31~rc2-1) experimental; urgency=medium * New upstream release candidate * Bug fix: "suggest elpa-mailscripts", thanks to Sean Whitton (Closes: #944269). + * Bug fix: "suggest mailscripts", thanks to Sean Whitton (Closes: + #944270). - -- David Bremner Tue, 25 Aug 2020 07:36:00 -0300 + -- David Bremner Tue, 25 Aug 2020 07:37:30 -0300 notmuch (0.31~rc1-1) experimental; urgency=medium diff --git a/debian/control b/debian/control index 6e5a2750..7cbe9508 100644 --- a/debian/control +++ b/debian/control @@ -53,6 +53,8 @@ Recommends: elpa-notmuch | notmuch-vim | notmuch-mutt | alot, gnupg-agent, gpgsm, +Suggests: + mailscripts Description: thread-based email index, search and tagging Notmuch is a system for indexing, searching, reading, and tagging large collections of email messages in maildir or mh format. It uses From d9385113ef3dd66ec8f84bb7d160de772273b6b4 Mon Sep 17 00:00:00 2001 From: David Bremner Date: Tue, 25 Aug 2020 08:03:26 -0300 Subject: [PATCH 398/427] debian: drop notmuch-emacs dependency package elpa-notmuch is now present in oldstable, and we don't need to support direct upgrades from older releases than that. --- debian/changelog | 4 +++- debian/control | 9 --------- 2 files changed, 3 insertions(+), 10 deletions(-) diff --git a/debian/changelog b/debian/changelog index b2da56c5..b800e361 100644 --- a/debian/changelog +++ b/debian/changelog @@ -5,8 +5,10 @@ notmuch (0.31~rc2-1) experimental; urgency=medium #944269). * Bug fix: "suggest mailscripts", thanks to Sean Whitton (Closes: #944270). + * Bug fix: "please drop transitional package notmuch-emacs from + src:notmuch", thanks to Holger Levsen (Closes: #940738). - -- David Bremner Tue, 25 Aug 2020 07:37:30 -0300 + -- David Bremner Tue, 25 Aug 2020 07:51:33 -0300 notmuch (0.31~rc1-1) experimental; urgency=medium diff --git a/debian/control b/debian/control index 7cbe9508..75359bc4 100644 --- a/debian/control +++ b/debian/control @@ -148,15 +148,6 @@ Description: Ruby interface to the notmuch mail search and index library This package provides a Ruby interface to the notmuch functionality, directly interfacing with a shared notmuch library. -Package: notmuch-emacs -Section: oldlibs -Architecture: all -Depends: - elpa-notmuch, - ${misc:Depends}, -Description: thread-based email index, search and tagging (transitional package) - This dummy package help ease transition to the new package elpa-notmuch - Package: elpa-notmuch Architecture: all Depends: From c8c0304d7759db29fc1197cc4709154b76d66cd3 Mon Sep 17 00:00:00 2001 From: David Bremner Date: Sat, 29 Aug 2020 09:32:36 -0300 Subject: [PATCH 399/427] version: bump to 0.31~rc2 --- bindings/python-cffi/version | 2 +- bindings/python/notmuch/version.py | 2 +- version | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/bindings/python-cffi/version b/bindings/python-cffi/version index 811b2d53..badb1e81 100644 --- a/bindings/python-cffi/version +++ b/bindings/python-cffi/version @@ -1 +1 @@ -0.31~rc1 +0.31~rc2 diff --git a/bindings/python/notmuch/version.py b/bindings/python/notmuch/version.py index 05c7fa7f..36212aa5 100644 --- a/bindings/python/notmuch/version.py +++ b/bindings/python/notmuch/version.py @@ -1,3 +1,3 @@ # this file should be kept in sync with ../../../version -__VERSION__ = '0.31~rc1' +__VERSION__ = '0.31~rc2' SOVERSION = '5' diff --git a/version b/version index 811b2d53..badb1e81 100644 --- a/version +++ b/version @@ -1 +1 @@ -0.31~rc1 +0.31~rc2 From ee26cae09345df9534b2fe36f10ff8aed57f2422 Mon Sep 17 00:00:00 2001 From: David Bremner Date: Sat, 29 Aug 2020 09:23:09 -0300 Subject: [PATCH 400/427] NEWS: mention Emacs 27.1 compatibility fixes --- NEWS | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/NEWS b/NEWS index 16a5b116..f226dffb 100644 --- a/NEWS +++ b/NEWS @@ -4,6 +4,10 @@ Notmuch 0.31 (UNRELEASED) Emacs ----- +Notmuch now supports Emacs 27.1. You may need to set +`mml-secure-openpgp-sign-with-sender` and/or +`mml-secure-smime-sign-with-sender` to continue signing messages. + The minimum supported major version of GNU Emacs is now 25.1. Add support for moving between threads after notmuch-tree-from-search-thread. @@ -47,6 +51,11 @@ Dependencies We now support building notmuch against Xapian 1.5 (the current development version). +Test Suite +---------- + +Test suite fixes for compatibility with Emacs 27.1. + Notmuch 0.30 (2020-07-10) ========================= From 81b390950903474ba9e4287947b247bf56d6c6bc Mon Sep 17 00:00:00 2001 From: David Bremner Date: Sat, 29 Aug 2020 09:23:10 -0300 Subject: [PATCH 401/427] NEWS: mention reproducibility fix --- NEWS | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/NEWS b/NEWS index f226dffb..af903252 100644 --- a/NEWS +++ b/NEWS @@ -56,6 +56,11 @@ Test Suite Test suite fixes for compatibility with Emacs 27.1. +Build System +------------ + +Man pages are now compressed reproducibly. + Notmuch 0.30 (2020-07-10) ========================= From 85b580386905ca1fdb168edc64fca66ffb9620a7 Mon Sep 17 00:00:00 2001 From: David Bremner Date: Thu, 3 Sep 2020 22:10:57 -0300 Subject: [PATCH 402/427] test: fix syntax errors in erroring calls to notmuch insert notmuch insert does not currently support passing a filename for the input, so all of these tests have an extra error in addition to the one being tested for. Currently this does not make a difference because the error being tested for is caught before the error of an extra command line argument. In the future it might make a difference, and in any case it is confusing. --- test/T070-insert.sh | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/T070-insert.sh b/test/T070-insert.sh index c8161e1e..1c7ca846 100755 --- a/test/T070-insert.sh +++ b/test/T070-insert.sh @@ -222,24 +222,24 @@ test_expect_equal "$output" "2" test_begin_subtest "Insert message, create invalid subfolder" gen_insert_msg -test_expect_code 1 "notmuch insert --folder=../G --create-folder $gen_msg_filename" +test_expect_code 1 "notmuch insert --folder=../G --create-folder < $gen_msg_filename" OLDCONFIG=$(notmuch config get new.tags) test_begin_subtest "Empty tags in new.tags are forbidden" notmuch config set new.tags "foo;;bar" gen_insert_msg -output=$(notmuch insert $gen_msg_filename 2>&1) +output=$(notmuch insert < $gen_msg_filename 2>&1) test_expect_equal "$output" "Error: tag '' in new.tags: empty tag forbidden" test_begin_subtest "Tags starting with '-' in new.tags are forbidden" notmuch config set new.tags "-foo;bar" gen_insert_msg -output=$(notmuch insert $gen_msg_filename 2>&1) +output=$(notmuch insert < $gen_msg_filename 2>&1) test_expect_equal "$output" "Error: tag '-foo' in new.tags: tag starting with '-' forbidden" test_begin_subtest "Invalid tags set exit code" -test_expect_code 1 "notmuch insert $gen_msg_filename 2>&1" +test_expect_code 1 "notmuch insert < $gen_msg_filename 2>&1" notmuch config set new.tags $OLDCONFIG From f43ea63953ce5b2ad9e08558d1462b3edc1ba6eb Mon Sep 17 00:00:00 2001 From: Mark Walters Date: Sat, 5 Sep 2020 18:44:45 +0100 Subject: [PATCH 403/427] News: add entry for unthreaded mode A belated NEWS entry for the new un-threaded mode introduced in Notmuch 0.30. --- NEWS | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/NEWS b/NEWS index af903252..a04c0937 100644 --- a/NEWS +++ b/NEWS @@ -12,6 +12,25 @@ The minimum supported major version of GNU Emacs is now 25.1. Add support for moving between threads after notmuch-tree-from-search-thread. +New `notmuch-unthreaded` mode (added in Notmuch 0.30) + + Unthreaded view is a mode where each matching message is shown on a + separate line. + + The main key entries to unthreaded view are + + 'u' enter a query to view in unthreaded mode (works in hello, + search, show and tree mode) + + 'U' view the current query in unthreaded mode (works from search, + show and tree) + + Saved searches can also specify that they should open in unthreaded + view. + + Currently it is not possible to specify the sort order: it will + always be newest first. + Notmuch-Mutt ------------ From 008b8b05742cae2f0193fa24e14ac35faa68fb87 Mon Sep 17 00:00:00 2001 From: David Bremner Date: Sat, 5 Sep 2020 21:26:36 -0300 Subject: [PATCH 404/427] version: bump to 0.31 --- bindings/python-cffi/version | 2 +- bindings/python/notmuch/version.py | 2 +- version | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/bindings/python-cffi/version b/bindings/python-cffi/version index badb1e81..48b9990e 100644 --- a/bindings/python-cffi/version +++ b/bindings/python-cffi/version @@ -1 +1 @@ -0.31~rc2 +0.31 diff --git a/bindings/python/notmuch/version.py b/bindings/python/notmuch/version.py index 36212aa5..e94942f5 100644 --- a/bindings/python/notmuch/version.py +++ b/bindings/python/notmuch/version.py @@ -1,3 +1,3 @@ # this file should be kept in sync with ../../../version -__VERSION__ = '0.31~rc2' +__VERSION__ = '0.31' SOVERSION = '5' diff --git a/version b/version index badb1e81..48b9990e 100644 --- a/version +++ b/version @@ -1 +1 @@ -0.31~rc2 +0.31 From 0b4490c82cfeae7a0692cbf675b87c3a0e8f5114 Mon Sep 17 00:00:00 2001 From: David Bremner Date: Sat, 5 Sep 2020 21:48:27 -0300 Subject: [PATCH 405/427] debian: changelog for 0.31-1 --- debian/changelog | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/debian/changelog b/debian/changelog index b800e361..fd0dddef 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,10 @@ +notmuch (0.31-1) unstable; urgency=medium + + * New upstream release + * Compatibility fixes for Emacs 27.1 + + -- David Bremner Sat, 05 Sep 2020 21:47:42 -0300 + notmuch (0.31~rc2-1) experimental; urgency=medium * New upstream release candidate From 4175d5cb914416dc0e159a00457b1c2a82f3905f Mon Sep 17 00:00:00 2001 From: David Bremner Date: Sat, 5 Sep 2020 21:50:03 -0300 Subject: [PATCH 406/427] NEWS: set release date --- NEWS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NEWS b/NEWS index a04c0937..58fc97c1 100644 --- a/NEWS +++ b/NEWS @@ -1,4 +1,4 @@ -Notmuch 0.31 (UNRELEASED) +Notmuch 0.31 (2020-09-05) ========================= Emacs From 45193bab16c728ba892a5d45fc62ef59e2a6ef85 Mon Sep 17 00:00:00 2001 From: Tim Quelch Date: Sat, 12 Sep 2020 14:45:40 +1000 Subject: [PATCH 407/427] emacs: Remove notmuch-mua-message-send-hook Currently `message-send-hook` functions are being called twice: In notmuch send common when `notmuch-mua-send-hook` functions are run (which by default includes `notmuch-mua-message-send-hook`) and in `message-send` itself. Because `message-send-hook` functions are run in `message-send` itself, we don't need also need to run them before we delegate to `message-send` Calling `notmuch-mua-message-send-hook` resulted in functions in `message-send-hook` to be called twice. This causes bugs in non-idempotent hook functions. --- emacs/notmuch-mua.el | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/emacs/notmuch-mua.el b/emacs/notmuch-mua.el index dcddca76..03c7cc97 100644 --- a/emacs/notmuch-mua.el +++ b/emacs/notmuch-mua.el @@ -40,7 +40,7 @@ ;; -(defcustom notmuch-mua-send-hook '(notmuch-mua-message-send-hook) +(defcustom notmuch-mua-send-hook nil "Hook run before sending messages." :type 'hook :group 'notmuch-send @@ -601,11 +601,6 @@ unencrypted. Really send? ")))) (interactive) (message-kill-buffer)) -(defun notmuch-mua-message-send-hook () - "The default function used for `notmuch-mua-send-hook', this -simply runs the corresponding `message-mode' hook functions." - (run-hooks 'message-send-hook)) - ;; (define-mail-user-agent 'notmuch-user-agent From bdb6956afdd2fca610c3bac0834636835793a831 Mon Sep 17 00:00:00 2001 From: Tomi Ollila Date: Fri, 9 Oct 2020 10:32:02 +0300 Subject: [PATCH 408/427] emacs docs: rstdoc.el: consistent single quote conversions With text-quoting-style 'grave keeps "'" and "`" quotes unaltered for further processing done by this code (regardless of locale...). The tools that read the reStructuredText markup generated can do their styling instead. Added temporary conversions of ' and ` to \001 and \002 so that 's and `s outside of `...' and `...` are converted separately ('s restored back to ' and `s converted to \`). Both `...' and `...` are finally "converted" to `...` (not ``...``). https://docutils.sourceforge.io/docs/user/rst/quickref.html documents that as `interpreted text`: "The rendering and meaning of interpreted text is domain- or application-dependent. It can be used for things like index entries or explicit descriptive markup (like program identifiers)." Which looks pretty much right. --- emacs/rstdoc.el | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/emacs/rstdoc.el b/emacs/rstdoc.el index 63fa2794..4221f142 100644 --- a/emacs/rstdoc.el +++ b/emacs/rstdoc.el @@ -40,6 +40,7 @@ "Write docstrings from IN-FILE to OUT-FILE." (load-file in-file) (let* ((definitions (cdr (assoc (expand-file-name in-file) load-history))) + (text-quoting-style 'grave) (doc-hash (make-hash-table :test 'eq))) (mapc (lambda (elt) @@ -65,11 +66,14 @@ (insert "\n")) (defvar rst--escape-alist - '(("\\\\='" . "\\\\'") - ("\\([^\\]\\)'" . "\\1`") - ("^[[:space:]\t]*$" . "|br|") - ("^[[:space:]\t]" . "|indent| ")) - "List of (regex . replacement) pairs.") + '( ("\\\\='" . "\001") + ("`\\([^\n`']*\\)[`']" . "\002\\1\002") ;; good enough for now... + ("`" . "\\\\`") + ("\001" . "'") + ("\002" . "`") + ("^[[:space:]]*$" . "|br|") + ("^[[:space:]]" . "|indent| ")) + "list of (regex . replacement) pairs") (defun rstdoc--rst-quote-string (str) (with-temp-buffer From b042a59cdf8be7ad215268eee32d4cc114d312bb Mon Sep 17 00:00:00 2001 From: Tomi Ollila Date: Tue, 20 Oct 2020 09:57:11 +0300 Subject: [PATCH 409/427] configure: replace $(realpath emacs) with $(cd emacs && pwd -P) For portability; the realpath command (e.g. from GNU coreutils) is not so common outside Linux systems. The "$(cd emacs && pwd -P)" replaces that realpath(1) execution suitably in this context (using just bash(1) builtins). --- configure | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure b/configure index 40e8b255..cfa9c09b 100755 --- a/configure +++ b/configure @@ -1536,7 +1536,7 @@ EOF if [ $WITH_PYTHON_DOCS = "1" ]; then echo "tags.add('WITH_PYTHON')" fi - printf "rsti_dir = '%s'\n" $(realpath emacs) + printf "rsti_dir = '%s'\n" "$(cd emacs && pwd -P)" } > sphinx.config # Finally, after everything configured, inform the user how to continue. From 582e919e2769bd99064b997b05e1601ffcfe9a98 Mon Sep 17 00:00:00 2001 From: David Bremner Date: Thu, 29 Oct 2020 21:13:01 -0300 Subject: [PATCH 410/427] lib/config: don't set destructor until iterator is initialized. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit As diagnosed by Olivier Taïbi in id:20201027100916.emry3k2wujod4xnl@galois.lan, if an exception is thrown while the initialization is happening (e.g. if the function is called on a closed database), then the destructor is (sometimes) invoked on an uninitialized Xapian object. Solve the problem by moving the setting of the destructor until after the placement new successfully completes. It is conceivable this might cause a memory leak, but that seems preferable to crashing, and in any case, there seems to be nothing better to be done if the initialization is failing things are in an undefined state by definition. --- lib/config.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/config.cc b/lib/config.cc index dae0ff0e..efab01e4 100644 --- a/lib/config.cc +++ b/lib/config.cc @@ -113,7 +113,6 @@ notmuch_database_get_config_list (notmuch_database_t *notmuch, goto DONE; } - talloc_set_destructor (list, _notmuch_config_list_destroy); list->notmuch = notmuch; list->current_key = NULL; list->current_val = NULL; @@ -122,6 +121,7 @@ notmuch_database_get_config_list (notmuch_database_t *notmuch, new(&(list->iterator)) Xapian::TermIterator (notmuch->xapian_db->metadata_keys_begin (CONFIG_PREFIX + (prefix ? prefix : ""))); + talloc_set_destructor (list, _notmuch_config_list_destroy); } catch (const Xapian::Error &error) { _notmuch_database_log (notmuch, "A Xapian exception occurred getting metadata iterator: %s.\n", From 981d5a01689937ba45f3cd9dbdcdc65e6d264286 Mon Sep 17 00:00:00 2001 From: Ralph Seichter Date: Wed, 28 Oct 2020 15:44:35 +0100 Subject: [PATCH 411/427] Rename version to version.txt Building Notmuch on macOS is known to cause problems because the Notmuch distribution archive contains two files named "version". These names clash with the header as defined in C++20. Therefore, the existing naming will likely become a problem on other platforms as well, once compilers adopt the new standard. Signed-off-by: Ralph Seichter Amended-by: db s/keyword/header/ in commit message. --- Makefile.global | 2 +- Makefile.local | 2 +- bindings/python-cffi/setup.py | 2 +- bindings/python-cffi/{version => version.txt} | 0 doc/conf.py | 2 +- version => version.txt | 0 6 files changed, 4 insertions(+), 4 deletions(-) rename bindings/python-cffi/{version => version.txt} (100%) rename version => version.txt (100%) diff --git a/Makefile.global b/Makefile.global index cd489ef2..8477468d 100644 --- a/Makefile.global +++ b/Makefile.global @@ -17,7 +17,7 @@ else DATE:=$(shell date +%F) endif -VERSION:=$(shell cat ${srcdir}/version) +VERSION:=$(shell cat ${srcdir}/version.txt) ELPA_VERSION:=$(subst ~,_,$(VERSION)) ifeq ($(filter release release-message pre-release update-versions,$(MAKECMDGOALS)),) ifeq ($(IS_GIT),yes) diff --git a/Makefile.local b/Makefile.local index c65cee7c..dde7981b 100644 --- a/Makefile.local +++ b/Makefile.local @@ -19,7 +19,7 @@ endif # Depend (also) on the file 'version'. In case of ifeq ($(IS_GIT),yes) # this file may already have been updated. -version.stamp: $(srcdir)/version +version.stamp: $(srcdir)/version.txt echo $(VERSION) > $@ $(TAR_FILE): diff --git a/bindings/python-cffi/setup.py b/bindings/python-cffi/setup.py index b0060835..cda52338 100644 --- a/bindings/python-cffi/setup.py +++ b/bindings/python-cffi/setup.py @@ -1,6 +1,6 @@ import setuptools -with open('version') as fp: +with open('version.txt') as fp: VERSION = fp.read().strip() setuptools.setup( diff --git a/bindings/python-cffi/version b/bindings/python-cffi/version.txt similarity index 100% rename from bindings/python-cffi/version rename to bindings/python-cffi/version.txt diff --git a/doc/conf.py b/doc/conf.py index 94e266af..11bed51d 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -19,7 +19,7 @@ copyright = u'2009-2020, Carl Worth and many others' location = os.path.dirname(__file__) for pathdir in ['.', '..']: - version_file = os.path.join(location,pathdir,'version') + version_file = os.path.join(location,pathdir,'version.txt') if os.path.exists(version_file): with open(version_file,'r') as infile: version=infile.read().replace('\n','') diff --git a/version b/version.txt similarity index 100% rename from version rename to version.txt From 59edcfd702072a172fbc975faa38ac65218c1d3e Mon Sep 17 00:00:00 2001 From: David Bremner Date: Sun, 8 Nov 2020 07:27:48 -0400 Subject: [PATCH 412/427] build: fix update-versions rule This was missed in the rename of the 'version' file to version.txt --- Makefile.local | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile.local b/Makefile.local index dde7981b..631fef3c 100644 --- a/Makefile.local +++ b/Makefile.local @@ -54,7 +54,7 @@ update-versions: sed -i -e "s/^__VERSION__[[:blank:]]*=.*$$/__VERSION__ = \'${VERSION}\'/" \ -e "s/^SOVERSION[[:blank:]]*=.*$$/SOVERSION = \'${LIBNOTMUCH_VERSION_MAJOR}\'/" \ ${PV_FILE} - cp version bindings/python-cffi + cp version.txt bindings/python-cffi # We invoke make recursively only to force ordering of our phony # targets in the case of parallel invocation of make (-j). From b8a3ed175c26912eee6d3c803dad5102b3641abb Mon Sep 17 00:00:00 2001 From: David Bremner Date: Sun, 8 Nov 2020 07:32:10 -0400 Subject: [PATCH 413/427] update versions --- bindings/python-cffi/version.txt | 2 +- bindings/python/notmuch/version.py | 2 +- version.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/bindings/python-cffi/version.txt b/bindings/python-cffi/version.txt index 48b9990e..f176c944 100644 --- a/bindings/python-cffi/version.txt +++ b/bindings/python-cffi/version.txt @@ -1 +1 @@ -0.31 +0.31.1 diff --git a/bindings/python/notmuch/version.py b/bindings/python/notmuch/version.py index e94942f5..f305d5c4 100644 --- a/bindings/python/notmuch/version.py +++ b/bindings/python/notmuch/version.py @@ -1,3 +1,3 @@ # this file should be kept in sync with ../../../version -__VERSION__ = '0.31' +__VERSION__ = '0.31.1' SOVERSION = '5' diff --git a/version.txt b/version.txt index 48b9990e..f176c944 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -0.31 +0.31.1 From 87ae900f57dfd03b698afc03332e47085313b537 Mon Sep 17 00:00:00 2001 From: David Bremner Date: Sun, 8 Nov 2020 07:39:14 -0400 Subject: [PATCH 414/427] NEWS: add news for 0.31.1 --- NEWS | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/NEWS b/NEWS index 58fc97c1..75e5dba3 100644 --- a/NEWS +++ b/NEWS @@ -1,3 +1,19 @@ +Notmuch 0.31.1 (2020-11-08) +=========================== + +Library +------- + +Fix a memory initialization bug in notmuch_database_get_config_list. + +Build +----- + +Rename file 'version' to 'version.txt'. The old file name conflicted +with a C++ header for some compilers. + +Replace use of coreutils `realpath` in configure. + Notmuch 0.31 (2020-09-05) ========================= From 0e6d2876b8675d5f3271d616a1e73c23e312a56f Mon Sep 17 00:00:00 2001 From: David Bremner Date: Sun, 8 Nov 2020 07:46:51 -0400 Subject: [PATCH 415/427] debian: changelog for 0.31.1-1 --- debian/changelog | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/debian/changelog b/debian/changelog index fd0dddef..a90bf03f 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,11 @@ +notmuch (0.31.1-1) unstable; urgency=medium + + * New upstream bugfix release. + - Portability / C++20 fixes + - Fix initialization bug in library config handling. + + -- David Bremner Sun, 08 Nov 2020 07:48:22 -0400 + notmuch (0.31-1) unstable; urgency=medium * New upstream release From 8e721f12ba2d03cabc299a75827b5282720204ba Mon Sep 17 00:00:00 2001 From: David Bremner Date: Sun, 8 Nov 2020 10:00:57 -0400 Subject: [PATCH 416/427] release: update release-checks.sh for s/version/version.txt/ Another place missed by the rename. --- devel/release-checks.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/devel/release-checks.sh b/devel/release-checks.sh index cfa208d5..f9dd6ade 100755 --- a/devel/release-checks.sh +++ b/devel/release-checks.sh @@ -29,7 +29,7 @@ append_emsg () emsgs="${emsgs:+$emsgs\n} $1" } -for f in ./version debian/changelog NEWS "$PV_FILE" +for f in ./version.txt debian/changelog NEWS "$PV_FILE" do if [ ! -f "$f" ]; then append_emsg "File '$f' is missing" elif [ ! -r "$f" ]; then append_emsg "File '$f' is unreadable" @@ -53,7 +53,7 @@ then else echo "Reading './version' file failed (surprisingly!)" exit 1 -fi < ./version +fi < ./version.txt readonly VERSION From 40b75f50be35dd372547bf03b972059e88b6f19b Mon Sep 17 00:00:00 2001 From: David Bremner Date: Sun, 8 Nov 2020 10:01:53 -0400 Subject: [PATCH 417/427] release: call python3 instead of python Debian does not install /usr/bin/python by default any more. --- devel/release-checks.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/devel/release-checks.sh b/devel/release-checks.sh index f9dd6ade..23c29eaa 100755 --- a/devel/release-checks.sh +++ b/devel/release-checks.sh @@ -109,7 +109,7 @@ else fi echo -n "Checking that python bindings version is $VERSION... " -py_version=`python -c "with open('$PV_FILE') as vf: exec(vf.read()); print(__VERSION__)"` +py_version=`python3 -c "with open('$PV_FILE') as vf: exec(vf.read()); print(__VERSION__)"` if [ "$py_version" = "$VERSION" ] then echo Yes. From c5504cb04398eeeb595cfebe84714ba982ef00e6 Mon Sep 17 00:00:00 2001 From: David Bremner Date: Sun, 8 Nov 2020 10:08:00 -0400 Subject: [PATCH 418/427] Commit Debian 3.0 (quilt) metadata [dgit (9.12) quilt-fixup] --- debian/patches/debian-changes | 34 ++++++++++++++++++++++++++++++++++ debian/patches/series | 1 + 2 files changed, 35 insertions(+) create mode 100644 debian/patches/debian-changes create mode 100644 debian/patches/series diff --git a/debian/patches/debian-changes b/debian/patches/debian-changes new file mode 100644 index 00000000..de0f871b --- /dev/null +++ b/debian/patches/debian-changes @@ -0,0 +1,34 @@ +This is an autogenerated patch header for a single-debian-patch file. The +delta against upstream is either kept as a single patch, or maintained +in some VCS, and exported as a single patch instead of more manageable +atomic patches. + +--- notmuch-0.31.1.orig/devel/release-checks.sh ++++ notmuch-0.31.1/devel/release-checks.sh +@@ -29,7 +29,7 @@ append_emsg () + emsgs="${emsgs:+$emsgs\n} $1" + } + +-for f in ./version debian/changelog NEWS "$PV_FILE" ++for f in ./version.txt debian/changelog NEWS "$PV_FILE" + do + if [ ! -f "$f" ]; then append_emsg "File '$f' is missing" + elif [ ! -r "$f" ]; then append_emsg "File '$f' is unreadable" +@@ -53,7 +53,7 @@ then + else + echo "Reading './version' file failed (surprisingly!)" + exit 1 +-fi < ./version ++fi < ./version.txt + + readonly VERSION + +@@ -109,7 +109,7 @@ else + fi + + echo -n "Checking that python bindings version is $VERSION... " +-py_version=`python -c "with open('$PV_FILE') as vf: exec(vf.read()); print(__VERSION__)"` ++py_version=`python3 -c "with open('$PV_FILE') as vf: exec(vf.read()); print(__VERSION__)"` + if [ "$py_version" = "$VERSION" ] + then + echo Yes. diff --git a/debian/patches/series b/debian/patches/series new file mode 100644 index 00000000..7bb82529 --- /dev/null +++ b/debian/patches/series @@ -0,0 +1 @@ +debian-changes From d824f83c6f2e528088ab3ee6e81901db7143a31c Mon Sep 17 00:00:00 2001 From: David Bremner Date: Sun, 8 Nov 2020 12:58:03 -0400 Subject: [PATCH 419/427] build: change one more occurrence of 'version' to 'version.txt'. This one disguised via tar and sed trickery. --- Makefile.local | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Makefile.local b/Makefile.local index 631fef3c..205fc9d3 100644 --- a/Makefile.local +++ b/Makefile.local @@ -30,12 +30,12 @@ $(TAR_FILE): echo "Warning: No signed tag for $(VERSION)"; \ fi ; \ git archive --format=tar --prefix=$(PACKAGE)-$(VERSION)/ $$ref > $(TAR_FILE).tmp - echo $(VERSION) > version.tmp + echo $(VERSION) > version.txt.tmp ct=`git --no-pager log -1 --pretty=format:%ct $$ref` ; \ tar --owner root --group root --append -f $(TAR_FILE).tmp \ --transform s_^_$(PACKAGE)-$(VERSION)/_ \ - --transform 's_.tmp$$__' --mtime=@$$ct version.tmp - rm version.tmp + --transform 's_.tmp$$__' --mtime=@$$ct version.txt.tmp + rm version.txt.tmp xz -C sha256 -9 < $(TAR_FILE).tmp > $(TAR_FILE) @echo "Source is ready for release in $(TAR_FILE)" From 6003af14c417a6a80bc2f8bb01a8e46ae52b0ed4 Mon Sep 17 00:00:00 2001 From: David Bremner Date: Sun, 8 Nov 2020 12:36:35 -0400 Subject: [PATCH 420/427] version: bump to 0.31.2 --- bindings/python-cffi/version.txt | 2 +- bindings/python/notmuch/version.py | 2 +- version.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/bindings/python-cffi/version.txt b/bindings/python-cffi/version.txt index f176c944..c415e1c6 100644 --- a/bindings/python-cffi/version.txt +++ b/bindings/python-cffi/version.txt @@ -1 +1 @@ -0.31.1 +0.31.2 diff --git a/bindings/python/notmuch/version.py b/bindings/python/notmuch/version.py index f305d5c4..0d8fdd04 100644 --- a/bindings/python/notmuch/version.py +++ b/bindings/python/notmuch/version.py @@ -1,3 +1,3 @@ # this file should be kept in sync with ../../../version -__VERSION__ = '0.31.1' +__VERSION__ = '0.31.2' SOVERSION = '5' diff --git a/version.txt b/version.txt index f176c944..c415e1c6 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -0.31.1 +0.31.2 From c87dd8ab2e1d6ae6338fe4db39c12debe1e1db08 Mon Sep 17 00:00:00 2001 From: David Bremner Date: Sun, 8 Nov 2020 11:33:27 -0400 Subject: [PATCH 421/427] debian: update changelog for 0.31.2-1 --- debian/changelog | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/debian/changelog b/debian/changelog index a90bf03f..dd47abf2 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,9 @@ +notmuch (0.31.2-1) unstable; urgency=medium + + * Delete stray "version" file in upstream source + + -- David Bremner Sun, 08 Nov 2020 11:32:45 -0400 + notmuch (0.31.1-1) unstable; urgency=medium * New upstream bugfix release. From 852df1a7ef373a0fcd478c30fb4b596bbffbdef3 Mon Sep 17 00:00:00 2001 From: David Bremner Date: Sun, 8 Nov 2020 11:56:20 -0400 Subject: [PATCH 422/427] debian: remove quilt patches These were generated by dgit due a mix up with .orig.tar.xz files. --- debian/patches/debian-changes | 34 ---------------------------------- debian/patches/series | 1 - 2 files changed, 35 deletions(-) delete mode 100644 debian/patches/debian-changes delete mode 100644 debian/patches/series diff --git a/debian/patches/debian-changes b/debian/patches/debian-changes deleted file mode 100644 index de0f871b..00000000 --- a/debian/patches/debian-changes +++ /dev/null @@ -1,34 +0,0 @@ -This is an autogenerated patch header for a single-debian-patch file. The -delta against upstream is either kept as a single patch, or maintained -in some VCS, and exported as a single patch instead of more manageable -atomic patches. - ---- notmuch-0.31.1.orig/devel/release-checks.sh -+++ notmuch-0.31.1/devel/release-checks.sh -@@ -29,7 +29,7 @@ append_emsg () - emsgs="${emsgs:+$emsgs\n} $1" - } - --for f in ./version debian/changelog NEWS "$PV_FILE" -+for f in ./version.txt debian/changelog NEWS "$PV_FILE" - do - if [ ! -f "$f" ]; then append_emsg "File '$f' is missing" - elif [ ! -r "$f" ]; then append_emsg "File '$f' is unreadable" -@@ -53,7 +53,7 @@ then - else - echo "Reading './version' file failed (surprisingly!)" - exit 1 --fi < ./version -+fi < ./version.txt - - readonly VERSION - -@@ -109,7 +109,7 @@ else - fi - - echo -n "Checking that python bindings version is $VERSION... " --py_version=`python -c "with open('$PV_FILE') as vf: exec(vf.read()); print(__VERSION__)"` -+py_version=`python3 -c "with open('$PV_FILE') as vf: exec(vf.read()); print(__VERSION__)"` - if [ "$py_version" = "$VERSION" ] - then - echo Yes. diff --git a/debian/patches/series b/debian/patches/series deleted file mode 100644 index 7bb82529..00000000 --- a/debian/patches/series +++ /dev/null @@ -1 +0,0 @@ -debian-changes From bbe62623420a7f6c6e08730769ec63120da03dd0 Mon Sep 17 00:00:00 2001 From: David Bremner Date: Sun, 8 Nov 2020 12:47:30 -0400 Subject: [PATCH 423/427] NEWS: update for 0.31.2 --- NEWS | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/NEWS b/NEWS index 75e5dba3..677c507d 100644 --- a/NEWS +++ b/NEWS @@ -1,3 +1,12 @@ +Notmuch 0.31.2 (2020-11-08) +=========================== + +Build +----- + +Catch one more occurence of "version" in the build system, which +caused the file to be regenerated in the release tarball. + Notmuch 0.31.1 (2020-11-08) =========================== From 02a7b026d938f53d9fd9b183ca2b3c44c92489f2 Mon Sep 17 00:00:00 2001 From: David Bremner Date: Sun, 8 Nov 2020 13:33:08 -0400 Subject: [PATCH 424/427] release: explicitely build sphinx-html This is to force it to happen after the "make clean". --- Makefile.local | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Makefile.local b/Makefile.local index 205fc9d3..fb126294 100644 --- a/Makefile.local +++ b/Makefile.local @@ -64,9 +64,10 @@ update-versions: # are part of the release and need to take the version from the # version file). .PHONY: release -release: verify-source-tree-and-version sphinx-html +release: verify-source-tree-and-version $(MAKE) VERSION=$(VERSION) verify-newer $(MAKE) VERSION=$(VERSION) clean + $(MAKE) VERSION=$(VERSION) sphinx-html $(MAKE) VERSION=$(VERSION) test git tag -s -m "$(PACKAGE) $(VERSION) release" $(UPSTREAM_TAG) $(MAKE) VERSION=$(VERSION) $(SHA256_FILE) $(DETACHED_SIG_FILE) From 19590605eaa60f9f4b59f4de10d533e4eb39293a Mon Sep 17 00:00:00 2001 From: David Bremner Date: Mon, 9 Nov 2020 08:44:41 -0400 Subject: [PATCH 425/427] debian: run tests in verbose mode A hopefully temporary change to try and debug some test failures on autobuilders. --- debian/rules | 3 +++ 1 file changed, 3 insertions(+) diff --git a/debian/rules b/debian/rules index 8de49d0f..fa0551a9 100755 --- a/debian/rules +++ b/debian/rules @@ -15,6 +15,9 @@ override_dh_auto_configure: --zshcompletiondir=/usr/share/zsh/vendor-completions \ --localstatedir=/var +override_dh_auto_test: + dh_auto_test -- V=1 + override_dh_auto_build: dh_auto_build -- V=1 PYBUILD_NAME=notmuch dh_auto_build --buildsystem=pybuild --sourcedirectory bindings/python From 5323fbb4ec669b667c00bba8694255dfe66310da Mon Sep 17 00:00:00 2001 From: David Bremner Date: Mon, 9 Nov 2020 08:46:07 -0400 Subject: [PATCH 426/427] debian: changelog for 0.31.2-2 --- debian/changelog | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/debian/changelog b/debian/changelog index dd47abf2..71a8ef86 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,9 @@ +notmuch (0.31.2-2) unstable; urgency=medium + + * Run tests in verbose mode + + -- David Bremner Mon, 09 Nov 2020 08:45:38 -0400 + notmuch (0.31.2-1) unstable; urgency=medium * Delete stray "version" file in upstream source From 900ee94b0f4f48ee536bd2e9bd6bb2dfc661d615 Mon Sep 17 00:00:00 2001 From: David Bremner Date: Mon, 9 Nov 2020 13:59:58 -0400 Subject: [PATCH 427/427] debian: upload 0.31.2-3 --- debian/changelog | 6 ++++++ debian/control | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/debian/changelog b/debian/changelog index 71a8ef86..ed76f7f8 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,9 @@ +notmuch (0.31.2-3) unstable; urgency=medium + + * Switch to debhelper compat level 13 + + -- David Bremner Mon, 09 Nov 2020 13:59:47 -0400 + notmuch (0.31.2-2) unstable; urgency=medium * Run tests in verbose mode diff --git a/debian/control b/debian/control index 75359bc4..585ff778 100644 --- a/debian/control +++ b/debian/control @@ -11,7 +11,7 @@ Build-Conflicts: ruby1.8, Build-Depends: bash-completion (>=1.9.0~), - debhelper-compat (= 12), + debhelper-compat (= 13), dh-elpa (>= 1.3), dh-python, desktop-file-utils,