notmuch release 0.36-1 for unstable (sid) [dgit]

[dgit distro=debian no-split --quilt=linear]
 -----BEGIN PGP SIGNATURE-----
 
 iQIzBAABCAAdFiEEkiyHYXwaY0SiY6fqA0U5G1WqFSEFAmJmjoQACgkQA0U5G1Wq
 FSEiTw//VGUFqchEOeSt0CeQTuk48CLf8ibol6NfQ9w/HUIH2O0nmG2Xj9VjtmbD
 Y97SytUE2lTKTaB7D7/k8NrGHBFCB0njIK4uxklzWjERS4UeG76Iavxe0gtiOTL+
 /SjbOwn/Vw3NDZPwM6RMwV7oGl+IoyATCCEaUTKnD8OQiZsnztI0LuJtdEgHYrTl
 JMqbY+j1Vt8zFe2aUPNcEgTH4W9xT4k9ByWu0AOCXk5bt7WrPMB/rqm4pWr56r5T
 7vbUMClW9bgB+Dudp9c7ZHt90gg6QM+vPp+JQABg/pFOTwPX71JW4bAncn8ZNkl7
 6PAV1G47iT35UfPnax3cyBSTJTgE9O9KCLPelBg1BHauvqf8c6m0DnfoMsXb/bqc
 AuHZ8PlpgOR2iiW1FOWsYk+8abCz3FU3iDnVJfoRa4wkbOKM5fbDcn3ei7yeyMH2
 V5VSaki8lWQXcgcIjHCFw+DlpdG6GUCOl7LdowfKAMXr15Zml2LyHcxOdxccrEAO
 CCGG8Gr+Ms6Lzp2SSc+9eKxwpx6GAUGhSagUE9VYP9+E9zNX6rh9UGa9ap+MJBmv
 50vCBFbp3eQRqfOb8ip7TTe9+YFJkFl/CtPWbNHv6j+nUDTa0givfhWxJ7Q/IGv7
 77d3DKEZ+B36oIQGnjPZ924V7B+zoVmlU0BQspyVNS918IMHxMU=
 =+lFq
 -----END PGP SIGNATURE-----

Merge tag 'debian/0.36-1' into debian/bullseye-backports

notmuch release 0.36-1 for unstable (sid) [dgit]

[dgit distro=debian no-split --quilt=linear]
This commit is contained in:
David Bremner 2022-04-30 14:26:55 -03:00
commit 97b6a43d46
61 changed files with 1594 additions and 444 deletions

View file

@ -73,7 +73,7 @@ release: verify-source-tree-and-version
ln -sf $(TAR_FILE) $(DEB_TAR_FILE)
pristine-tar commit $(DEB_TAR_FILE) $(UPSTREAM_TAG)
mkdir -p releases
mv $(TAR_FILE) $(SHA256_FILE) $(DETACHED_SIG_FILE) releases
mv $(TAR_FILE) $(DEB_TAR_FILE) $(SHA256_FILE) $(DETACHED_SIG_FILE) releases
$(MAKE) VERSION=$(VERSION) release-message > $(PACKAGE)-$(VERSION).announce
ifeq ($(REALLY_UPLOAD),yes)
git push origin $(VERSION) release pristine-tar
@ -92,7 +92,11 @@ pre-release:
ln -sf $(TAR_FILE) $(DEB_TAR_FILE)
pristine-tar commit $(DEB_TAR_FILE) $(UPSTREAM_TAG)
mkdir -p releases
mv $(TAR_FILE) $(DEB_TAR_FILE) releases
mv $(TAR_FILE) $(DEB_TAR_FILE) $(SHA256_FILE) $(DETACHED_SIG_FILE) releases
ifeq ($(REALLY_UPLOAD),yes)
git push origin $(UPSTREAM_TAG) release pristine-tar
cd releases && scp $(TAR_FILE) $(SHA256_FILE) $(DETACHED_SIG_FILE) $(RELEASE_HOST):$(RELEASE_DIR)
endif
.PHONY: debian-snapshot
debian-snapshot:
@ -120,8 +124,7 @@ release-message:
@echo "Which can be verified with:"
@echo ""
@echo " $(RELEASE_URL)/$(SHA256_FILE)"
@echo -n " "
@cat releases/$(SHA256_FILE)
@sed "s/^/ /" releases/$(SHA256_FILE)
@echo ""
@echo " $(RELEASE_URL)/$(DETACHED_SIG_FILE)"
@echo " (signed by `getent passwd "$$USER" | cut -d: -f 5 | cut -d, -f 1`)"
@ -169,7 +172,7 @@ release-checks:
.PHONY: verify-newer
verify-newer:
@echo -n "Checking that no $(VERSION) release already exists..."
@printf %s "Checking that no $(VERSION) release already exists..."
@wget -q --no-check-certificate -O /dev/null $(RELEASE_URL)/$(TAR_FILE) ; \
case $$? in \
8) echo "Good." ;; \

57
NEWS
View file

@ -1,3 +1,60 @@
Notmuch 0.36 (2022-04-25)
=========================
Library
-------
Add the `sexp` prefix to the infix (traditional) query parser. This
allows specific subqueries to be parsed by the sexp parser (with
appropropriate quoting). See `notmuch-search-terms(7)` for details.
Add another heuristic to regexp fields to prevent phrase parsing of
bracketed sub-expressions.
Command Line Interface
----------------------
Envelope from ("From ") headers are now escaped as X-Envelope-From: in
input to `notmuch-insert`. This prevents creating mbox files when
calling `notmuch-insert` from e.g. `postfix`.
Python (CFFI) Bindings
----------------------
Use the `config_pairs` API in ConfigIterator. This returns all
matching key-value pairs, not just those that happen to be stored in
the database.
Documentation
-------------
Reorganize documention for `notmuch-config`. Add a few links from
other man pages.
Emacs
-----
Bind the usual undo key sequences to new command
"notmuch-tag-undo". This allows transparent undo of tagging
operations.
Tests
-----
Fix smime.4 with newer gmime. Unset `XDG_DATA_HOME` and `MAILDIR` for tests.
New add-on tool: notmuch-web
-----------------------------
The new devel/ tool `notmuch-web` is a very thin web client. It
supports a full search interface for one user: there is no facility
for multiple users provided today. See the notmuch-web README file
for more information.
Be careful about running it on a network-connected system: it will
expose a web interface that requires no authentication but exposes
your mail store.
Notmuch 0.35 (2022-02-06)
=========================

View file

@ -97,7 +97,7 @@ ffibuilder.cdef(
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;
typedef struct _notmuch_config_pairs notmuch_config_pairs_t;
typedef struct _notmuch_indexopts notmuch_indexopts_t;
const char *
@ -325,18 +325,18 @@ ffibuilder.cdef(
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_config_pairs_t *
notmuch_config_get_pairs (notmuch_database_t *db, const char *prefix);
notmuch_bool_t
notmuch_config_list_valid (notmuch_config_list_t *config_list);
notmuch_config_pairs_valid (notmuch_config_pairs_t *config_list);
const char *
notmuch_config_list_key (notmuch_config_list_t *config_list);
notmuch_config_pairs_key (notmuch_config_pairs_t *config_list);
const char *
notmuch_config_list_value (notmuch_config_list_t *config_list);
notmuch_config_pairs_value (notmuch_config_pairs_t *config_list);
void
notmuch_config_list_move_to_next (notmuch_config_list_t *config_list);
notmuch_config_pairs_move_to_next (notmuch_config_pairs_t *config_list);
void
notmuch_config_list_destroy (notmuch_config_list_t *config_list);
notmuch_config_pairs_destroy (notmuch_config_pairs_t *config_list);
"""
)

View file

@ -13,27 +13,42 @@ 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)
fn_destroy=capi.lib.notmuch_config_pairs_destroy,
fn_valid=capi.lib.notmuch_config_pairs_valid,
fn_get=capi.lib.notmuch_config_pairs_key,
fn_next=capi.lib.notmuch_config_pairs_move_to_next)
def __next__(self):
item = super().__next__()
return base.BinString.from_cffi(item)
# skip pairs whose value is NULL
while capi.lib.notmuch_config_pairs_valid(super()._iter_p):
val_p = capi.lib.notmuch_config_pairs_value(super()._iter_p)
key_p = capi.lib.notmuch_config_pairs_key(super()._iter_p)
if key_p == capi.ffi.NULL:
# this should never happen
raise errors.NullPointerError
key = base.BinString.from_cffi(key_p)
capi.lib.notmuch_config_pairs_move_to_next(super()._iter_p)
if val_p != capi.ffi.NULL and base.BinString.from_cffi(val_p) != "":
return key
self._destroy()
raise StopIteration
class ConfigMapping(base.NotmuchObject, collections.abc.MutableMapping):
"""The config key/value pairs stored in the database.
"""The config key/value pairs loaded from the database, config file,
and and/or defaults.
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.
Mutating (deleting or updating values) in the map persists only in
the database, which can be shadowed by config files.
: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):
@ -77,11 +92,10 @@ class ConfigMapping(base.NotmuchObject, collections.abc.MutableMapping):
: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])
config_pairs_p = capi.lib.notmuch_config_get_pairs(self._ptr(), b'')
if config_pairs_p == capi.ffi.NULL:
raise KeyError
return ConfigIter(self._parent, config_pairs_p)
def __len__(self):
return sum(1 for t in self)

View file

@ -34,20 +34,24 @@ class TestIter:
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 has_prefix(x):
return x.startswith('TEST.')
assert [ x for x in db.config if has_prefix(x) ] == []
db.config['TEST.spam'] = 'TEST.ham'
db.config['TEST.eggs'] = 'TEST.bacon'
assert { x for x in db.config if has_prefix(x) } == {'TEST.spam', 'TEST.eggs'}
assert { x for x in db.config.keys() if has_prefix(x) } == {'TEST.spam', 'TEST.eggs'}
assert { x for x in db.config.values() if has_prefix(x) } == {'TEST.ham', 'TEST.bacon'}
assert { (x, y) for (x,y) in db.config.items() if has_prefix(x) } == \
{('TEST.spam', 'TEST.ham'), ('TEST.eggs', 'TEST.bacon')}
def test_len(self, db):
assert len(db.config) == 0
defaults = len(db.config)
db.config['spam'] = 'ham'
assert len(db.config) == 1
assert len(db.config) == defaults + 1
db.config['eggs'] = 'bacon'
assert len(db.config) == 2
assert len(db.config) == defaults + 2
def test_del(self, db):
db.config['spam'] = 'ham'

View file

@ -1,3 +1,3 @@
# this file should be kept in sync with ../../../version
__VERSION__ = '0.35'
__VERSION__ = '0.36'
SOVERSION = '5'

35
configure vendored
View file

@ -552,11 +552,7 @@ EOF
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 <<EOF
cat > _check_gmime_cert.c <<EOF
#include <stdio.h>
#include <gmime/gmime.h>
@ -589,16 +585,27 @@ int main () {
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");
#ifdef CHECK_VALIDITY
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);
#endif
#ifdef CHECK_EMAIL
const char *email = g_mime_certificate_get_email (cert);
if (! email) return !! fprintf (stderr, "no email returned");
if (email[0] == '<') return 2;
#endif
return 0;
}
EOF
# 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... "
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 \
elif ${CC} -DCHECK_VALIDITY ${CFLAGS} ${gmime_cflags} _check_gmime_cert.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
@ -620,6 +627,15 @@ EOF
errors=$((errors + 1))
fi
fi
printf "Checking whether GMime emits email addresses with angle brackets... "
if ${CC} -DCHECK_EMAIL ${CFLAGS} ${gmime_cflags} _check_gmime_cert.c ${gmime_ldflags} -o _check_email &&
GNUPGHOME=${TEMP_GPG} ./_check_email; then
gmime_emits_angle_brackets=0
printf "No.\n"
else
gmime_emits_angle_brackets=1
printf "Yes.\n"
fi
else
printf 'No.\nFailed to set up gpgsm for testing X.509 certificate validity support.\n'
errors=$((errors + 1))
@ -1256,7 +1272,7 @@ for flag in -Wmissing-declarations; do
done
printf "\n\t%s\n" "${WARN_CFLAGS}"
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 \
rm -f minimal minimal.c _time_t.c _libversion.c _libversion _libversion.sh _check_session_keys.c _check_session_keys _check_gmime_cert.c _check_x509_validity _check_email \
_verify_sig_with_session_key.c _verify_sig_with_session_key
# construct the Makefile.config
@ -1557,6 +1573,9 @@ NOTMUCH_HAVE_XAPIAN_DB_RETRY_LOCK=${WITH_RETRY_LOCK}
# Whether GMime can verify X.509 certificate validity
NOTMUCH_GMIME_X509_CERT_VALIDITY=${gmime_x509_cert_validity}
# Whether GMime emits addresses with angle brackets (with <>)
NOTMUCH_GMIME_EMITS_ANGLE_BRACKETS=${gmime_emits_angle_brackets}
# Whether GMime can verify signatures when decrypting with a session key:
NOTMUCH_GMIME_VERIFY_WITH_SESSION_KEY=${gmime_verify_with_session_key}

28
debian/changelog vendored
View file

@ -1,3 +1,31 @@
notmuch (0.36-1) unstable; urgency=medium
* New upstream release
-- David Bremner <bremner@debian.org> Mon, 25 Apr 2022 08:47:41 -0300
notmuch (0.36~rc1-1) experimental; urgency=medium
* New upstream release candidate
* Fix for build in environments where libsexp is not available
(i.e. outside Debian).
-- David Bremner <bremner@debian.org> Sat, 16 Apr 2022 08:37:12 -0300
notmuch (0.36~rc0-1) experimental; urgency=medium
* New upstream release candidate
* Re-enable test smime.4, allegedly fixed upstream.
-- David Bremner <bremner@debian.org> Fri, 15 Apr 2022 08:45:10 -0300
notmuch (0.35-2) unstable; urgency=medium
* Disable test smime.4, which is broken by gmime 3.2.9 thanks to Lucas
Nussbaum for the report (Closes: #1008462).
-- David Bremner <bremner@debian.org> Mon, 28 Mar 2022 11:45:11 -0600
notmuch (0.35-1~bpo11+2) bullseye-backports; urgency=medium
* Once more with binaries

3
debian/rules vendored
View file

@ -15,9 +15,6 @@ 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 all sphinx-html
PYBUILD_NAME=notmuch dh_auto_build --buildsystem=pybuild --sourcedirectory bindings/python

View file

@ -1,58 +1,59 @@
|-----------+----------------------------------------+-------------------------------------------------------+-----------------------------------------|
| Key | Search Mode | Show Mode | Tree Mode |
|-----------+----------------------------------------+-------------------------------------------------------+-----------------------------------------|
| a | notmuch-search-archive-thread | notmuch-show-archive-message-then-next-or-next-thread | notmuch-tree-archive-message-then-next |
| b | notmuch-search-scroll-down | notmuch-show-resend-message | notmuch-show-resend-message |
| c | notmuch-search-stash-map | notmuch-show-stash-map | notmuch-show-stash-map |
| d | | | |
| e | | | (notmuch-tree-button-activate) |
| f | | notmuch-show-forward-message | notmuch-show-forward-message |
| g | | | |
| h | | notmuch-show-toggle-visibility-headers | |
| i | | | |
| j | notmuch-jump-search | notmuch-jump-search | notmuch-jump-search |
| k | notmuch-tag-jump | notmuch-tag-jump | notmuch-tag-jump |
| l | notmuch-search-filter | notmuch-show-filter-thread | notmuch-tree-filter |
| m | notmuch-mua-new-mail | notmuch-mua-new-mail | notmuch-mua-new-mail |
| n | notmuch-search-next-thread | notmuch-show-next-open-message | notmuch-tree-next-matching-message |
| o | notmuch-search-toggle-order | | notmuch-tree-toggle-order |
| p | notmuch-search-previous-thread | notmuch-show-previous-open-message | notmuch-tree-prev-matching-message |
| 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 | notmuch-tree-filter-by-tag |
| u | | | |
| v | | | notmuch-show-view-all-mime-parts |
| w | | notmuch-show-save-attachments | notmuch-show-save-attachments |
| x | notmuch-bury-or-kill-this-buffer | notmuch-show-archive-message-then-next-or-exit | notmuch-tree-quit |
| y | | | |
| z | notmuch-tree | notmuch-tree | notmuch-tree-to-tree |
| A | | notmuch-show-archive-thread-then-next | notmuch-tree-archive-thread |
| F | | notmuch-show-forward-open-messages | |
| G | notmuch-poll-and-refresh-this-buffer | notmuch-poll-and-refresh-this-buffer | notmuch-poll-and-refresh-this-buffer |
| N | | notmuch-show-next-message | notmuch-tree-next-message |
| O | | | |
| P | | notmuch-show-previous-message | notmuch-tree-prev-message |
| R | notmuch-search-reply-to-thread | notmuch-show-reply | notmuch-show-reply |
| S | | | notmuch-search-from-tree-current-query |
| V | | notmuch-show-view-raw-message | notmuch-show-view-raw-message |
| X | | notmuch-show-archive-thread-then-exit | |
| Z | notmuch-tree-from-search-current-query | notmuch-tree-from-show-current-query | |
| =!= | | notmuch-show-toggle-elide-non-matching | |
| =#= | | notmuch-show-print-message | |
| =$= | | notmuch-show-toggle-process-crypto | |
| =*= | notmuch-search-tag-all | notmuch-show-tag-all | notmuch-tree-tag-thread |
| + | notmuch-search-add-tag | notmuch-show-add-tag | notmuch-tree-add-tag |
| - | notmuch-search-remove-tag | notmuch-show-remove-tag | notmuch-tree-remove-tag |
| . | | notmuch-show-part-map | |
| < | notmuch-search-first-thread | notmuch-show-toggle-thread-indentation | |
| <DEL> | notmuch-search-scroll-down | notmuch-show-rewind | notmuch-tree-scroll-message-window-back |
| <RET> | notmuch-search-show-thread | notmuch-show-toggle-message | notmuch-tree-show-message |
| <SPC> | notmuch-search-scroll-up | notmuch-show-advance | notmuch-tree-scroll-or-next |
| <TAB> | | notmuch-show-next-button | notmuch-show-next-button |
| <backtab> | | notmuch-show-previous-button | notmuch-show-previous-button |
| = | notmuch-refresh-this-buffer | notmuch-refresh-this-buffer | notmuch-tree-refresh-view |
| > | notmuch-search-last-thread | | |
| ? | notmuch-help | notmuch-help | notmuch-help |
| \vert | | notmuch-show-pipe-message | notmuch-show-pipe-message |
|-----------+----------------------------------------+-------------------------------------------------------+-----------------------------------------|
|--------------+----------------------------------------+-------------------------------------------------------+-----------------------------------------|
| Key | Search Mode | Show Mode | Tree Mode |
|--------------+----------------------------------------+-------------------------------------------------------+-----------------------------------------|
| a | notmuch-search-archive-thread | notmuch-show-archive-message-then-next-or-next-thread | notmuch-tree-archive-message-then-next |
| b | notmuch-search-scroll-down | notmuch-show-resend-message | notmuch-show-resend-message |
| c | notmuch-search-stash-map | notmuch-show-stash-map | notmuch-show-stash-map |
| d | | | |
| e | | | (notmuch-tree-button-activate) |
| f | | notmuch-show-forward-message | notmuch-show-forward-message |
| g | | | |
| h | | notmuch-show-toggle-visibility-headers | |
| i | | | |
| j | notmuch-jump-search | notmuch-jump-search | notmuch-jump-search |
| k | notmuch-tag-jump | notmuch-tag-jump | notmuch-tag-jump |
| l | notmuch-search-filter | notmuch-show-filter-thread | notmuch-tree-filter |
| m | notmuch-mua-new-mail | notmuch-mua-new-mail | notmuch-mua-new-mail |
| n | notmuch-search-next-thread | notmuch-show-next-open-message | notmuch-tree-next-matching-message |
| o | notmuch-search-toggle-order | | notmuch-tree-toggle-order |
| p | notmuch-search-previous-thread | notmuch-show-previous-open-message | notmuch-tree-prev-matching-message |
| 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 | notmuch-tree-filter-by-tag |
| u | | | |
| v | | | notmuch-show-view-all-mime-parts |
| w | | notmuch-show-save-attachments | notmuch-show-save-attachments |
| x | notmuch-bury-or-kill-this-buffer | notmuch-show-archive-message-then-next-or-exit | notmuch-tree-quit |
| y | | | |
| z | notmuch-tree | notmuch-tree | notmuch-tree-to-tree |
| A | | notmuch-show-archive-thread-then-next | notmuch-tree-archive-thread |
| F | | notmuch-show-forward-open-messages | |
| G | notmuch-poll-and-refresh-this-buffer | notmuch-poll-and-refresh-this-buffer | notmuch-poll-and-refresh-this-buffer |
| N | | notmuch-show-next-message | notmuch-tree-next-message |
| O | | | |
| P | | notmuch-show-previous-message | notmuch-tree-prev-message |
| R | notmuch-search-reply-to-thread | notmuch-show-reply | notmuch-show-reply |
| S | | | notmuch-search-from-tree-current-query |
| V | | notmuch-show-view-raw-message | notmuch-show-view-raw-message |
| X | | notmuch-show-archive-thread-then-exit | |
| Z | notmuch-tree-from-search-current-query | notmuch-tree-from-show-current-query | |
| =!= | | notmuch-show-toggle-elide-non-matching | |
| =#= | | notmuch-show-print-message | |
| =$= | | notmuch-show-toggle-process-crypto | |
| =*= | notmuch-search-tag-all | notmuch-show-tag-all | notmuch-tree-tag-thread |
| + | notmuch-search-add-tag | notmuch-show-add-tag | notmuch-tree-add-tag |
| - | notmuch-search-remove-tag | notmuch-show-remove-tag | notmuch-tree-remove-tag |
| . | | notmuch-show-part-map | |
| < | notmuch-search-first-thread | notmuch-show-toggle-thread-indentation | |
| <DEL> | notmuch-search-scroll-down | notmuch-show-rewind | notmuch-tree-scroll-message-window-back |
| <RET> | notmuch-search-show-thread | notmuch-show-toggle-message | notmuch-tree-show-message |
| <SPC> | notmuch-search-scroll-up | notmuch-show-advance | notmuch-tree-scroll-or-next |
| <TAB> | | notmuch-show-next-button | notmuch-show-next-button |
| <backtab> | | notmuch-show-previous-button | notmuch-show-previous-button |
| = | notmuch-refresh-this-buffer | notmuch-refresh-this-buffer | notmuch-tree-refresh-view |
| > | notmuch-search-last-thread | | |
| ? | notmuch-help | notmuch-help | notmuch-help |
| \vert | | notmuch-show-pipe-message | notmuch-show-pipe-message |
| [remap undo] | notmuch-tag-undo | notmuch-tag-undo | notmuch-tag-undo |
|--------------+----------------------------------------+-------------------------------------------------------+-----------------------------------------|

View file

@ -0,0 +1,11 @@
#!/usr/bin/env python3
# to launch nmweb from gunicorn.
from nmweb import urls, index, search, show
import web
app = web.application(urls, globals())
# get the wsgi app from web.py application object
wsgiapp = app.wsgifunc()

366
devel/notmuch-web/nmweb.py Executable file
View file

@ -0,0 +1,366 @@
#!/usr/bin/env python
from __future__ import absolute_import
try:
from urllib.parse import quote_plus
from urllib.parse import unquote_plus
except ImportError:
from urllib import quote_plus
from urllib import unquote_plus
from datetime import datetime
from mailbox import MaildirMessage
import mimetypes
import email
import re
import html
import os
import bleach
import web
from notmuch2 import Database
from jinja2 import Environment, FileSystemLoader # FIXME to PackageLoader
from jinja2 import Markup
try:
import bjoern # from https://github.com/jonashaag/bjoern/
use_bjoern = True
except:
use_bjoern = False
# Configuration options
safe_tags = bleach.sanitizer.ALLOWED_TAGS + \
[u'div', u'span', u'p', u'br', u'table', u'tr', u'td', u'th']
linkify_plaintext = True # delays page load by about 0.02s of 0.20s budget
show_thread_nav = True # delays page load by about 0.04s of 0.20s budget
prefix = os.environ.get('NMWEB_PREFIX', "http://localhost:8080")
webprefix = os.environ.get('NMWEB_STATIC', prefix + "/static")
cachedir = os.environ.get('NMWEB_CACHE', "static/cache") # special for webpy server; changeable if using your own
cachepath = os.environ.get('NMWEB_CACHE_PATH', cachedir) # location of static cache in the local filesystem
if 'NMWEB_DEBUG' in os.environ:
web.config.debug = True
else:
web.config.debug = False
# End of config options
env = Environment(autoescape=True,
loader=FileSystemLoader('templates'))
urls = (
'/', 'index',
'/search/(.*)', 'search',
'/show/(.*)', 'show',
)
def urlencode_filter(s):
if type(s) == 'Markup':
s = s.unescape()
s = s.encode('utf8')
s = quote_plus(s)
return Markup(s)
env.filters['url'] = urlencode_filter
class index:
def GET(self):
web.header('Content-type', 'text/html')
base = env.get_template('base.html')
template = env.get_template('index.html')
db = Database()
tags = db.tags
return template.render(tags=tags,
title="Notmuch webmail",
prefix=prefix,
sprefix=webprefix)
class search:
def GET(self, terms):
redir = False
if web.input(terms=None).terms:
redir = True
terms = web.input().terms
terms = unquote_plus (terms)
if web.input(afters=None).afters:
afters = web.input(afters=None).afters[:-3]
else:
afters = '0'
if web.input(befores=None).befores:
befores = web.input(befores=None).befores
else:
befores = '4294967296' # 2^32
try:
if int(afters) > 0 or int(befores) < 4294967296:
redir = True
terms += ' date:@%s..@%s' % (int(afters), int(befores))
except ValueError:
pass
if redir:
raise web.seeother('/search/%s' % quote_plus(terms.encode('utf8')))
web.header('Content-type', 'text/html')
db = Database()
ts = db.threads(query=terms, sort=Database.SORT.NEWEST_FIRST)
template = env.get_template('search.html')
return template.generate(terms=terms,
ts=ts,
title=terms,
prefix=prefix,
sprefix=webprefix)
def format_time_range(start, end):
if end-start < (60*60*24):
time = datetime.fromtimestamp(start).strftime('%Y %b %d %H:%M')
else:
start = datetime.fromtimestamp(start).strftime("%Y %b %d")
end = datetime.fromtimestamp(end).strftime("%Y %b %d")
time = "%s through %s" % (start, end)
return time
env.globals['format_time_range'] = format_time_range
def mailto_addrs(msg,header_name):
try:
hdr = msg.header(header_name)
except LookupError:
return ''
frm = email.utils.getaddresses([hdr])
return ','.join(['<a href="mailto:%s">%s</a> ' % ((l, p) if p else (l, l)) for (p, l) in frm])
env.globals['mailto_addrs'] = mailto_addrs
def link_msg(msg):
lnk = quote_plus(msg.messageid.encode('utf8'))
try:
subj = msg.header('Subject')
except LookupError:
subj = ""
out = '<a href="%s/show/%s">%s</a>' % (prefix, lnk, subj)
return out
env.globals['link_msg'] = link_msg
def show_msgs(msgs):
r = '<ul>'
for msg in msgs:
red = 'color:black; font-style:normal'
if msg.matched:
red = 'color:red; font-style:italic'
frm = mailto_addrs(msg,'From')
lnk = link_msg(msg)
tags = ", ".join(msg.tags)
rs = show_msgs(msg.replies())
r += '<li><span style="%s">%s&mdash;%s</span> [%s] %s</li>' % (red, frm, lnk, tags, rs)
r += '</ul>'
return r
env.globals['show_msgs'] = show_msgs
# As email.message.walk, but showing close tags as well
def mywalk(self):
yield self
if self.is_multipart():
for subpart in self.get_payload():
for subsubpart in mywalk(subpart):
yield subsubpart
yield 'close-div'
class show:
def GET(self, mid):
web.header('Content-type', 'text/html')
db = Database()
try:
m = db.find(mid)
except:
raise web.notfound("No such message id.")
template = env.get_template('show.html')
# FIXME add reply-all link with email.urils.getaddresses
# FIXME add forward link using mailto with body parameter?
return template.render(m=m,
mid=mid,
title=m.header('Subject'),
prefix=prefix,
sprefix=webprefix)
def thread_nav(m):
if not show_thread_nav: return
db = Database()
thread = next(db.threads('thread:'+m.threadid))
prv = None
found = False
nxt = None
for msg in thread:
if m == msg:
found = True
elif not found:
prv = msg
else: # found message, but not on this loop
nxt = msg
break
yield "<hr><ul>"
if prv: yield "<li>Previous message (by thread): %s</li>" % link_msg(prv)
if nxt: yield "<li>Next message (by thread): %s</li>" % link_msg(nxt)
yield "</ul><h3>Thread:</h3>"
# FIXME show now takes three queries instead of 1;
# can we yield the message body while computing the thread shape?
thread = next(db.threads('thread:'+m.threadid))
yield show_msgs(thread.toplevel())
return
env.globals['thread_nav'] = thread_nav
def format_message(nm_msg, mid):
fn = list(nm_msg.filenames())[0]
msg = MaildirMessage(open(fn))
return format_message_walk(msg, mid)
def decodeAnyway(txt, charset='ascii'):
try:
out = txt.decode(charset)
except:
try:
out = txt.decode('utf-8')
except UnicodeDecodeError:
out = txt.decode('latin1')
return out
def require_protocol_prefix(attrs, new=False):
if not new:
return attrs
link_text = attrs[u'_text']
if link_text.startswith(('http:', 'https:', 'mailto:', 'git:', 'id:')):
return attrs
return None
# Bleach doesn't even try to linkify id:... text, so no point invoking this yet
def modify_id_links(attrs, new=False):
if attrs[(None, u'href')].startswith(u'id:'):
attrs[(None, u'href')] = prefix + "/show/" + attrs[(None, u'href')][3:]
return attrs
def css_part_id(content_type, parts=[]):
c = content_type.replace('/', '-')
out = "-".join(parts + [c])
return out
def format_message_walk(msg, mid):
counter = 0
cid_refd = []
parts = ['main']
for part in mywalk(msg):
if part == 'close-div':
parts.pop()
yield '</div>'
elif part.get_content_maintype() == 'multipart':
yield '<div class="multipart-%s" id="%s">' % \
(part.get_content_subtype(), css_part_id(part.get_content_type(), parts))
parts.append(part.get_content_subtype())
if part.get_content_subtype() == 'alternative':
yield '<ul>'
for subpart in part.get_payload():
yield ('<li><a href="#%s">%s</a></li>' %
(css_part_id(subpart.get_content_type(), parts),
subpart.get_content_type()))
yield '</ul>'
elif part.get_content_type() == 'message/rfc822':
# FIXME extract subject, date, to/cc/from into a separate template and use it here
yield '<div class="message-rfc822">'
elif part.get_content_maintype() == 'text':
if part.get_content_subtype() == 'plain':
yield '<div id="%s">' % css_part_id(part.get_content_type(), parts)
yield '<pre>'
out = part.get_payload(decode=True)
out = decodeAnyway(out, part.get_content_charset('ascii'))
out = html.escape(out)
out = out.encode('ascii', 'xmlcharrefreplace').decode('ascii')
if linkify_plaintext: out = bleach.linkify(out, callbacks=[require_protocol_prefix])
yield out
yield '</pre></div>'
elif part.get_content_subtype() == 'html':
yield '<div id="%s">' % css_part_id(part.get_content_type(), parts)
unb64 = part.get_payload(decode=True)
decoded = decodeAnyway(unb64, part.get_content_charset('ascii'))
cid_refd += find_cids(decoded)
part.set_payload(bleach.clean(replace_cids(decoded, mid), tags=safe_tags).
encode(part.get_content_charset('ascii'), 'xmlcharrefreplace'))
(filename, cid) = link_to_cached_file(part, mid, counter)
counter += 1
yield '<iframe class="embedded-html" src="%s"></iframe>' % \
os.path.join(prefix, cachedir, mid, filename)
yield '</div>'
else:
yield '<div id="%s">' % css_part_id(part.get_content_type(), parts)
(filename, cid) = link_to_cached_file(part, mid, counter)
counter += 1
yield '<a href="%s">%s (%s)</a>' % (os.path.join(prefix,
cachedir,
mid,
filename),
filename,
part.get_content_type())
yield '</div>'
elif part.get_content_maintype() == 'image':
(filename, cid) = link_to_cached_file(part, mid, counter)
if cid not in cid_refd:
counter += 1
yield '<img src="%s" alt="%s">' % (os.path.join(prefix,
cachedir,
mid,
filename),
filename)
else:
(filename, cid) = link_to_cached_file(part, mid, counter)
counter += 1
yield '<a href="%s">%s (%s)</a>' % (os.path.join(prefix,
cachedir,
mid,
filename),
filename,
part.get_content_type())
env.globals['format_message'] = format_message
def replace_cids(body, mid):
return body.replace('cid:', os.path.join(prefix, cachedir, mid)+'/')
def find_cids(body):
return re.findall(r'cid:([^ "\'>]*)', body)
def link_to_cached_file(part, mid, counter):
filename = part.get_filename()
if not filename:
ext = mimetypes.guess_extension(part.get_content_type())
if not ext:
ext = '.bin'
filename = 'part-%03d%s' % (counter, ext)
try:
os.makedirs(os.path.join(cachepath, mid))
except OSError:
pass
fn = os.path.join(cachepath, mid, filename) # FIXME escape mid, filename
fp = open(fn, 'wb')
if part.get_content_maintype() == 'text':
data = part.get_payload(decode=True)
data = decodeAnyway(data, part.get_content_charset('ascii')).encode('utf-8')
else:
try:
data = part.get_payload(decode=True)
except:
data = part.get_payload(decode=False)
if data:
fp.write(data)
fp.close()
if 'Content-ID' in part:
cid = part['Content-ID']
if cid[0] == '<' and cid[-1] == '>': cid = cid[1:-1]
cid_fn = os.path.join(cachepath, mid, cid) # FIXME escape mid, cid
try:
os.unlink(cid_fn)
except OSError:
pass
os.link(fn, cid_fn)
return (filename, cid)
else:
return (filename, None)
if __name__ == '__main__':
app = web.application(urls, globals())
if use_bjoern:
bjoern.run(app.wsgifunc(), "127.0.0.1", 8080)
else:
app.run()

View file

@ -0,0 +1 @@
/usr/share/javascript/jquery-ui/themes/base/jquery-ui.min.css

View file

@ -0,0 +1,15 @@
pre {
white-space: pre-wrap;
}
.message-rfc822 {
border: 1px solid;
border-radius: 25px;
}
.embedded-html {
frameborder: 0;
border: 0;
scrolling: no;
width: 100%;
}

1
devel/notmuch-web/static/js/jquery-ui.js vendored Symbolic link
View file

@ -0,0 +1 @@
/usr/share/javascript/jquery-ui/jquery-ui.min.js

1
devel/notmuch-web/static/js/jquery.js vendored Symbolic link
View file

@ -0,0 +1 @@
/usr/share/javascript/jquery/jquery.min.js

View file

@ -0,0 +1,35 @@
$(function(){
$("#after").datepicker({
altField: "#afters",
altFormat: "@",
changeMonth: true,
changeYear: true,
defaultDate: "-7d",
minDate: "01/01/1970",
yearRange: "2000:+0",
onSelect: function(selectedDate) {
$("#before").datepicker("option","minDate",selectedDate);
}
});
$("#before").datepicker({
altField: "#befores",
altFormat: "@",
changeMonth: true,
changeYear: true,
defaultDate: "+1d",
maxDate: "+1d",
yearRange: "2000:+0",
onSelect: function(selectedDate) {
$("#after").datepicker("option","maxDate",selectedDate);
}
});
$(function(){
$('.multipart-alternative').tabs()
});
$(function(){
$('.embedded-html').on('load',function(){
this.style.height = this.contentWindow.document.body.offsetHeight + 'px';
});
});
});

View file

@ -0,0 +1,39 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
<html lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"
/>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link type="text/css" href="{{sprefix}}/css/jquery-ui.css" rel="stylesheet" />
<link type="text/css" href="{{sprefix}}/css/notmuch-0.1.css" rel="stylesheet" />
<script type="text/javascript" src="{{sprefix}}/js/jquery.js"></script>
<script type="text/javascript" src="{{sprefix}}/js/jquery-ui.js"></script>
<script type="text/javascript" src="{{sprefix}}/js/notmuch-0.1.js"></script>
<title>{{title}}</title>
</head><body>
<div data-role="page">
<div data-role="header">
{% block searchform %}
<form action="{{prefix}}/search/" method="GET" data-ajax="false">
<label for="terms">Terms</label><input id="terms" name="terms">
<label for="after">After</label><input id="after"
name="after"><input type="hidden" id="afters" name="afters">
<label for="before">Before</label><input id="before"
name="before"><input id="befores" type="hidden" name="befores">
<input type="submit" name="submit" id="submit" value="Search">
</form>
{% endblock searchform %}
<h2>{{title}}</h2>
</div>
<div data-role="content">
{% block content %}
<h2>Common tags</h2>
<ul>
{% for tag in tags %}
<li><a href="search/tag:{{ tag|url }}">{{ tag|e }}</a></li>
{% endfor %}
</ul>
</div>
{% endblock content %}
</div>
</body></html>

View file

@ -0,0 +1,9 @@
{% extends "base.html" %}
{% block content %}
<h2>Common tags</h2>
<ul>
{% for tag in tags %}
<li><a href="search/tag:{{ tag|url }}">{{ tag|e }}</a></li>
{% endfor %}
</ul>
{% endblock content %}

View file

@ -0,0 +1,10 @@
{% extends "base.html" %}
<h1>{{ terms|e }}</h1>
{% block content %}
{% for t in ts %}
<h2>{{ t.subject|e }}</h2>
<p><i>{{ t.authors|e }}</i></p>
<p><b>{{ format_time_range(t.first,t.last)|e }}</b></p>
{{ show_msgs(t.toplevel())|safe }}
{% endfor %}
{% endblock content %}

View file

@ -0,0 +1,15 @@
{% extends "base.html" %}
{% block content %}
{% set headers = ['Subject', 'Date'] %}
{% set addr_headers = ['To', 'Cc', 'From'] %}
{% for header in headers: %}
<p><b>{{header}}:</b>{{m.header(header)|e}}</p>
{% endfor %}
{% for header in addr_headers: %}
<p><b>{{header}}:</b>{{mailto_addrs(m,header)|safe}}</p>
{% endfor %}
<hr>
{% for part in format_message(m,mid): %}{{ part|safe }}{% endfor %}
{% for b in thread_nav(m): %}{{b|safe}}{% endfor %}
<hr>
{% endblock content %}

14
devel/notmuch-web/todo Normal file
View file

@ -0,0 +1,14 @@
review escaping and safety handling mail from Bad People
revise template loader---can we make this faster?
add reply-all link with email.urils.getaddresses
change current reply links to quote body
add forward link using mailto with body parameter?
unescape the current search term, including translating back dates
later: json support, iOS app?

View file

@ -59,7 +59,7 @@ readonly VERSION
# In the rest of this file, tests collect list of errors to be fixed
echo -n "Checking that git working directory is clean... "
printf %s "Checking that git working directory is clean... "
git_status=`git status --porcelain`
if [ "$git_status" = '' ]
then
@ -77,7 +77,7 @@ verfail ()
append_emsg " Please follow the instructions in RELEASING to choose a version"
}
echo -n "Checking that '$VERSION' is good with digits and periods... "
printf %s "Checking that '$VERSION' is good with digits and periods... "
case $VERSION in
*[!0-9.]*)
verfail "'$VERSION' contains other characters than digits and periods" ;;
@ -88,7 +88,7 @@ case $VERSION in
*) verfail "'$VERSION' is a single number" ;;
esac
echo -n "Checking that this is Debian package for notmuch... "
printf %s "Checking that this is Debian package for notmuch... "
read deb_notmuch deb_version rest < debian/changelog
if [ "$deb_notmuch" = 'notmuch' ]
then
@ -98,7 +98,7 @@ else
append_emsg "Package name '$deb_notmuch' is not 'notmuch' in debian/changelog"
fi
echo -n "Checking that Debian package version is $VERSION-1... "
printf %s "Checking that Debian package version is $VERSION-1... "
if [ "$deb_version" = "($VERSION-1)" ]
then
@ -108,7 +108,7 @@ else
append_emsg "Version '$deb_version' is not '($VERSION-1)' in debian/changelog"
fi
echo -n "Checking that python bindings version is $VERSION... "
printf %s "Checking that python bindings version is $VERSION... "
py_version=`python3 -c "with open('$PV_FILE') as vf: exec(vf.read()); print(__VERSION__)"`
if [ "$py_version" = "$VERSION" ]
then
@ -118,7 +118,7 @@ else
append_emsg "Version '$py_version' is not '$VERSION' in $PV_FILE"
fi
echo -n "Checking that NEWS header is tidy... "
printf %s "Checking that NEWS header is tidy... "
if [ "`exec sed 's/./=/g; 1q' NEWS`" = "`exec sed '1d; 2q' NEWS`" ]
then
echo Yes.
@ -132,7 +132,7 @@ else
fi
fi
echo -n "Checking that this is Notmuch NEWS... "
printf %s "Checking that this is Notmuch NEWS... "
read news_notmuch news_version news_date < NEWS
if [ "$news_notmuch" = "Notmuch" ]
then
@ -142,7 +142,7 @@ else
append_emsg "First word '$news_notmuch' is not 'Notmuch' in NEWS file"
fi
echo -n "Checking that NEWS version is $VERSION... "
printf %s "Checking that NEWS version is $VERSION... "
if [ "$news_version" = "$VERSION" ]
then
echo Yes.
@ -154,7 +154,7 @@ fi
#eval `date '+year=%Y mon=%m day=%d'`
today0utc=`date --date=0Z +%s` # gnu date feature
echo -n "Checking that NEWS date is right... "
printf %s "Checking that NEWS date is right... "
case $news_date in
'('[2-9][0-9][0-9][0-9]-[01][0-9]-[0123][0-9]')')
newsdate0utc=`nd=${news_date#\\(}; date --date="${nd%)} 0Z" +%s`
@ -176,7 +176,7 @@ case $news_date in
esac
year=`exec date +%Y`
echo -n "Checking that copyright in documentation contains 2009-$year... "
printf %s "Checking that copyright in documentation contains 2009-$year... "
# Read the value of variable `copyright' defined in 'doc/conf.py'.
copyrightline=$(grep ^copyright doc/conf.py)
case $copyrightline in

View file

@ -55,22 +55,19 @@ The available configuration items are described below. Non-absolute
paths are presumed relative to `$HOME` for items in section
**database**.
database.path
Notmuch will store its database here, (in
sub-directory named ``.notmuch`` if **database.mail\_root**
is unset).
built_with.<name>
Compile time feature <name>. Current possibilities include
"retry_lock" (configure option, included by default).
(since notmuch 0.30, "compact" and "field_processor" are
always included.)
Default: see :ref:`database`
database.autocommit
database.mail_root
The top-level directory where your mail currently exists and to
where mail will be delivered in the future. Files should be
individual email messages.
How often to commit transactions to disk. `0` means wait until
command completes, otherwise an integer `n` specifies to commit to
disk after every `n` completed transactions.
History: this configuration value was introduced in notmuch 0.32.
Default: For compatibility with older configurations, the value of
database.path is used if **database.mail\_root** is unset.
History: this configuration value was introduced in notmuch 0.33.
database.backup_dir
Directory to store tag dumps when upgrading database.
@ -88,109 +85,26 @@ database.hook_dir
Default: See HOOKS, below.
database.autocommit
.. _database.mail_root:
How often to commit transactions to disk. `0` means wait until
command completes, otherwise an integer `n` specifies to commit to
disk after every `n` completed transactions.
database.mail_root
The top-level directory where your mail currently exists and to
where mail will be delivered in the future. Files should be
individual email messages.
History: this configuration value was introduced in notmuch 0.33.
History: this configuration value was introduced in notmuch 0.32.
user.name
Your full name.
Default: For compatibility with older configurations, the value of
database.path is used if **database.mail\_root** is unset.
Default: ``$NAME`` variable if set, otherwise read from
``/etc/passwd``.
database.path
Notmuch will store its database here, (in
sub-directory named ``.notmuch`` if **database.mail\_root**
is unset).
user.primary\_email
Your primary email address.
Default: see :ref:`database`
Default: ``$EMAIL`` variable if set, otherwise constructed from
the username and hostname of the current machine.
user.other\_email
A list of other email addresses at which you receive email.
Default: not set.
new.tags
A list of tags that will be added to all messages incorporated by
**notmuch new**.
Default: ``unread;inbox``.
new.ignore
A list to specify files and directories that will not be searched
for messages by :any:`notmuch-new(1)`. Each entry in the list is either:
A file or a directory name, without path, that will be ignored,
regardless of the location in the mail store directory hierarchy.
Or:
A regular expression delimited with // that will be matched
against the path of the file or directory relative to the database
path. Matching files and directories will be ignored. The
beginning and end of string must be explicitly anchored. For
example, /.*/foo$/ would match "bar/foo" and "bar/baz/foo", but
not "foo" or "bar/foobar".
Default: empty list.
search.exclude\_tags
A list of tags that will be excluded from search results by
default. Using an excluded tag in a query will override that
exclusion.
Default: empty list. Note that :any:`notmuch-setup(1)` puts
``deleted;spam`` here when creating new configuration file.
.. _show.extra_headers:
show.extra\_headers
By default :any:`notmuch-show(1)` includes the following headers
in structured output if they are present in the message:
`Subject`, `From`, `To`, `Cc`, `Bcc`, `Reply-To`, `Date`. This
option allows the specification of a list of further
headers to output.
History: This configuration value was introduced in notmuch 0.35.
Default: empty list.
maildir.synchronize\_flags
If true, then the following maildir flags (in message filenames)
will be synchronized with the corresponding notmuch tags:
+--------+-----------------------------------------------+
| Flag | Tag |
+========+===============================================+
| D | draft |
+--------+-----------------------------------------------+
| F | flagged |
+--------+-----------------------------------------------+
| P | passed |
+--------+-----------------------------------------------+
| R | replied |
+--------+-----------------------------------------------+
| S | unread (added when 'S' flag is not present) |
+--------+-----------------------------------------------+
The :any:`notmuch-new(1)` command will notice flag changes in
filenames and update tags, while the :any:`notmuch-tag(1)` and
:any:`notmuch-restore(1)` commands will notice tag changes and
update flags in filenames.
If there have been any changes in the maildir (new messages added,
old ones removed or renamed, maildir flags changed, etc.), it is
advisable to run :any:`notmuch-new(1)` before
:any:`notmuch-tag(1)` or :any:`notmuch-restore(1)` commands to
ensure the tag changes are properly synchronized to the maildir
flags, as the commands expect the database and maildir to be in
sync.
Default: ``true``.
.. _index.decrypt:
index.decrypt
Policy for decrypting encrypted messages during indexing. Must be
@ -245,6 +159,8 @@ index.decrypt
Default: ``auto``.
.. _index.header:
index.header.<prefix>
Define the query prefix <prefix>, based on a mail header. For
example ``index.header.List=List-Id`` will add a probabilistic
@ -254,22 +170,121 @@ index.header.<prefix>
supported. See :any:`notmuch-search-terms(7)` for a list of existing
prefixes, and an explanation of probabilistic prefixes.
built_with.<name>
Compile time feature <name>. Current possibilities include
"retry_lock" (configure option, included by default).
(since notmuch 0.30, "compact" and "field_processor" are
always included.)
.. _maildir.synchronize_flags:
maildir.synchronize\_flags
If true, then the following maildir flags (in message filenames)
will be synchronized with the corresponding notmuch tags:
+--------+-----------------------------------------------+
| Flag | Tag |
+========+===============================================+
| D | draft |
+--------+-----------------------------------------------+
| F | flagged |
+--------+-----------------------------------------------+
| P | passed |
+--------+-----------------------------------------------+
| R | replied |
+--------+-----------------------------------------------+
| S | unread (added when 'S' flag is not present) |
+--------+-----------------------------------------------+
The :any:`notmuch-new(1)` command will notice flag changes in
filenames and update tags, while the :any:`notmuch-tag(1)` and
:any:`notmuch-restore(1)` commands will notice tag changes and
update flags in filenames.
If there have been any changes in the maildir (new messages added,
old ones removed or renamed, maildir flags changed, etc.), it is
advisable to run :any:`notmuch-new(1)` before
:any:`notmuch-tag(1)` or :any:`notmuch-restore(1)` commands to
ensure the tag changes are properly synchronized to the maildir
flags, as the commands expect the database and maildir to be in
sync.
Default: ``true``.
.. _new.ignore:
new.ignore
A list to specify files and directories that will not be searched
for messages by :any:`notmuch-new(1)`. Each entry in the list is either:
A file or a directory name, without path, that will be ignored,
regardless of the location in the mail store directory hierarchy.
Or:
A regular expression delimited with // that will be matched
against the path of the file or directory relative to the database
path. Matching files and directories will be ignored. The
beginning and end of string must be explicitly anchored. For
example, /.*/foo$/ would match "bar/foo" and "bar/baz/foo", but
not "foo" or "bar/foobar".
Default: empty list.
.. _new.tags:
new.tags
A list of tags that will be added to all messages incorporated by
**notmuch new**.
Default: ``unread;inbox``.
query.<name>
Expansion for named query called <name>. See
:any:`notmuch-search-terms(7)` for more information about named
queries.
search.exclude\_tags
A list of tags that will be excluded from search results by
default. Using an excluded tag in a query will override that
exclusion.
Default: empty list. Note that :any:`notmuch-setup(1)` puts
``deleted;spam`` here when creating new configuration file.
.. _show.extra_headers:
show.extra\_headers
By default :any:`notmuch-show(1)` includes the following headers
in structured output if they are present in the message:
`Subject`, `From`, `To`, `Cc`, `Bcc`, `Reply-To`, `Date`. This
option allows the specification of a list of further
headers to output.
History: This configuration value was introduced in notmuch 0.35.
Default: empty list.
squery.<name>
Expansion for named query called <name>, using s-expression syntax. See
:any:`notmuch-sexp-queries(7)` for more information about s-expression
queries.
user.name
Your full name.
Default: ``$NAME`` variable if set, otherwise read from
``/etc/passwd``.
user.other\_email
A list of other email addresses at which you receive email
(see also, :ref:`user.primary_email <user.primary_email>`).
Default: not set.
.. _user.primary_email:
user.primary\_email
Your primary email address.
Default: ``$EMAIL`` variable if set, otherwise constructed from
the username and hostname of the current machine.
FILES
=====

View file

@ -14,12 +14,12 @@ DESCRIPTION
**notmuch insert** reads a message from standard input and delivers it
into the maildir directory given by configuration option
**database.mail_root**, then incorporates the message into the notmuch
:ref:`database.mail_root <database.mail_root>`, then incorporates the message into the notmuch
database. It is an alternative to using a separate tool to deliver the
message then running :any:`notmuch-new(1)` afterwards.
The new message will be tagged with the tags specified by the
**new.tags** configuration option, then by operations specified on the
:ref:`new.tags <new.tags>` configuration option, then by operations specified on the
command-line: tags prefixed by '+' are added while those prefixed by '-'
are removed.
@ -86,7 +86,17 @@ Supported options for **insert** include
``--decrypt=nostash`` without considering the security of your
index.
See also ``index.decrypt`` in :any:`notmuch-config(1)`.
See also :ref:`index.decrypt <index.decrypt>` in :any:`notmuch-config(1)`.
CONFIGURATION
=============
Indexing is influenced by the configuration options
:ref:`index.decrypt <index.decrypt>` and :ref:`index.header
<index.header>`. Tagging
is controlled by :ref:`new.tags <new.tags>` and
:ref:`maildir.synchronize_flags <maildir.synchronize_flags>`. See
:any:`notmuch-config(1)` for details.
EXIT STATUS
===========

View file

@ -78,6 +78,16 @@ Supported options for **new** include
to optimize the scanning of directories for new mail. This option turns
that optimization off.
CONFIGURATION
=============
Indexing is influenced by the configuration options
:ref:`index.decrypt <index.decrypt>`, :ref:`index.header
<index.header>`, and :ref:`new.ignore <new.ignore>`. Tagging
is controlled by :ref:`new.tags <new.tags>` and
:ref:`maildir.synchronize_flags <maildir.synchronize_flags>`. See
:any:`notmuch-config(1)` for details.
EXIT STATUS
===========

View file

@ -30,9 +30,9 @@ pre-new
post-new
This hook is invoked by the :any:`notmuch-new(1)` command after
new messages have been imported into the database and initial tags
have been applied. The hook will not be run if there have been any
errors during the scan or import.
any new messages have been imported into the database and initial
tags have been applied. The hook will not be run if there have
been any errors during the scan or import.
Typically this hook is used to perform additional query-based
tagging on the imported messages.

View file

@ -169,6 +169,12 @@ property:<key>=<value>
can be present on a given message with several different values.
See :any:`notmuch-properties(7)` for more details.
sexp:<subquery>
The **sexp:** prefix allows subqueries in the format
documented in :any:`notmuch-sexp-queries(7)`. Note that subqueries containing
spaces must be quoted, and any embedded double quotes must be escaped
(see :any:`quoting`).
User defined prefixes are also supported, see :any:`notmuch-config(1)` for
details.
@ -257,7 +263,7 @@ Boolean
Probabilistic
**body:**, **to:**, **attachment:**, **mimetype:**
Special
**from:**, **query:**, **subject:**
**from:**, **query:**, **subject:**, **sexp:**
Terms and phrases
-----------------
@ -275,11 +281,13 @@ the same phrase.
- a.list.of.words
Both parenthesised lists of terms and quoted phrases are ok with
probabilistic prefixes such as **to:**, **from:**, and **subject:**. In particular
probabilistic prefixes such as **to:**, **from:**, and **subject:**.
For prefixes supporting regex search, the parenthesised list should be
quoted. In particular
::
subject:(pizza free)
subject:"(pizza free)"
is equivalent to
@ -295,6 +303,8 @@ Both of these will match a subject "Free Delicious Pizza" while
will not.
.. _quoting:
Quoting
-------
@ -322,6 +332,13 @@ e.g.
% notmuch search 'folder:"/^.*/(Junk|Spam)$/"'
% notmuch search 'thread:"{from:mallory and date:2009}" and thread:{to:mallory}'
Double quotes within query strings need to be doubled to escape them.
::
% notmuch search 'tag:"""quoted tag"""'
% notmuch search 'sexp:"(or ""wizard"" ""php"")"'
DATE AND TIME SEARCH
====================

View file

@ -331,11 +331,21 @@ tags.
As is the case with :ref:`notmuch-search`, the presentation of results
can be controlled by the variable ``notmuch-search-oldest-first``.
.. _notmuch-unthreaded:
notmuch-unthreaded
------------------
``notmuch-unthreaded-mode`` is similar to :any:`notmuch-tree` in that
each line corresponds to a single message, but no thread information
is presented.
Keybindings are the same as :any:`notmuch-tree`.
Global key bindings
===================
Several features are accessible from anywhere in notmuch through the
Several features are accessible from most places in notmuch through the
following key bindings:
``j``
@ -344,6 +354,8 @@ following key bindings:
``k``
Tagging operations using :ref:`notmuch-tag-jump`
``C-_`` ``C-/`` ``C-x u``: Undo previous tagging operation using :ref:`notmuch-tag-undo`
.. _notmuch-jump:
notmuch-jump
@ -373,6 +385,21 @@ operations specified in ``notmuch-tagging-keys``; i.e. each
|docstring::notmuch-tagging-keys|
.. _notmuch-tag-undo:
notmuch-tag-undo
----------------
Each notmuch buffer supporting tagging operations (i.e buffers in
:any:`notmuch-show`, :any:`notmuch-search`, :any:`notmuch-tree`, and
:any:`notmuch-unthreaded` mode) keeps a local stack of tagging
operations. These can be undone via ``notmuch-tag-undo``. By default
this is bound to the usual Emacs keys for undo.
:index:`notmuch-tag-undo`
|docstring::notmuch-tag-undo|
Buffer navigation
=================

View file

@ -710,6 +710,9 @@ with `notmuch-hello-query-counts'."
;; that when we modify map it does not modify widget-keymap).
(let ((map (make-composed-keymap (list (make-sparse-keymap) widget-keymap))))
(set-keymap-parent map notmuch-common-keymap)
;; Currently notmuch-hello-mode supports free text entry, but not
;; tagging operations, so provide standard undo.
(define-key map [remap notmuch-tag-undo] #'undo)
map)
"Keymap for \"notmuch hello\" buffers.")

View file

@ -166,6 +166,7 @@ For example, if you wanted to remove an \"inbox\" tag and add an
(define-key map (kbd "M-=") 'notmuch-refresh-all-buffers)
(define-key map "G" 'notmuch-poll-and-refresh-this-buffer)
(define-key map "j" 'notmuch-jump-search)
(define-key map [remap undo] 'notmuch-tag-undo)
map)
"Keymap shared by all notmuch modes.")

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

View file

@ -275,6 +275,11 @@ This can be used with `notmuch-tag-format-image-data'."
</g>
</svg>")
;;; track history of tag operations
(defvar-local notmuch-tag-history nil
"Buffer local history of `notmuch-tag' function.")
(put 'notmuch-tag-history 'permanent-local t)
;;; Format Handling
(defvar notmuch-tag--format-cache (make-hash-table :test 'equal)
@ -458,14 +463,19 @@ from TAGS if present."
"Use batch tagging if the tagging query is longer than this.
This limits the length of arguments passed to the notmuch CLI to
avoid system argument length limits and performance problems.")
avoid system argument length limits and performance problems.
(defun notmuch-tag (query tag-changes)
NOTE: this variable is no longer used.")
(make-obsolete-variable 'notmuch-tag-argument-limit nil "notmuch 0.36")
(defun notmuch-tag (query tag-changes &optional omit-hist)
"Add/remove tags in TAG-CHANGES to messages matching QUERY.
QUERY should be a string containing the search-terms.
TAG-CHANGES is a list of strings of the form \"+tag\" or
\"-tag\" to add or remove tags, respectively.
TAG-CHANGES is a list of strings of the form \"+tag\" or \"-tag\"
to add or remove tags, respectively. OMIT-HIST disables history
tracking if non-nil.
Note: Other code should always use this function to alter tags of
messages instead of running (notmuch-call-notmuch-process \"tag\" ..)
@ -481,16 +491,30 @@ notmuch-after-tag-hook will be run."
(notmuch-dlet ((tag-changes tag-changes)
(query query))
(run-hooks 'notmuch-before-tag-hook))
(if (<= (length query) notmuch-tag-argument-limit)
(apply 'notmuch-call-notmuch-process "tag"
(append tag-changes (list "--" query)))
;; Use batch tag mode to avoid argument length limitations
(let ((batch-op (concat (mapconcat #'notmuch-hex-encode tag-changes " ")
" -- " query)))
(notmuch-call-notmuch-process :stdin-string batch-op "tag" "--batch")))
(notmuch-dlet ((tag-changes tag-changes)
(query query))
(run-hooks 'notmuch-after-tag-hook))))
(with-temp-buffer
(insert (concat (mapconcat #'notmuch-hex-encode tag-changes " ") " -- " query))
(unless (= 0
(notmuch--call-process-region
(point-min) (point-max) notmuch-command t t nil "tag" "--batch"))
(notmuch-logged-error "notmuch tag failed" (buffer-string))))
(unless omit-hist
(push (list :query query :tag-changes tag-changes) notmuch-tag-history)))
(notmuch-dlet ((tag-changes tag-changes)
(query query))
(run-hooks 'notmuch-after-tag-hook)))
(defun notmuch-tag-undo ()
"Undo the previous tagging operation in the current buffer. Uses
buffer local variable `notmuch-tag-history' to determine what
that operation was."
(interactive)
(when (null notmuch-tag-history)
(error "no further notmuch undo information"))
(let* ((action (pop notmuch-tag-history))
(query (plist-get action :query))
(changes (notmuch-tag-change-list (plist-get action :tag-changes) t)))
(notmuch-tag query changes t))
(notmuch-refresh-this-buffer))
(defun notmuch-tag-change-list (tags &optional reverse)
"Convert TAGS into a list of tag changes.

View file

@ -93,7 +93,7 @@
Supported fields are: date, count, authors, subject, tags.
For example:
(setq notmuch-search-result-format
'((\"authors\" . \"%-40s\")
\\='((\"authors\" . \"%-40s\")
(\"subject\" . \"%s\")))
Line breaks are permitted in format strings (though this is

View file

@ -64,7 +64,8 @@ libnotmuch_cxx_srcs = \
$(dir)/prefix.cc \
$(dir)/open.cc \
$(dir)/init.cc \
$(dir)/parse-sexp.cc
$(dir)/parse-sexp.cc \
$(dir)/sexp-fp.cc
libnotmuch_modules := $(libnotmuch_c_srcs:.c=.o) $(libnotmuch_cxx_srcs:.cc=.o)

View file

@ -354,10 +354,6 @@ _notmuch_query_string_to_xapian_query (notmuch_database_t *notmuch,
std::string query_string,
Xapian::Query &output,
std::string &msg);
/* parse-sexp.cc */
notmuch_status_t
_notmuch_sexp_string_to_xapian_query (notmuch_database_t *notmuch, const char *querystr,
Xapian::Query &output);
notmuch_status_t
_notmuch_query_expand (notmuch_database_t *notmuch, const char *field, Xapian::Query subquery,

View file

@ -3,6 +3,7 @@
#include "thread-fp.h"
#include "regexp-fields.h"
#include "parse-time-vrp.h"
#include "sexp-fp.h"
typedef struct {
const char *name;
@ -60,6 +61,8 @@ prefix_t prefix_table[] = {
NOTMUCH_FIELD_PROCESSOR },
{ "query", NULL, NOTMUCH_FIELD_EXTERNAL |
NOTMUCH_FIELD_PROCESSOR },
{ "sexp", NULL, NOTMUCH_FIELD_EXTERNAL |
NOTMUCH_FIELD_PROCESSOR },
{ "from", "XFROM", NOTMUCH_FIELD_EXTERNAL |
NOTMUCH_FIELD_PROBABILISTIC |
NOTMUCH_FIELD_PROCESSOR },
@ -138,6 +141,8 @@ _setup_query_field (const prefix_t *prefix, notmuch_database_t *notmuch)
fp = (new QueryFieldProcessor (*notmuch->query_parser, notmuch))->release ();
else if (STRNCMP_LITERAL (prefix->name, "thread") == 0)
fp = (new ThreadFieldProcessor (*notmuch->query_parser, notmuch))->release ();
else if (STRNCMP_LITERAL (prefix->name, "sexp") == 0)
fp = (new SexpFieldProcessor (notmuch))->release ();
else
fp = (new RegexpFieldProcessor (prefix->name, prefix->flags,
*notmuch->query_parser, notmuch))->release ();

View file

@ -227,6 +227,7 @@ _notmuch_query_ensure_parsed_xapian (notmuch_query_t *query)
return NOTMUCH_STATUS_SUCCESS;
}
#if HAVE_SFSEXP
static notmuch_status_t
_notmuch_query_ensure_parsed_sexpr (notmuch_query_t *query)
{
@ -243,6 +244,7 @@ _notmuch_query_ensure_parsed_sexpr (notmuch_query_t *query)
_notmuch_query_cache_terms (query);
return NOTMUCH_STATUS_SUCCESS;
}
#endif
static notmuch_status_t
_notmuch_query_ensure_parsed (notmuch_query_t *query)

View file

@ -227,7 +227,8 @@ RegexpFieldProcessor::operator() (const std::string & str)
* phrase parsing, when possible */
std::string query_str;
if (*str.rbegin () != '*' || str.find (' ') != std::string::npos)
if ((str.at (0) != '(' || *str.rbegin () != ')') &&
(*str.rbegin () != '*' || str.find (' ') != std::string::npos))
query_str = '"' + str + '"';
else
query_str = str;

44
lib/sexp-fp.cc Normal file
View file

@ -0,0 +1,44 @@
/* sexp-fp.cc - "sexp:" field processor glue
*
* This file is part of notmuch.
*
* Copyright © 2022 David Bremner
*
* 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/ .
*
* Author: David Bremner <david@tethera.net>
*/
#include "database-private.h"
#include "sexp-fp.h"
#include <iostream>
Xapian::Query
SexpFieldProcessor::operator() (const std::string & query_string)
{
notmuch_status_t status;
Xapian::Query output;
#if HAVE_SFSEXP
status = _notmuch_sexp_string_to_xapian_query (notmuch, query_string.c_str (), output);
if (status) {
throw Xapian::QueryParserError ("error parsing " + query_string);
}
#else
throw Xapian::QueryParserError ("sexp query parser not available");
#endif
return output;
}

41
lib/sexp-fp.h Normal file
View file

@ -0,0 +1,41 @@
/* sexp-fp.h - sexp field processor glue
*
* This file is part of notmuch.
*
* Copyright © 2022 David Bremner
*
* 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/ .
*
* Author: David Bremner <david@tethera.net>
*/
#ifndef NOTMUCH_SEXP_FP_H
#define NOTMUCH_SEXP_FP_H
#include <xapian.h>
#include "notmuch.h"
class SexpFieldProcessor : public Xapian::FieldProcessor {
protected:
notmuch_database_t *notmuch;
public:
SexpFieldProcessor (notmuch_database_t *notmuch_) : notmuch (notmuch_)
{
};
Xapian::Query operator() (const std::string & query_string);
};
#endif /* NOTMUCH_SEXP_FP_H */

View file

@ -241,6 +241,26 @@ maildir_mktemp (const void *ctx, const char *maildir, bool world_readable, char
return fd;
}
static bool
write_buf (const char *buf, int fdout, ssize_t remain)
{
const char *p = buf;
do {
ssize_t written = write (fdout, p, remain);
if (written < 0 && errno == EINTR)
continue;
if (written <= 0) {
fprintf (stderr, "Error: writing to temporary file: %s",
strerror (errno));
return false;
}
p += written;
remain -= written;
} while (remain > 0);
return true;
}
/*
* Copy fdin to fdout, return true on success, and false on errors and
* empty input.
@ -249,11 +269,13 @@ static bool
copy_fd (int fdout, int fdin)
{
bool empty = true;
bool first = true;
const char *header = "X-Envelope-From: ";
while (! interrupted) {
ssize_t remain;
char buf[4096];
char *p;
const char *p = buf;
remain = read (fdin, buf, sizeof (buf));
if (remain == 0)
@ -266,20 +288,18 @@ copy_fd (int fdout, int fdin)
return false;
}
p = buf;
do {
ssize_t written = write (fdout, p, remain);
if (written < 0 && errno == EINTR)
continue;
if (written <= 0) {
fprintf (stderr, "Error: writing to temporary file: %s",
strerror (errno));
if (first && remain >= 5 && 0 == strncmp (buf, "From ", 5)) {
if (! write_buf (header, fdout, strlen (header)))
return false;
}
p += written;
remain -= written;
empty = false;
} while (remain > 0);
p += 5;
remain -= 5;
}
first = false;
if (! write_buf (p, fdout, remain))
return false;
empty = false;
}
return (! interrupted && ! empty);

21
performance-test/T06-emacs.sh Executable file
View file

@ -0,0 +1,21 @@
#!/usr/bin/env bash
test_description='emacs operations'
. $(dirname "$0")/perf-test-lib.sh || exit 1
. $NOTMUCH_SRCDIR/test/test-lib-emacs.sh || exit 1
test_require_emacs
time_start
print_emacs_header
MSGS=$(notmuch search --output=messages "*" | shuf -n 50 | awk '{printf " \"%s\"",$1}')
time_emacs "tag messages" \
"(dolist (msg (list $MSGS))
(notmuch-tag msg (list \"+test\"))
(notmuch-tag msg (list \"-test\"))))"
time_done

View file

@ -41,6 +41,8 @@ done
# Ensure NOTMUCH_SRCDIR and NOTMUCH_BUILDDIR are set.
. $(dirname "$0")/../test/export-dirs.sh || exit 1
. "$NOTMUCH_SRCDIR/test/test-vars.sh" || exit 1
# Where to run the tests
TEST_DIRECTORY=$NOTMUCH_BUILDDIR/performance-test
@ -208,6 +210,11 @@ print_header ()
printf "\t\t\tWall(s)\tUsr(s)\tSys(s)\tRes(K)\tIn/Out(512B)\n"
}
print_emacs_header ()
{
printf "\t\t\tWall(s)\tGCs\tGC time(s)\n"
}
time_run ()
{
printf " %-22s" "$1"

View file

@ -293,6 +293,26 @@ user.primary_email=test_suite@notmuchmail.org
EOF
test_expect_equal_file EXPECTED OUTPUT
test_begin_subtest "Config list from python ($config)"
test_python <<EOF > OUTPUT
from notmuch2 import Database
db=Database(config=Database.CONFIG.SEARCH)
for key in list(db.config):
print(key)
EOF
cat <<EOF > EXPECTED
database.autocommit
database.backup_dir
database.hook_dir
database.mail_root
database.path
maildir.synchronize_flags
new.tags
user.name
user.other_email
user.primary_email
EOF
test_expect_equal_file EXPECTED OUTPUT
case $config in
XDG*)
test_begin_subtest "Set shadowed config value in database ($config)"

View file

@ -292,4 +292,9 @@ for code in OUT_OF_MEMORY XAPIAN_EXCEPTION ; do
test_expect_code 0 "notmuch_with_shim shim-$code insert --keep < \"$gen_msg_filename\""
done
test_begin_subtest "insert converts mboxes on delivery"
notmuch insert +unmboxed < "${TEST_DIRECTORY}"/corpora/indexing/mbox-attachment.eml
output=$(notmuch count tag:unmboxed)
test_expect_equal "${output}" 1
test_done

View file

@ -31,6 +31,13 @@ thread:XXX 2009-11-18 [1/3] Carl Worth| Jan Janak; [notmuch] What a great idea
EOF
test_expect_equal_file EXPECTED OUTPUT
test_begin_subtest "and of stemmed terms"
notmuch search --query=sexp '(and wonderful wizard)' | notmuch_search_sanitize > OUTPUT
cat <<EOF > EXPECTED
thread:XXX 2009-11-18 [1/3] Carl Worth| Jan Janak; [notmuch] What a great idea! (inbox unread)
EOF
test_expect_equal_file EXPECTED OUTPUT
test_begin_subtest "or of exact terms"
notmuch search --query=sexp '(or "php" "wizard")' | notmuch_search_sanitize > OUTPUT
cat <<EOF > EXPECTED
@ -39,6 +46,14 @@ thread:XXX 2009-11-18 [1/3] Carl Worth| Jan Janak; [notmuch] What a great idea
EOF
test_expect_equal_file EXPECTED OUTPUT
test_begin_subtest "or of exact terms via field processor"
notmuch search 'sexp:"(or ""php"" ""wizard"")"' | notmuch_search_sanitize > OUTPUT
cat <<EOF > EXPECTED
thread:XXX 2010-12-29 [1/1] François Boulogne; [aur-general] Guidelines: cp, mkdir vs install (inbox unread)
thread:XXX 2009-11-18 [1/3] Carl Worth| Jan Janak; [notmuch] What a great idea! (inbox unread)
EOF
test_expect_equal_file EXPECTED OUTPUT
test_begin_subtest "single term in body"
notmuch search --query=sexp 'wizard' | notmuch_search_sanitize>OUTPUT
cat <<EOF > EXPECTED
@ -707,6 +722,11 @@ notmuch search property:foo=bar > EXPECTED
notmuch search --query=sexp '(property (rx foo=.*))' > OUTPUT
test_expect_equal_file EXPECTED OUTPUT
test_begin_subtest "regexp 'property' search via field processor"
notmuch search property:foo=bar > EXPECTED
notmuch search 'sexp:"(property (rx foo=.*))"' > OUTPUT
test_expect_equal_file EXPECTED OUTPUT
test_begin_subtest "anchored 'tag' search"
notmuch search tag:signed > EXPECTED
notmuch search --query=sexp '(tag (rx ^si))' > OUTPUT
@ -743,6 +763,13 @@ thread:XXX 2009-11-18 [7/7] Lars Kellogg-Stedman, Mikhail Gusarov, Keith Packa
EOF
test_expect_equal_file EXPECTED OUTPUT
test_begin_subtest "Compound subquery via field processor"
notmuch search 'sexp:"(thread (of (from keithp) (subject Maildir)))"' | notmuch_search_sanitize > OUTPUT
cat<<EOF > 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)
EOF
test_expect_equal_file EXPECTED OUTPUT
test_begin_subtest "empty subquery"
notmuch search --query=sexp '(thread (of))' 1>OUTPUT 2>&1
notmuch search '*' > EXPECTED
@ -969,6 +996,11 @@ grep -Ril List-Id ${MAIL_DIR} | sort | notmuch_dir_sanitize > EXPECTED
notmuch search --output=files --query=sexp '(List *)' | sort | notmuch_dir_sanitize > OUTPUT
test_expect_equal_file EXPECTED OUTPUT
test_begin_subtest "wildcard search for user header via field processor"
grep -Ril List-Id ${MAIL_DIR} | sort | notmuch_dir_sanitize > EXPECTED
notmuch search --output=files 'sexp:"(List *)"' | sort | notmuch_dir_sanitize > OUTPUT
test_expect_equal_file EXPECTED OUTPUT
test_begin_subtest "wildcard search for user header 2"
grep -Ril List-Id ${MAIL_DIR} | sort | notmuch_dir_sanitize > EXPECTED
notmuch search --output=files --query=sexp '(List (starts-with not))' | sort | notmuch_dir_sanitize > OUTPUT

View file

@ -435,7 +435,7 @@ test_expect_equal_file EXPECTED OUTPUT
test_begin_subtest "search for non-existent message prints nothing"
notmuch search "no-message-matches-this" > OUTPUT
echo -n >EXPECTED
: >EXPECTED
test_expect_equal_file EXPECTED OUTPUT
test_begin_subtest "search --format=json for non-existent message prints proper empty json"

View file

@ -683,7 +683,7 @@ test_expect_equal_json "$(cat OUTPUT)" "$(cat EXPECTED)"
test_begin_subtest "'notmuch show --part' does not corrupt a part with CRLF pair"
notmuch show --format=raw --part=3 id:base64-part-with-crlf > crlf.out
echo -n -e "\xEF\x0D\x0A" > crlf.expected
printf "\xEF\x0D\x0A" > crlf.expected
test_expect_equal_file crlf.out crlf.expected

View file

@ -130,75 +130,6 @@ test_emacs '(notmuch-search "tag:inbox")
(test-output)'
test_expect_equal_file $EXPECTED/notmuch-show-thread-maildir-storage OUTPUT
test_begin_subtest "Add tag from search view"
os_x_darwin_thread=$(notmuch search --output=threads id:ddd65cda0911171950o4eea4389v86de9525e46052d3@mail.gmail.com)
test_emacs "(notmuch-search \"$os_x_darwin_thread\")
(notmuch-test-wait)
(execute-kbd-macro \"+tag-from-search-view\")"
output=$(notmuch search $os_x_darwin_thread | notmuch_search_sanitize)
test_expect_equal "$output" "thread:XXX 2009-11-18 [4/4] Jjgod Jiang, Alexander Botero-Lowry; [notmuch] Mac OS X/Darwin compatibility issues (inbox tag-from-search-view unread)"
test_begin_subtest "Remove tag from search view"
test_emacs "(notmuch-search \"$os_x_darwin_thread\")
(notmuch-test-wait)
(execute-kbd-macro \"-tag-from-search-view\")"
output=$(notmuch search $os_x_darwin_thread | notmuch_search_sanitize)
test_expect_equal "$output" "thread:XXX 2009-11-18 [4/4] Jjgod Jiang, Alexander Botero-Lowry; [notmuch] Mac OS X/Darwin compatibility issues (inbox unread)"
test_begin_subtest "Add tag (large query)"
# We use a long query to force us into batch mode and use a funny tag
# that requires escaping for batch tagging.
test_emacs "(notmuch-tag (concat \"$os_x_darwin_thread\" \" or \" (mapconcat #'identity (make-list notmuch-tag-argument-limit \"x\") \"-\")) (list \"+tag-from-%-large-query\"))"
output=$(notmuch search $os_x_darwin_thread | notmuch_search_sanitize)
test_expect_equal "$output" "thread:XXX 2009-11-18 [4/4] Jjgod Jiang, Alexander Botero-Lowry; [notmuch] Mac OS X/Darwin compatibility issues (inbox tag-from-%-large-query unread)"
notmuch tag -tag-from-%-large-query $os_x_darwin_thread
test_begin_subtest "notmuch-show: add single tag to single message"
test_emacs "(notmuch-show \"$os_x_darwin_thread\")
(execute-kbd-macro \"+tag-from-show-view\")"
output=$(notmuch search $os_x_darwin_thread | notmuch_search_sanitize)
test_expect_equal "$output" "thread:XXX 2009-11-18 [4/4] Jjgod Jiang, Alexander Botero-Lowry; [notmuch] Mac OS X/Darwin compatibility issues (inbox tag-from-show-view unread)"
test_begin_subtest "notmuch-show: remove single tag from single message"
test_emacs "(notmuch-show \"$os_x_darwin_thread\")
(execute-kbd-macro \"-tag-from-show-view\")"
output=$(notmuch search $os_x_darwin_thread | notmuch_search_sanitize)
test_expect_equal "$output" "thread:XXX 2009-11-18 [4/4] Jjgod Jiang, Alexander Botero-Lowry; [notmuch] Mac OS X/Darwin compatibility issues (inbox unread)"
test_begin_subtest "notmuch-show: add multiple tags to single message"
test_emacs "(notmuch-show \"$os_x_darwin_thread\")
(execute-kbd-macro \"+tag1-from-show-view +tag2-from-show-view\")"
output=$(notmuch search $os_x_darwin_thread | notmuch_search_sanitize)
test_expect_equal "$output" "thread:XXX 2009-11-18 [4/4] Jjgod Jiang, Alexander Botero-Lowry; [notmuch] Mac OS X/Darwin compatibility issues (inbox tag1-from-show-view tag2-from-show-view unread)"
test_begin_subtest "notmuch-show: remove multiple tags from single message"
test_emacs "(notmuch-show \"$os_x_darwin_thread\")
(execute-kbd-macro \"-tag1-from-show-view -tag2-from-show-view\")"
output=$(notmuch search $os_x_darwin_thread | notmuch_search_sanitize)
test_expect_equal "$output" "thread:XXX 2009-11-18 [4/4] Jjgod Jiang, Alexander Botero-Lowry; [notmuch] Mac OS X/Darwin compatibility issues (inbox unread)"
test_begin_subtest "notmuch-show: before-tag-hook is run, variables are defined"
output=$(test_emacs '(let ((notmuch-test-tag-hook-output nil)
(notmuch-before-tag-hook (function notmuch-test-tag-hook)))
(notmuch-show "id:ddd65cda0911171950o4eea4389v86de9525e46052d3@mail.gmail.com")
(execute-kbd-macro "+activate-hook\n")
(execute-kbd-macro "-activate-hook\n")
notmuch-test-tag-hook-output)')
test_expect_equal "$output" \
'(("id:ddd65cda0911171950o4eea4389v86de9525e46052d3@mail.gmail.com" "-activate-hook")
("id:ddd65cda0911171950o4eea4389v86de9525e46052d3@mail.gmail.com" "+activate-hook"))'
test_begin_subtest "notmuch-show: after-tag-hook is run, variables are defined"
output=$(test_emacs '(let ((notmuch-test-tag-hook-output nil)
(notmuch-after-tag-hook (function notmuch-test-tag-hook)))
(notmuch-show "id:ddd65cda0911171950o4eea4389v86de9525e46052d3@mail.gmail.com")
(execute-kbd-macro "+activate-hook\n")
(execute-kbd-macro "-activate-hook\n")
notmuch-test-tag-hook-output)')
test_expect_equal "$output" \
'(("id:ddd65cda0911171950o4eea4389v86de9525e46052d3@mail.gmail.com" "-activate-hook")
("id:ddd65cda0911171950o4eea4389v86de9525e46052d3@mail.gmail.com" "+activate-hook"))'
test_begin_subtest "Message with .. in Message-Id:"
add_message [id]=123..456@example '[subject]="Message with .. in Message-Id"'
test_emacs '(notmuch-search "id:\"123..456@example\"")
@ -1133,30 +1064,6 @@ This is a warning (see *Notmuch errors* for more details)
This is a warning
This is another warning"
test_begin_subtest "Search thread tag operations are race-free"
add_message '[subject]="Search race test"'
gen_msg_id_1=$gen_msg_id
generate_message '[in-reply-to]="<'$gen_msg_id_1'>"' \
'[references]="<'$gen_msg_id_1'>"' \
'[subject]="Search race test two"'
test_emacs '(notmuch-search "subject:\"search race test\"")
(notmuch-test-wait)
(notmuch-poll)
(execute-kbd-macro "+search-thread-race-tag")'
output=$(notmuch search --output=messages 'tag:search-thread-race-tag')
test_expect_equal "$output" "id:$gen_msg_id_1"
test_begin_subtest "Search global tag operations are race-free"
generate_message '[in-reply-to]="<'$gen_msg_id_1'>"' \
'[references]="<'$gen_msg_id_1'>"' \
'[subject]="Re: Search race test"'
test_emacs '(notmuch-search "subject:\"search race test\" -subject:two")
(notmuch-test-wait)
(notmuch-poll)
(execute-kbd-macro "*+search-global-race-tag")'
output=$(notmuch search --output=messages 'tag:search-global-race-tag')
test_expect_equal "$output" "id:$gen_msg_id_1"
test_begin_subtest "Term escaping"
output=$(test_emacs "(mapcar 'notmuch-escape-boolean-term (list
\"\"

166
test/T315-emacs-tagging.sh Executable file
View file

@ -0,0 +1,166 @@
#!/usr/bin/env bash
test_description="emacs interface"
. $(dirname "$0")/test-lib.sh || exit 1
. $NOTMUCH_SRCDIR/test/test-lib-emacs.sh || exit 1
EXPECTED=$NOTMUCH_SRCDIR/test/emacs.expected-output
test_require_emacs
add_email_corpus
test_begin_subtest "Add tag from search view"
os_x_darwin_thread=$(notmuch search --output=threads id:ddd65cda0911171950o4eea4389v86de9525e46052d3@mail.gmail.com)
test_emacs "(notmuch-search \"$os_x_darwin_thread\")
(notmuch-test-wait)
(execute-kbd-macro \"+tag-from-search-view\")"
output=$(notmuch search $os_x_darwin_thread | notmuch_search_sanitize)
test_expect_equal "$output" "thread:XXX 2009-11-18 [4/4] Jjgod Jiang, Alexander Botero-Lowry; [notmuch] Mac OS X/Darwin compatibility issues (inbox tag-from-search-view unread)"
test_begin_subtest "Remove tag from search view"
test_emacs "(notmuch-search \"$os_x_darwin_thread\")
(notmuch-test-wait)
(execute-kbd-macro \"-tag-from-search-view\")"
output=$(notmuch search $os_x_darwin_thread | notmuch_search_sanitize)
test_expect_equal "$output" "thread:XXX 2009-11-18 [4/4] Jjgod Jiang, Alexander Botero-Lowry; [notmuch] Mac OS X/Darwin compatibility issues (inbox unread)"
test_begin_subtest "Add tag (large query)"
# We use a long query to force us into batch mode and use a funny tag
# that requires escaping for batch tagging.
test_emacs "(notmuch-tag (concat \"$os_x_darwin_thread\" \" or \" (mapconcat #'identity (make-list notmuch-tag-argument-limit \"x\") \"-\")) (list \"+tag-from-%-large-query\"))"
output=$(notmuch search $os_x_darwin_thread | notmuch_search_sanitize)
test_expect_equal "$output" "thread:XXX 2009-11-18 [4/4] Jjgod Jiang, Alexander Botero-Lowry; [notmuch] Mac OS X/Darwin compatibility issues (inbox tag-from-%-large-query unread)"
notmuch tag -tag-from-%-large-query $os_x_darwin_thread
test_begin_subtest "notmuch-show: add single tag to single message"
test_emacs "(notmuch-show \"$os_x_darwin_thread\")
(execute-kbd-macro \"+tag-from-show-view\")"
output=$(notmuch search $os_x_darwin_thread | notmuch_search_sanitize)
test_expect_equal "$output" "thread:XXX 2009-11-18 [4/4] Jjgod Jiang, Alexander Botero-Lowry; [notmuch] Mac OS X/Darwin compatibility issues (inbox tag-from-show-view unread)"
test_begin_subtest "notmuch-show: remove single tag from single message"
test_emacs "(notmuch-show \"$os_x_darwin_thread\")
(execute-kbd-macro \"-tag-from-show-view\")"
output=$(notmuch search $os_x_darwin_thread | notmuch_search_sanitize)
test_expect_equal "$output" "thread:XXX 2009-11-18 [4/4] Jjgod Jiang, Alexander Botero-Lowry; [notmuch] Mac OS X/Darwin compatibility issues (inbox unread)"
test_begin_subtest "notmuch-show: add multiple tags to single message"
test_emacs "(notmuch-show \"$os_x_darwin_thread\")
(execute-kbd-macro \"+tag1-from-show-view +tag2-from-show-view\")"
output=$(notmuch search $os_x_darwin_thread | notmuch_search_sanitize)
test_expect_equal "$output" "thread:XXX 2009-11-18 [4/4] Jjgod Jiang, Alexander Botero-Lowry; [notmuch] Mac OS X/Darwin compatibility issues (inbox tag1-from-show-view tag2-from-show-view unread)"
test_begin_subtest "notmuch-show: remove multiple tags from single message"
test_emacs "(notmuch-show \"$os_x_darwin_thread\")
(execute-kbd-macro \"-tag1-from-show-view -tag2-from-show-view\")"
output=$(notmuch search $os_x_darwin_thread | notmuch_search_sanitize)
test_expect_equal "$output" "thread:XXX 2009-11-18 [4/4] Jjgod Jiang, Alexander Botero-Lowry; [notmuch] Mac OS X/Darwin compatibility issues (inbox unread)"
test_begin_subtest "notmuch-show: before-tag-hook is run, variables are defined"
output=$(test_emacs '(let ((notmuch-test-tag-hook-output nil)
(notmuch-before-tag-hook (function notmuch-test-tag-hook)))
(notmuch-show "id:ddd65cda0911171950o4eea4389v86de9525e46052d3@mail.gmail.com")
(execute-kbd-macro "+activate-hook\n")
(execute-kbd-macro "-activate-hook\n")
notmuch-test-tag-hook-output)')
test_expect_equal "$output" \
'(("id:ddd65cda0911171950o4eea4389v86de9525e46052d3@mail.gmail.com" "-activate-hook")
("id:ddd65cda0911171950o4eea4389v86de9525e46052d3@mail.gmail.com" "+activate-hook"))'
test_begin_subtest "notmuch-show: after-tag-hook is run, variables are defined"
output=$(test_emacs '(let ((notmuch-test-tag-hook-output nil)
(notmuch-after-tag-hook (function notmuch-test-tag-hook)))
(notmuch-show "id:ddd65cda0911171950o4eea4389v86de9525e46052d3@mail.gmail.com")
(execute-kbd-macro "+activate-hook\n")
(execute-kbd-macro "-activate-hook\n")
notmuch-test-tag-hook-output)')
test_expect_equal "$output" \
'(("id:ddd65cda0911171950o4eea4389v86de9525e46052d3@mail.gmail.com" "-activate-hook")
("id:ddd65cda0911171950o4eea4389v86de9525e46052d3@mail.gmail.com" "+activate-hook"))'
test_begin_subtest "Search thread tag operations are race-free"
add_message '[subject]="Search race test"'
gen_msg_id_1=$gen_msg_id
generate_message '[in-reply-to]="<'$gen_msg_id_1'>"' \
'[references]="<'$gen_msg_id_1'>"' \
'[subject]="Search race test two"'
test_emacs '(notmuch-search "subject:\"search race test\"")
(notmuch-test-wait)
(notmuch-poll)
(execute-kbd-macro "+search-thread-race-tag")'
output=$(notmuch search --output=messages 'tag:search-thread-race-tag')
test_expect_equal "$output" "id:$gen_msg_id_1"
test_begin_subtest "Search global tag operations are race-free"
generate_message '[in-reply-to]="<'$gen_msg_id_1'>"' \
'[references]="<'$gen_msg_id_1'>"' \
'[subject]="Re: Search race test"'
test_emacs '(notmuch-search "subject:\"search race test\" -subject:two")
(notmuch-test-wait)
(notmuch-poll)
(execute-kbd-macro "*+search-global-race-tag")'
output=$(notmuch search --output=messages 'tag:search-global-race-tag')
test_expect_equal "$output" "id:$gen_msg_id_1"
test_begin_subtest "undo with empty history is an error"
test_emacs "(let ((notmuch-tag-history nil))
(test-log-error
(notmuch-tag-undo)))
"
cat <<EOF > EXPECTED
(error no further notmuch undo information)
EOF
test_expect_equal_file EXPECTED MESSAGES
for mode in search show tree unthreaded; do
test_begin_subtest "undo tagging in $mode mode"
test_emacs "(let ((notmuch-tag-history nil))
(notmuch-$mode \"$os_x_darwin_thread\")
(notmuch-test-wait)
(execute-kbd-macro \"+tag-to-be-undone-$mode\")
(notmuch-tag-undo)
(notmuch-test-wait))"
count=$(notmuch count "tag:tag-to-be-undone-$mode")
test_expect_equal "$count" "0"
test_begin_subtest "undo tagging in $mode mode (multiple operations)"
test_emacs "(let ((notmuch-tag-history nil))
(notmuch-$mode \"$os_x_darwin_thread\")
(notmuch-test-wait)
(execute-kbd-macro \"+one-$mode\")
(execute-kbd-macro \"+two-$mode\")
(notmuch-tag-undo)
(notmuch-test-wait)
(execute-kbd-macro \"+three-$mode\"))"
output=$(notmuch search $os_x_darwin_thread | notmuch_search_sanitize)
notmuch tag "-one-$mode" "-three-$mode" $os_x_darwin_thread
test_expect_equal "$output" "thread:XXX 2009-11-18 [4/4] Jjgod Jiang, Alexander Botero-Lowry; [notmuch] Mac OS X/Darwin compatibility issues (inbox one-$mode three-$mode unread)"
test_begin_subtest "undo tagging in $mode mode (multiple undo)"
test_emacs "(let ((notmuch-tag-history nil))
(notmuch-$mode \"$os_x_darwin_thread\")
(notmuch-test-wait)
(execute-kbd-macro \"+one-$mode\")
(execute-kbd-macro \"+two-$mode\")
(notmuch-tag-undo)
(notmuch-test-wait)
(notmuch-tag-undo)
(notmuch-test-wait)
(execute-kbd-macro \"+three-$mode\"))"
output=$(notmuch search $os_x_darwin_thread | notmuch_search_sanitize)
notmuch tag "-one-$mode" "-three-$mode" $os_x_darwin_thread
test_expect_equal "$output" "thread:XXX 2009-11-18 [4/4] Jjgod Jiang, Alexander Botero-Lowry; [notmuch] Mac OS X/Darwin compatibility issues (inbox three-$mode unread)"
test_begin_subtest "undo tagging in $mode mode (via binding)"
test_emacs "(let ((notmuch-tag-history nil))
(notmuch-$mode \"$os_x_darwin_thread\")
(notmuch-test-wait)
(execute-kbd-macro \"+tag-to-be-undone-$mode\")
(execute-kbd-macro (kbd \"C-x u\"))
(notmuch-test-wait))"
count=$(notmuch count "tag:tag-to-be-undone-$mode")
test_expect_equal "$count" "0"
done
test_done

View file

@ -35,6 +35,11 @@ EOF
test_expect_equal_file EXPECTED OUTPUT
test_begin_subtest "signature verification (notmuch CLI)"
if [ $NOTMUCH_GMIME_EMITS_ANGLE_BRACKETS == 1 ]; then
EXPECTED_EMAIL_ADDR='<test_suite@notmuchmail.org>'
else
EXPECTED_EMAIL_ADDR='test_suite@notmuchmail.org'
fi
output=$(notmuch show --format=json --verify subject:"test signed message 001" \
| notmuch_json_show_sanitize \
| sed -e 's|"created": [-1234567890]*|"created": 946728000|g' \
@ -46,7 +51,7 @@ expected='[[[{"id": "XXXXX",
"timestamp": 946728000,
"date_relative": "2000-01-01",
"tags": ["inbox","signed"],
"crypto": {"signed": {"status": [{"fingerprint": "'$FINGERPRINT'", "status": "good","userid": "CN=Notmuch Test Suite", "email": "<test_suite@notmuchmail.org>", "expires": 424242424, "created": 946728000}]}},
"crypto": {"signed": {"status": [{"fingerprint": "'$FINGERPRINT'", "status": "good","userid": "CN=Notmuch Test Suite", "email": "'$EXPECTED_EMAIL_ADDR'", "expires": 424242424, "created": 946728000}]}},
"headers": {"Subject": "test signed message 001",
"From": "Notmuch Test Suite <test_suite@notmuchmail.org>",
"To": "test_suite@notmuchmail.org",
@ -55,7 +60,7 @@ expected='[[[{"id": "XXXXX",
"sigstatus": [{"fingerprint": "'$FINGERPRINT'",
"status": "good",
"userid": "CN=Notmuch Test Suite",
"email": "<test_suite@notmuchmail.org>",
"email": "'$EXPECTED_EMAIL_ADDR'",
"expires": 424242424,
"created": 946728000}],
"content-type": "multipart/signed",

View file

@ -71,8 +71,8 @@ if test_require_external_prereq gdb; then
# Check output against golden output
outcount=$(cat outcount)
echo -n > searchall
echo -n > expectall
: > searchall
: > expectall
for ((i = 0; i < $outcount; i++)); do
if ! cmp -s search.$i expected; then
# Find the range of interruptions that match this output

View file

@ -65,6 +65,31 @@ thread:XXX 2001-01-05 [1/1] Notmuch Test Suite; - (inbox unread)
EOF
test_expect_equal_file EXPECTED OUTPUT
test_begin_subtest "bracketed subject search (with dquotes)"
notmuch search subject:notmuch and subject:show > EXPECTED
notmuch search 'subject:"(show notmuch)"' > OUTPUT
test_expect_equal_file_nonempty EXPECTED OUTPUT
test_begin_subtest "bracketed subject search (with dquotes and operator 'or')"
notmuch search subject:notmuch or subject:show > EXPECTED
notmuch search 'subject:"(notmuch or show)"' > OUTPUT
test_expect_equal_file_nonempty EXPECTED OUTPUT
test_begin_subtest "bracketed subject search (with dquotes and operator 'and')"
notmuch search subject:notmuch and subject:show > EXPECTED
notmuch search 'subject:"(notmuch and show)"' > OUTPUT
test_expect_equal_file_nonempty EXPECTED OUTPUT
test_begin_subtest "bracketed subject search (with phrase, operator 'or')"
notmuch search 'subject:"mailing list"' or subject:FreeBSD > EXPECTED
notmuch search 'subject:"(""mailing list"" or FreeBSD)"' > OUTPUT
test_expect_equal_file_nonempty EXPECTED OUTPUT
test_begin_subtest "bracketed subject search (with phrase, operator 'and')"
notmuch search search 'subject:"notmuch show"' and subject:commands > EXPECTED
notmuch search 'subject:"(""notmuch show"" and commands)"' > OUTPUT
test_expect_equal_file_nonempty EXPECTED OUTPUT
test_begin_subtest "xapian wildcard search for from:"
notmuch search --output=messages 'from:cwo*' > OUTPUT
test_expect_equal_file cworth.msg-ids OUTPUT

View file

@ -0,0 +1,83 @@
From david@tethera.net Sat Feb 5 09:19:10 2022
From: David Bremner <david@tethera.net>
To: David Bremner <david@tethera.net>
Subject: Re: [RFC PATCH v2 12/12] emacs: whitespace cleanup for keybindings
Date: Sat, 05 Feb 2022 10:19:09 -0400
Message-ID: <87k0e9o0pu.fsf@tethera.net>
MIME-Version: 1.0
Content-Type: multipart/mixed; boundary="=-=-="
--=-=-=
Content-Type: text/plain
Content-Disposition: inline
I figured out the race condition in the tests. The previous test was
still running when the failing test started, the joys of using a shared
emacs for running all of the tests in one file.
The attached diff is split into the the commits that introduce the tests
in question in my working series, but you should be able to just apply
it on top of the posted series if you want.
--=-=-=
Content-Type: text/x-diff
Content-Disposition: inline; filename=0001-test-fixups.patch
From fc88cba7f1f37b9cf3b296eace2422dd0e173502 Mon Sep 17 00:00:00 2001
From: David Bremner <david@tethera.net>
Date: Thu, 3 Feb 2022 21:05:05 -0400
Subject: [PATCH] test fixups
---
test/T315-emacs-tagging.sh | 9 ++++-----
1 file changed, 4 insertions(+), 5 deletions(-)
diff --git a/test/T315-emacs-tagging.sh b/test/T315-emacs-tagging.sh
index c9e3e53a..c26413ce 100755
--- a/test/T315-emacs-tagging.sh
+++ b/test/T315-emacs-tagging.sh
@@ -119,7 +119,8 @@ for mode in search show tree unthreaded; do
(notmuch-$mode \"$os_x_darwin_thread\")
(notmuch-test-wait)
(execute-kbd-macro \"+tag-to-be-undone-$mode\")
- (notmuch-tag-undo))"
+ (notmuch-tag-undo)
+ (notmuch-test-wait))"
count=$(notmuch count "tag:tag-to-be-undone-$mode")
test_expect_equal "$count" "0"
@@ -128,9 +129,7 @@ for mode in search show tree unthreaded; do
(notmuch-$mode \"$os_x_darwin_thread\")
(notmuch-test-wait)
(execute-kbd-macro \"+one-$mode\")
- (notmuch-test-wait)
(execute-kbd-macro \"+two-$mode\")
- (notmuch-test-wait)
(notmuch-tag-undo)
(notmuch-test-wait)
(execute-kbd-macro \"+three-$mode\"))"
@@ -143,7 +142,6 @@ for mode in search show tree unthreaded; do
(notmuch-$mode \"$os_x_darwin_thread\")
(notmuch-test-wait)
(execute-kbd-macro \"+one-$mode\")
- (notmuch-test-wait)
(execute-kbd-macro \"+two-$mode\")
(notmuch-tag-undo)
(notmuch-test-wait)
@@ -159,7 +157,8 @@ for mode in search show tree unthreaded; do
(notmuch-$mode \"$os_x_darwin_thread\")
(notmuch-test-wait)
(execute-kbd-macro \"+tag-to-be-undone-$mode\")
- (execute-kbd-macro (kbd \"C-x u\")))"
+ (execute-kbd-macro (kbd \"C-x u\"))
+ (notmuch-test-wait))"
count=$(notmuch count "tag:tag-to-be-undone-$mode")
test_expect_equal "$count" "0"
done
--
2.30.2
--=-=-=--

View file

@ -29,6 +29,20 @@ if [[ -z "$NOTMUCH_SRCDIR" ]] || [[ -z "$NOTMUCH_BUILDDIR" ]]; then
exit 1
fi
# Explicitly require external prerequisite. Useful when binary is
# 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
eval "$binary"
else
true
fi
}
backup_database () {
test_name=$(basename $0 .sh)
rm -rf $TMP_DIRECTORY/notmuch-dir-backup."$test_name"

View file

@ -207,4 +207,12 @@ test_emacs () {
${TEST_EMACSCLIENT} --socket-name="$EMACS_SERVER" --eval "(notmuch-test-progn $*)"
}
time_emacs () {
rm -f MESSAGES
printf "%s" "$1"
shift
test_emacs "(test-time $*)" > emacs.out
tail -n 1 MESSAGES
}
emacs_generate_script

View file

@ -186,6 +186,11 @@ running, quit if it terminated."
(t (message "%s" err)))
(with-current-buffer "*Messages*" (test-output "MESSAGES"))))
(defmacro test-time (&rest body)
`(let ((results (mapcar (lambda (x) (/ x 5.0)) (benchmark-run 5 ,@body))))
(message "\t\t%0.2f\t%0.2f\t%0.2f" (nth 0 results) (nth 1 results) (nth 2 results))
(with-current-buffer "*Messages*" (test-output "MESSAGES"))))
;; For historical reasons, we hide deleted tags by default in the test
;; suite
(setq notmuch-tag-deleted-formats

View file

@ -64,55 +64,7 @@ exec 6>&1 7>&2
BASH_XTRACEFD=7
export PS4='+(${BASH_SOURCE}:${LINENO}): ${FUNCNAME[0]:+${FUNCNAME[0]}(): }'
# Keep the original TERM for say_color and test_emacs
ORIGINAL_TERM=$TERM
# Set SMART_TERM to vt100 for known dumb/unknown terminal.
# Otherwise use whatever TERM is currently used so that
# users' actual TERM environments are being used in tests.
case ${TERM-} in
'' | dumb | unknown )
SMART_TERM=vt100 ;;
*)
SMART_TERM=$TERM ;;
esac
# For repeatability, reset the environment to known value.
LANG=C
LC_ALL=C
PAGER=cat
TZ=UTC
TERM=dumb
export LANG LC_ALL PAGER TERM TZ
GIT_TEST_CMP=${GIT_TEST_CMP:-diff -u}
if [[ ( -n "$TEST_EMACS" && -z "$TEST_EMACSCLIENT" ) || \
( -z "$TEST_EMACS" && -n "$TEST_EMACSCLIENT" ) ]]; then
echo "error: must specify both or neither of TEST_EMACS and TEST_EMACSCLIENT" >&2
exit 1
fi
TEST_EMACS=${TEST_EMACS:-${EMACS:-emacs}}
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
unset CDPATH
unset GREP_OPTIONS
# For lib/open.cc:_load_key_file
unset XDG_CONFIG_HOME
# For emacsclient
unset ALTERNATE_EDITOR
# for reproducibility
unset EMAIL
unset NAME
. "$NOTMUCH_SRCDIR/test/test-vars.sh" || exit 1
add_gnupg_home () {
[ -e "${GNUPGHOME}/gpg.conf" ] && return
@ -330,11 +282,6 @@ die () {
exit 1
}
GIT_EXIT_OK=
# Note: TEST_TMPDIR *NOT* exported!
TEST_TMPDIR=$(mktemp -d "${TMPDIR:-/tmp}/notmuch-test-$$.XXXXXX")
# Put GNUPGHOME in TMPDIR to avoid problems with long paths.
export GNUPGHOME="${TEST_TMPDIR}/gnupg"
trap 'trap_exit' EXIT
trap 'trap_signal' HUP INT TERM
@ -656,20 +603,6 @@ $binary () {
fi
}
# Explicitly require external prerequisite. Useful when binary is
# 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
eval "$binary"
else
true
fi
}
# You are not expected to call test_ok_ and test_failure_ directly, use
# the text_expect_* functions instead.

62
test/test-vars.sh Normal file
View file

@ -0,0 +1,62 @@
# Common variable settings for (correctness) tests and performance
# tests.
# Keep the original TERM for say_color and test_emacs
ORIGINAL_TERM=$TERM
# Set SMART_TERM to vt100 for known dumb/unknown terminal.
# Otherwise use whatever TERM is currently used so that
# users' actual TERM environments are being used in tests.
case ${TERM-} in
'' | dumb | unknown )
SMART_TERM=vt100 ;;
*)
SMART_TERM=$TERM ;;
esac
# For repeatability, reset the environment to known value.
LANG=C
LC_ALL=C
PAGER=cat
TZ=UTC
TERM=dumb
export LANG LC_ALL PAGER TERM TZ
GIT_TEST_CMP=${GIT_TEST_CMP:-diff -u}
if [[ ( -n "$TEST_EMACS" && -z "$TEST_EMACSCLIENT" ) || \
( -z "$TEST_EMACS" && -n "$TEST_EMACSCLIENT" ) ]]; then
echo "error: must specify both or neither of TEST_EMACS and TEST_EMACSCLIENT" >&2
exit 1
fi
TEST_EMACS=${TEST_EMACS:-${EMACS:-emacs}}
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
unset CDPATH
unset GREP_OPTIONS
# For lib/open.cc:_load_key_file
unset XDG_CONFIG_HOME
# for lib/open.cc:_choose_database_path
unset XDG_DATA_HOME
unset MAILDIR
# For emacsclient
unset ALTERNATE_EDITOR
# for reproducibility
unset EMAIL
unset NAME
GIT_EXIT_OK=
# Note: TEST_TMPDIR *NOT* exported!
TEST_TMPDIR=$(mktemp -d "${TMPDIR:-/tmp}/notmuch-test-$$.XXXXXX")
# Put GNUPGHOME in TMPDIR to avoid problems with long paths.
export GNUPGHOME="${TEST_TMPDIR}/gnupg"

View file

@ -1 +1 @@
0.35
0.36