notmuch 0.35 release

-----BEGIN PGP SIGNATURE-----
 
 iQIzBAABCAAdFiEEkiyHYXwaY0SiY6fqA0U5G1WqFSEFAmIAAxEACgkQA0U5G1Wq
 FSGIdw//aytmkD3YaI0jT9Q7JjvkChMjGYzP0KeVlJrSIjNu+tkIoMSFayPzR1Zg
 f6nQ6K/c8NDJROQYFsjDBltnY2cwC1DxWMN9infr80bJhHYQlrACzeBKrGGIho44
 QceWgPW054GqYHsKozGNxZVlgKLeDImj2dX3E0scNYtVuhsyVO9x4Eb2xi6oDRze
 HvnpiKoE8zkwObK9REjwNG1n1dVmvSZ+H25akcb3siiwKFLNIwcPAYIMiSFaBsNe
 vwwJaUhZP6TnE+54QX03ssLFT5Dlm/d+YOT4EPAujRwGaLlaUsK04KHbyT/wWbgK
 wW5AUsOL8zTjQz5sg8+v90DRFlamRg8SK8piTJo+Dx5Lsjpqk5PgLG7DJ0WeTaxJ
 35Bph1/5mnUWwi2dsNeJ11rK0vamouSsatF+VFScWxesLhsUhzFFt20VfiiYXxVQ
 t4mqH/aHitbbzqy3y0vwIj+USdIfskJyncgXKjNkwEMJuh25Sg815ilnvNXMWbv+
 7byXnxNpaDaLNO1h/2KYYbz+WGFU8fsLUp/O3WtITzIZms3gddnP2GNk55/B8Jfq
 9zcNuljrUNlY3zJvXgiRj/7jCWiLevF0AMOpc1EmFmp0LLISkH+VdIVXIyLb06Y/
 bj5QDYTtXLZViZ9T6rw5QlLtNAv25ZTmbV0nOK97c3Jfwt6QRdA=
 =7kSG
 -----END PGP SIGNATURE-----

Merge tag '0.35' into debian/bullseye-backports

notmuch 0.35 release
This commit is contained in:
David Bremner 2022-02-15 20:53:12 -04:00
commit f1b2ab70c3
110 changed files with 1580 additions and 405 deletions

1
.gitignore vendored
View file

@ -16,5 +16,6 @@
/sh.config /sh.config
/sphinx.config /sphinx.config
/version.stamp /version.stamp
/bindings/python-cffi/_notmuch_config.py
TAGS TAGS
tags tags

View file

@ -50,9 +50,9 @@ DETACHED_SIG_FILE=$(TAR_FILE).asc
PV_FILE=bindings/python/notmuch/version.py PV_FILE=bindings/python/notmuch/version.py
# Smash together user's values with our extra values # Smash together user's values with our extra values
FINAL_CFLAGS = -DNOTMUCH_VERSION=$(VERSION) $(CPPFLAGS) $(CFLAGS) $(WARN_CFLAGS) $(extra_cflags) $(CONFIGURE_CFLAGS) FINAL_CFLAGS = -DNOTMUCH_VERSION=$(VERSION) $(WARN_CFLAGS) $(extra_cflags) $(CPPFLAGS) $(CONFIGURE_CFLAGS) $(CFLAGS)
FINAL_CXXFLAGS = $(CPPFLAGS) $(CXXFLAGS) $(WARN_CXXFLAGS) $(extra_cflags) $(extra_cxxflags) $(CONFIGURE_CXXFLAGS) FINAL_CXXFLAGS = $(WARN_CXXFLAGS) $(extra_cflags) $(extra_cxxflags) $(CPPFLAGS) $(CONFIGURE_CXXFLAGS) $(CXXFLAGS)
FINAL_NOTMUCH_LDFLAGS = $(LDFLAGS) -Lutil -lnotmuch_util -Llib -lnotmuch FINAL_NOTMUCH_LDFLAGS = -Lutil -lnotmuch_util -Llib -lnotmuch $(LDFLAGS)
ifeq ($(LIBDIR_IN_LDCONFIG),0) ifeq ($(LIBDIR_IN_LDCONFIG),0)
FINAL_NOTMUCH_LDFLAGS += $(RPATH_LDFLAGS) FINAL_NOTMUCH_LDFLAGS += $(RPATH_LDFLAGS)
endif endif

View file

@ -54,7 +54,6 @@ update-versions:
sed -i -e "s/^__VERSION__[[:blank:]]*=.*$$/__VERSION__ = \'${VERSION}\'/" \ sed -i -e "s/^__VERSION__[[:blank:]]*=.*$$/__VERSION__ = \'${VERSION}\'/" \
-e "s/^SOVERSION[[:blank:]]*=.*$$/SOVERSION = \'${LIBNOTMUCH_VERSION_MAJOR}\'/" \ -e "s/^SOVERSION[[:blank:]]*=.*$$/SOVERSION = \'${LIBNOTMUCH_VERSION_MAJOR}\'/" \
${PV_FILE} ${PV_FILE}
cp version.txt bindings/python-cffi
# We invoke make recursively only to force ordering of our phony # We invoke make recursively only to force ordering of our phony
# targets in the case of parallel invocation of make (-j). # targets in the case of parallel invocation of make (-j).

106
NEWS
View file

@ -1,3 +1,109 @@
Notmuch 0.35 (2022-02-06)
=========================
Library
-------
Implement the `date` and `lastmod` fields in the S-expression parser.
Ignore trailing `/` for pathnames in both query parsers.
Rename configuration option `built_with.sexpr_query` to
`built_with.sexp_queries`.
Do not assume a default mail root in split (e.g. XDG) configurations.
Fix some small memory leaks in `notmuch_database_open_with_config`.
CLI
---
Improve handling of leading/trailing punctation and space for
configuration lists.
Only ignore `.notmuch` at the top level in `notmuch new`.
Optionally show extra headers in `notmuch show`. See
`show.extra_headers` in notmuch-config(1).
Emacs
-----
Drop `C-TAB` binding in hello mode, document `backtab`.
Fix visual glitch in search mode by running `notmuch-search-hook`
lazily.
Don't add space to completion candidates, improves compatibility with
third party completion frameworks.
Make citation formating more robust against whitespace.
Use `--excludes=false` when generating the 'All tags' section.
Use cached copy of message body for `Fcc`, avoiding variant bodies for
signed and/or encrypted messages.
Add notmuch-logo.svg and use it in notmuch-hello view, replacing
the .png version.
Make header line in show buffers optional.
Add customizable names for search buffers.
Build
-----
Fix out-of-tree build for `python-cffi` bindings.
Rearrange position of {C,CXX,CPP,LD}FLAGS, prevent some clashes with
installed version of notmuch.
Ignore more configure options.
Test Suite
----------
Replace some uses of `gdb` in the test suite with `LD_PRELOAD` based
shims.
Use `--with-colons` for gpgsm, fix compatibility with newer gnupg.
Python bindings
---------------
Add `matched` property to message objects.
Users are reminded that the old python bindings in bindings/python are
deprecated; this will probably be the last major release that ships
them.
Completion
----------
Use `database.mail_root` for path completion in bash/zsh.
Notmuch 0.34.3 (2022-01-09)
===========================
Library
-------
Do not crash when presented with a .notmuch directory without a
xapian/ subdirectory.
Python Bindings (notmuch2)
--------------------------
Database constructor now searches for configuration by default. Pass
`config=Database.CONFIG.EMPTY` to disable.
The `Message.replies()` method now returns OwnedMessage objects, to
prevent certain memory de-allocation errors.
Fix for importing `notmuch2` module when building bindings
documentation.
Notmuch 0.34.2 (2021-12-09) Notmuch 0.34.2 (2021-12-09)
=========================== ===========================

View file

@ -3,21 +3,26 @@
dir := bindings dir := bindings
# force the shared library to be built # force the shared library to be built
ruby-bindings: lib/$(LINKER_NAME) ruby-bindings: $(dir)/ruby.stamp
$(dir)/ruby.stamp: lib/$(LINKER_NAME)
ifeq ($(HAVE_RUBY_DEV),1) ifeq ($(HAVE_RUBY_DEV),1)
cd $(dir)/ruby && \ cd $(dir)/ruby && \
EXTRA_LDFLAGS="$(NO_UNDEFINED_LDFLAGS)" \ EXTRA_LDFLAGS="$(NO_UNDEFINED_LDFLAGS)" \
LIBNOTMUCH="../../lib/$(LINKER_NAME)" \ LIBNOTMUCH="../../lib/$(LINKER_NAME)" \
NOTMUCH_SRCDIR='$(NOTMUCH_SRCDIR)' \ NOTMUCH_SRCDIR='$(NOTMUCH_SRCDIR)' \
$(RUBY) extconf.rb --vendor $(RUBY) extconf.rb --vendor
$(MAKE) -C $(dir)/ruby CFLAGS="$(CFLAGS) -pipe -fno-plt -fPIC" $(MAKE) -C $(dir)/ruby CFLAGS="$(CFLAGS) -pipe -fno-plt -fPIC" && touch $@
endif endif
python-cffi-bindings: lib/$(LINKER_NAME) python-cffi-bindings: $(dir)/python-cffi.stamp
$(dir)/python-cffi.stamp: lib/$(LINKER_NAME)
ifeq ($(HAVE_PYTHON3_CFFI),1) ifeq ($(HAVE_PYTHON3_CFFI),1)
cd $(dir)/python-cffi && \ cd $(dir)/python-cffi && \
${PYTHON} setup.py build --build-lib build/stage && \ ${PYTHON} setup.py build --build-lib build/stage && \
mkdir -p build/stage/tests && cp tests/*.py build/stage/tests mkdir -p build/stage/tests && cp tests/*.py build/stage/tests && \
touch ../python-cffi.stamp
endif endif
CLEAN += $(patsubst %,$(dir)/ruby/%, \ CLEAN += $(patsubst %,$(dir)/ruby/%, \
@ -26,6 +31,6 @@ CLEAN += $(patsubst %,$(dir)/ruby/%, \
init.o message.o messages.o mkmf.log notmuch.so query.o \ init.o message.o messages.o mkmf.log notmuch.so query.o \
status.o tags.o thread.o threads.o) status.o tags.o thread.o threads.o)
CLEAN += bindings/ruby/.vendorarchdir.time CLEAN += bindings/ruby/.vendorarchdir.time $(dir)/ruby.stamp
CLEAN += bindings/python-cffi/build CLEAN += bindings/python-cffi/build $(dir)/python-cffi.stamp

View file

@ -1,5 +1,5 @@
import cffi import cffi
from _notmuch_config import *
ffibuilder = cffi.FFI() ffibuilder = cffi.FFI()
ffibuilder.set_source( ffibuilder.set_source(
@ -16,8 +16,8 @@ ffibuilder.set_source(
#ERROR libnotmuch version < 5.1 not supported #ERROR libnotmuch version < 5.1 not supported
#endif #endif
""", """,
include_dirs=['../../lib'], include_dirs=[NOTMUCH_INCLUDE_DIR],
library_dirs=['../../lib'], library_dirs=[NOTMUCH_LIB_DIR],
libraries=['notmuch'], libraries=['notmuch'],
) )
ffibuilder.cdef( ffibuilder.cdef(
@ -54,6 +54,7 @@ ffibuilder.cdef(
NOTMUCH_STATUS_NO_DATABASE, NOTMUCH_STATUS_NO_DATABASE,
NOTMUCH_STATUS_DATABASE_EXISTS, NOTMUCH_STATUS_DATABASE_EXISTS,
NOTMUCH_STATUS_BAD_QUERY_SYNTAX, NOTMUCH_STATUS_BAD_QUERY_SYNTAX,
NOTMUCH_STATUS_NO_MAIL_ROOT,
NOTMUCH_STATUS_LAST_STATUS NOTMUCH_STATUS_LAST_STATUS
} notmuch_status_t; } notmuch_status_t;
typedef enum { typedef enum {

View file

@ -139,7 +139,7 @@ class Database(base.NotmuchObject):
path = os.fsencode(path) path = os.fsencode(path)
return path return path
def __init__(self, path=None, mode=MODE.READ_ONLY, config=CONFIG.EMPTY): def __init__(self, path=None, mode=MODE.READ_ONLY, config=CONFIG.SEARCH):
if isinstance(mode, str): if isinstance(mode, str):
mode = self.STR_MODE_MAP[mode] mode = self.STR_MODE_MAP[mode]
self.mode = mode self.mode = mode

View file

@ -205,6 +205,20 @@ class Message(base.NotmuchObject):
self._msg_p, capi.lib.NOTMUCH_MESSAGE_FLAG_EXCLUDED) self._msg_p, capi.lib.NOTMUCH_MESSAGE_FLAG_EXCLUDED)
return bool(ret) return bool(ret)
@property
def matched(self):
"""Indicates whether this message was matched by the query.
When a thread is created from a search, some of the
messages may not match the original query. This property
is set to *True* for those that do match.
:raises ObjectDestroyedError: if used after destroyed.
"""
ret = capi.lib.notmuch_message_get_flag(
self._msg_p, capi.lib.NOTMUCH_MESSAGE_FLAG_MATCH)
return bool(ret)
@property @property
def date(self): def date(self):
"""The message date as an integer. """The message date as an integer.
@ -357,14 +371,14 @@ class Message(base.NotmuchObject):
This method will only work if the message was created from a This method will only work if the message was created from a
thread. Otherwise it will yield no results. thread. Otherwise it will yield no results.
:returns: An iterator yielding :class:`Message` instances. :returns: An iterator yielding :class:`OwnedMessage` instances.
:rtype: MessageIter :rtype: MessageIter
""" """
# The notmuch_messages_valid call accepts NULL and this will # The notmuch_messages_valid call accepts NULL and this will
# become an empty iterator, raising StopIteration immediately. # become an empty iterator, raising StopIteration immediately.
# Hence no return value checking here. # Hence no return value checking here.
msgs_p = capi.lib.notmuch_message_get_replies(self._msg_p) msgs_p = capi.lib.notmuch_message_get_replies(self._msg_p)
return MessageIter(self, msgs_p, db=self._db) return MessageIter(self, msgs_p, db=self._db, msg_cls=OwnedMessage)
def __hash__(self): def __hash__(self):
return hash(self.messageid) return hash(self.messageid)

View file

@ -1,6 +1,7 @@
import setuptools import setuptools
from _notmuch_config import *
with open('version.txt') as fp: with open(NOTMUCH_VERSION_FILE) as fp:
VERSION = fp.read().strip() VERSION = fp.read().strip()
setuptools.setup( setuptools.setup(

View file

@ -23,9 +23,9 @@ class TestIter:
def test_set_get(self, maildir): def test_set_get(self, maildir):
# Ensure get-set works from different db objects # Ensure get-set works from different db objects
with dbmod.Database.create(maildir.path) as db0: with dbmod.Database.create(maildir.path, config=dbmod.Database.CONFIG.EMPTY) as db0:
db0.config['spam'] = 'ham' db0.config['spam'] = 'ham'
with dbmod.Database(maildir.path) as db1: with dbmod.Database(maildir.path, config=dbmod.Database.CONFIG.EMPTY) as db1:
assert db1.config['spam'] == 'ham' assert db1.config['spam'] == 'ham'
def test_get_keyerror(self, db): def test_get_keyerror(self, db):

View file

@ -13,7 +13,7 @@ import notmuch2._message as message
@pytest.fixture @pytest.fixture
def db(maildir): def db(maildir):
with dbmod.Database.create(maildir.path) as db: with dbmod.Database.create(maildir.path, config=notmuch2.Database.CONFIG.EMPTY) as db:
yield db yield db
@ -293,7 +293,7 @@ class TestQuery:
maildir.deliver(body='baz', maildir.deliver(body='baz',
headers=[('In-Reply-To', '<{}>'.format(msgid))]) headers=[('In-Reply-To', '<{}>'.format(msgid))])
notmuch('new') notmuch('new')
with dbmod.Database(maildir.path, 'rw') as db: with dbmod.Database(maildir.path, 'rw', config=notmuch2.Database.CONFIG.EMPTY) as db:
yield db yield db
def test_count_messages(self, db): def test_count_messages(self, db):

View file

@ -97,6 +97,9 @@ class TestMessage:
def test_ghost_no(self, msg): def test_ghost_no(self, msg):
assert not msg.ghost assert not msg.ghost
def test_matched_no(self,msg):
assert not msg.matched
def test_date(self, msg): def test_date(self, msg):
# XXX Someone seems to treat things as local time instead of # XXX Someone seems to treat things as local time instead of
# UTC or the other way around. # UTC or the other way around.

View file

@ -23,7 +23,7 @@ class TestImmutable:
""" """
maildir.deliver() maildir.deliver()
notmuch('new') notmuch('new')
with database.Database(maildir.path) as db: with database.Database(maildir.path, config=database.Database.CONFIG.EMPTY) as db:
yield db.tags yield db.tags
def test_type(self, tagset): def test_type(self, tagset):
@ -33,7 +33,7 @@ class TestImmutable:
def test_hash(self, tagset, maildir, notmuch): def test_hash(self, tagset, maildir, notmuch):
h0 = hash(tagset) h0 = hash(tagset)
notmuch('tag', '+foo', '*') notmuch('tag', '+foo', '*')
with database.Database(maildir.path) as db: with database.Database(maildir.path, config=database.Database.CONFIG.EMPTY) as db:
h1 = hash(db.tags) h1 = hash(db.tags)
assert h0 != h1 assert h0 != h1
@ -42,7 +42,7 @@ class TestImmutable:
def test_neq(self, tagset, maildir, notmuch): def test_neq(self, tagset, maildir, notmuch):
notmuch('tag', '+foo', '*') notmuch('tag', '+foo', '*')
with database.Database(maildir.path) as db: with database.Database(maildir.path, config=database.Database.CONFIG.EMPTY) as db:
assert tagset != db.tags assert tagset != db.tags
def test_contains(self, tagset): def test_contains(self, tagset):
@ -159,7 +159,8 @@ class TestMutableTagset:
_, pathname = maildir.deliver() _, pathname = maildir.deliver()
notmuch('new') notmuch('new')
with database.Database(maildir.path, with database.Database(maildir.path,
mode=database.Mode.READ_WRITE) as db: mode=database.Mode.READ_WRITE,
config=database.Database.CONFIG.EMPTY) as db:
msg = db.get(pathname) msg = db.get(pathname)
yield msg.tags yield msg.tags
@ -195,7 +196,8 @@ class TestMutableTagset:
_, pathname = maildir.deliver(flagged=True) _, pathname = maildir.deliver(flagged=True)
notmuch('new') notmuch('new')
with database.Database(maildir.path, with database.Database(maildir.path,
mode=database.Mode.READ_WRITE) as db: mode=database.Mode.READ_WRITE,
config=database.Database.CONFIG.EMPTY) as db:
msg = db.get(pathname) msg = db.get(pathname)
msg.tags.discard('flagged') msg.tags.discard('flagged')
msg.tags.from_maildir_flags() msg.tags.from_maildir_flags()
@ -205,7 +207,8 @@ class TestMutableTagset:
_, pathname = maildir.deliver(flagged=True) _, pathname = maildir.deliver(flagged=True)
notmuch('new') notmuch('new')
with database.Database(maildir.path, with database.Database(maildir.path,
mode=database.Mode.READ_WRITE) as db: mode=database.Mode.READ_WRITE,
config=database.Database.CONFIG.EMPTY) as db:
msg = db.get(pathname) msg = db.get(pathname)
flags = msg.path.name.split(',')[-1] flags = msg.path.name.split(',')[-1]
assert 'F' in flags assert 'F' in flags

View file

@ -13,7 +13,7 @@ def thread(maildir, notmuch):
maildir.deliver(body='bar', maildir.deliver(body='bar',
headers=[('In-Reply-To', '<{}>'.format(msgid))]) headers=[('In-Reply-To', '<{}>'.format(msgid))])
notmuch('new') notmuch('new')
with notmuch2.Database(maildir.path) as db: with notmuch2.Database(maildir.path, config=notmuch2.Database.CONFIG.EMPTY) as db:
yield next(db.threads('foo')) yield next(db.threads('foo'))
@ -57,6 +57,13 @@ def test_iter(thread):
def test_matched(thread): def test_matched(thread):
assert thread.matched == 1 assert thread.matched == 1
def test_matched_iter(thread):
count = 0
msgs = list(iter(thread))
for msg in msgs:
if msg.matched:
count += 1
assert count == thread.matched
def test_authors_type(thread): def test_authors_type(thread):
assert isinstance(thread.authors, notmuch2.BinString) assert isinstance(thread.authors, notmuch2.BinString)

View file

@ -1 +0,0 @@
0.34.2

View file

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

View file

@ -103,12 +103,12 @@ _notmuch_search_terms()
COMPREPLY=( $(compgen -P "from:" -W "`_notmuch_email ${cur}`" -- ${cur##from:}) ) COMPREPLY=( $(compgen -P "from:" -W "`_notmuch_email ${cur}`" -- ${cur##from:}) )
;; ;;
path:*) path:*)
local path=`notmuch config get database.path` local path=`notmuch config get database.mail_root`
compopt -o nospace compopt -o nospace
COMPREPLY=( $(compgen -d "$path/${cur##path:}" | sed "s|^$path/||" ) ) COMPREPLY=( $(compgen -d "$path/${cur##path:}" | sed "s|^$path/||" ) )
;; ;;
folder:*) folder:*)
local path=`notmuch config get database.path` local path=`notmuch config get database.mail_root`
compopt -o nospace compopt -o nospace
COMPREPLY=( $(compgen -d "$path/${cur##folder:}" | \ COMPREPLY=( $(compgen -d "$path/${cur##folder:}" | \
sed "s|^$path/||" | grep -v "\(^\|/\)\(cur\|new\|tmp\)$" ) ) sed "s|^$path/||" | grep -v "\(^\|/\)\(cur\|new\|tmp\)$" ) )
@ -281,7 +281,7 @@ _notmuch_insert()
$split && $split &&
case "${prev}" in case "${prev}" in
--folder) --folder)
local path=`notmuch config get database.path` local path=`notmuch config get database.mail_root`
compopt -o nospace compopt -o nospace
COMPREPLY=( $(compgen -d "$path/${cur}" | \ COMPREPLY=( $(compgen -d "$path/${cur}" | \
sed "s|^$path/||" | grep -v "\(^\|/\)\(cur\|new\|tmp\)$" ) ) sed "s|^$path/||" | grep -v "\(^\|/\)\(cur\|new\|tmp\)$" ) )

View file

@ -69,8 +69,8 @@ _notmuch_term_mimetype() {
_notmuch_term_path() { _notmuch_term_path() {
local ret=1 expl local ret=1 expl
local maildir="$(notmuch config get database.path)" local maildir="$(notmuch config get database.mail_root)"
[[ -d $maildir ]] || { _message -e "database.path not found" ; return $ret } [[ -d $maildir ]] || { _message -e "database.mail_root not found" ; return $ret }
_description notmuch-folder expl 'maildir folder' _description notmuch-folder expl 'maildir folder'
_files "$expl[@]" -W $maildir -/ && ret=0 _files "$expl[@]" -W $maildir -/ && ret=0
@ -79,8 +79,8 @@ _notmuch_term_path() {
_notmuch_term_folder() { _notmuch_term_folder() {
local ret=1 expl local ret=1 expl
local maildir="$(notmuch config get database.path)" local maildir="$(notmuch config get database.mail_root)"
[[ -d $maildir ]] || { _message -e "database.path not found" ; return $ret } [[ -d $maildir ]] || { _message -e "database.mail_root not found" ; return $ret }
_description notmuch-folder expl 'maildir folder' _description notmuch-folder expl 'maildir folder'
local ignoredfolders=( '*/(cur|new|tmp)' ) local ignoredfolders=( '*/(cur|new|tmp)' )

40
configure vendored
View file

@ -55,6 +55,8 @@ subdirs="${subdirs} bindings"
# the directory structure and copy Makefiles. # the directory structure and copy Makefiles.
if [ "$srcdir" != "." ]; then if [ "$srcdir" != "." ]; then
NOTMUCH_BUILDDIR=$PWD
for dir in . ${subdirs}; do for dir in . ${subdirs}; do
mkdir -p "$dir" mkdir -p "$dir"
cp "$srcdir"/"$dir"/Makefile.local "$dir" cp "$srcdir"/"$dir"/Makefile.local "$dir"
@ -78,6 +80,8 @@ if [ "$srcdir" != "." ]; then
"$srcdir"/bindings/python-cffi/notmuch2 \ "$srcdir"/bindings/python-cffi/notmuch2 \
"$srcdir"/bindings/python-cffi/setup.py \ "$srcdir"/bindings/python-cffi/setup.py \
bindings/python-cffi/ bindings/python-cffi/
else
NOTMUCH_BUILDDIR=$NOTMUCH_SRCDIR
fi fi
# Set several defaults (optionally specified by the user in # Set several defaults (optionally specified by the user in
@ -308,12 +312,22 @@ for option; do
true true
elif [ "${option%%=*}" = '--host' ] ; then elif [ "${option%%=*}" = '--host' ] ; then
true true
elif [ "${option%%=*}" = '--bindir' ] ; then
true
elif [ "${option%%=*}" = '--sbindir' ] ; then
true
elif [ "${option%%=*}" = '--datadir' ] ; then elif [ "${option%%=*}" = '--datadir' ] ; then
true true
elif [ "${option%%=*}" = '--localstatedir' ] ; then elif [ "${option%%=*}" = '--localstatedir' ] ; then
true true
elif [ "${option%%=*}" = '--sharedstatedir' ] ; then
true
elif [ "${option%%=*}" = '--libexecdir' ] ; then elif [ "${option%%=*}" = '--libexecdir' ] ; then
true true
elif [ "${option%%=*}" = '--exec-prefix' ] ; then
true
elif [ "${option%%=*}" = '--program-prefix' ] ; then
true
elif [ "${option}" = '--disable-maintainer-mode' ] ; then elif [ "${option}" = '--disable-maintainer-mode' ] ; then
true true
elif [ "${option}" = '--disable-dependency-tracking' ] ; then elif [ "${option}" = '--disable-dependency-tracking' ] ; then
@ -396,6 +410,18 @@ EOF
exit 1 exit 1
fi fi
printf "C compiler supports address sanitizer... "
test_cmdline="${CC} ${CFLAGS} ${CPPFLAGS} -fsanitize=address minimal.c ${LDFLAGS} -o minimal"
if ${test_cmdline} >/dev/null 2>&1 && ./minimal
then
printf "Yes.\n"
have_asan=1
else
printf "Nope, skipping those tests.\n"
have_asan=0
fi
unset test_cmdline
printf "Reading libnotmuch version from source... " printf "Reading libnotmuch version from source... "
cat > _libversion.c <<EOF cat > _libversion.c <<EOF
#include <stdio.h> #include <stdio.h>
@ -734,6 +760,7 @@ if command -v ${BASHCMD} > /dev/null; then
printf "Yes (%s).\n" "$bash_absolute" printf "Yes (%s).\n" "$bash_absolute"
else else
have_bash=0 have_bash=0
bash_absolute=
printf "No. (%s not found)\n" "${BASHCMD}" printf "No. (%s not found)\n" "${BASHCMD}"
fi fi
@ -744,6 +771,7 @@ if command -v ${PERL} > /dev/null; then
printf "Yes (%s).\n" "$perl_absolute" printf "Yes (%s).\n" "$perl_absolute"
else else
have_perl=0 have_perl=0
perl_absolute=
printf "No. (%s not found)\n" "${PERL}" printf "No. (%s not found)\n" "${PERL}"
fi fi
@ -1245,6 +1273,7 @@ cat > Makefile.config <<EOF
# directory (the current directory at the time configure was run). # directory (the current directory at the time configure was run).
srcdir = ${srcdir} srcdir = ${srcdir}
NOTMUCH_SRCDIR = ${NOTMUCH_SRCDIR} NOTMUCH_SRCDIR = ${NOTMUCH_SRCDIR}
NOTMUCH_BUILDDIR = ${NOTMUCH_BUILDDIR}
# subdirectories to build # subdirectories to build
subdirs = ${subdirs} subdirs = ${subdirs}
@ -1531,6 +1560,9 @@ NOTMUCH_GMIME_X509_CERT_VALIDITY=${gmime_x509_cert_validity}
# Whether GMime can verify signatures when decrypting with a session key: # Whether GMime can verify signatures when decrypting with a session key:
NOTMUCH_GMIME_VERIFY_WITH_SESSION_KEY=${gmime_verify_with_session_key} NOTMUCH_GMIME_VERIFY_WITH_SESSION_KEY=${gmime_verify_with_session_key}
# Does the C compiler support the address sanitizer
NOTMUCH_HAVE_ASAN=${have_asan}
# do we have man pages? # do we have man pages?
NOTMUCH_HAVE_MAN=$((have_sphinx)) NOTMUCH_HAVE_MAN=$((have_sphinx))
@ -1579,6 +1611,14 @@ EOF
printf "rsti_dir = '%s'\n" "$(cd emacs && pwd -P)" printf "rsti_dir = '%s'\n" "$(cd emacs && pwd -P)"
} > sphinx.config } > sphinx.config
cat > bindings/python-cffi/_notmuch_config.py <<EOF
# _notmuch_config.py was automatically generated by the configure
# script in the root of the notmuch source tree.
NOTMUCH_VERSION_FILE='${NOTMUCH_SRCDIR}/version.txt'
NOTMUCH_INCLUDE_DIR='${NOTMUCH_SRCDIR}/lib'
NOTMUCH_LIB_DIR='${NOTMUCH_SRCDIR}/lib'
EOF
# Finally, after everything configured, inform the user how to continue. # Finally, after everything configured, inform the user how to continue.
cat <<EOF cat <<EOF

31
debian/changelog vendored
View file

@ -1,3 +1,34 @@
notmuch (0.35-1~bpo11+1) bullseye-backports; urgency=medium
* Rebuild for bullseye-backports.
-- David Bremner <bremner@debian.org> Tue, 15 Feb 2022 20:54:43 -0400
notmuch (0.35-1) unstable; urgency=medium
* New upstream release
-- David Bremner <bremner@debian.org> Sun, 06 Feb 2022 12:15:19 -0400
notmuch (0.35~rc0-2) experimental; urgency=medium
* Reupload with binaries
-- David Bremner <bremner@debian.org> Sat, 29 Jan 2022 21:53:29 -0400
notmuch (0.35~rc0-1) experimental; urgency=medium
* New upstream release candidate
-- David Bremner <bremner@debian.org> Sat, 29 Jan 2022 18:14:57 -0400
notmuch (0.34.3-1) unstable; urgency=medium
* New upstream bugfix release, with several fixes for the notmuch2
python module.
-- David Bremner <bremner@debian.org> Sun, 09 Jan 2022 15:30:38 -0400
notmuch (0.34.2-1~bpo11+1) bullseye-backports; urgency=medium notmuch (0.34.2-1~bpo11+1) bullseye-backports; urgency=medium
* Rebuild for bullseye-backports. * Rebuild for bullseye-backports.

18
debian/control vendored
View file

@ -56,7 +56,8 @@ Recommends:
gnupg-agent, gnupg-agent,
gpgsm, gpgsm,
Suggests: Suggests:
mailscripts mailscripts,
notmuch-doc,
Description: thread-based email index, search and tagging Description: thread-based email index, search and tagging
Notmuch is a system for indexing, searching, reading, and tagging Notmuch is a system for indexing, searching, reading, and tagging
large collections of email messages in maildir or mh format. It uses large collections of email messages in maildir or mh format. It uses
@ -65,6 +66,21 @@ Description: thread-based email index, search and tagging
. .
This package contains the notmuch command-line interface This package contains the notmuch command-line interface
Package: notmuch-doc
Architecture: all
Depends:
${misc:Depends},
${sphinxdoc:Depends},
Suggests:
notmuch
Description: thread-based email index, search and tagging
Notmuch is a system for indexing, searching, reading, and tagging
large collections of email messages in maildir or mh format. It uses
the Xapian library to provide fast, full-text search with a very
convenient search syntax.
.
This package contains the HTML documentation
Package: libnotmuch5 Package: libnotmuch5
Section: libs Section: libs
Architecture: any Architecture: any

View file

@ -1,3 +1,3 @@
debian/tmp/usr/share/emacs/site-lisp/*.el debian/tmp/usr/share/emacs/site-lisp/*.el
debian/tmp/usr/share/emacs/site-lisp/notmuch-logo.png debian/tmp/usr/share/emacs/site-lisp/notmuch-logo.svg
emacs/notmuch-pkg.el emacs/notmuch-pkg.el

1
debian/notmuch-doc.install vendored Normal file
View file

@ -0,0 +1 @@
doc/_build/html usr/share/doc/notmuch

4
debian/rules vendored
View file

@ -3,7 +3,7 @@
export DEB_BUILD_MAINT_OPTIONS = hardening=+all export DEB_BUILD_MAINT_OPTIONS = hardening=+all
%: %:
dh $@ --with python3,elpa dh $@ --with python3,elpa,sphinxdoc
override_dh_auto_configure: override_dh_auto_configure:
BASHCMD=/bin/bash ./configure --prefix=/usr \ BASHCMD=/bin/bash ./configure --prefix=/usr \
@ -19,7 +19,7 @@ override_dh_auto_test:
dh_auto_test -- V=1 dh_auto_test -- V=1
override_dh_auto_build: override_dh_auto_build:
dh_auto_build -- V=1 dh_auto_build -- V=1 all sphinx-html
PYBUILD_NAME=notmuch dh_auto_build --buildsystem=pybuild --sourcedirectory bindings/python PYBUILD_NAME=notmuch dh_auto_build --buildsystem=pybuild --sourcedirectory bindings/python
PYBUILD_NAME=notmuch2 dh_auto_build --buildsystem=pybuild --sourcedirectory bindings/python-cffi PYBUILD_NAME=notmuch2 dh_auto_build --buildsystem=pybuild --sourcedirectory bindings/python-cffi
$(MAKE) -C contrib/notmuch-mutt $(MAKE) -C contrib/notmuch-mutt

View file

@ -145,9 +145,11 @@ headers = {
Cc?: string, Cc?: string,
Bcc?: string, Bcc?: string,
Reply-To?: string, Reply-To?: string,
Date: string Date: string,
extra_header_pair*
} }
extra_header_pair= (header_name: string)
# Encryption status (format_part_sprinter) # Encryption status (format_part_sprinter)
encstatus = [{status: "good"|"bad"}] encstatus = [{status: "good"|"bad"}]

View file

@ -35,7 +35,7 @@ endif
INFO_INFO_FILES := $(INFO_TEXI_FILES:.texi=.info) INFO_INFO_FILES := $(INFO_TEXI_FILES:.texi=.info)
.PHONY: sphinx-html sphinx-texinfo sphinx-info doc-prereqs .PHONY: sphinx-html sphinx-texinfo sphinx-info
.PHONY: install-man build-man apidocs install-apidocs .PHONY: install-man build-man apidocs install-apidocs
@ -47,23 +47,28 @@ $(DOCBUILDDIR)/.roff.stamp $(DOCBUILDDIR)/.html.stamp $(DOCBUILDDIR)/.texi.stamp
endif endif
ifeq ($(HAVE_PYTHON3_CFFI),1) ifeq ($(HAVE_PYTHON3_CFFI),1)
doc-prereqs: python-cffi-bindings DOC_PREREQS=bindings/python-cffi.stamp
else
DOC_PREREQS=
endif endif
sphinx-html: $(DOCBUILDDIR)/.html.stamp sphinx-html: $(DOCBUILDDIR)/.html.stamp
$(DOCBUILDDIR)/.html.stamp: $(ALL_RST_FILES) doc-prereqs $(DOCBUILDDIR)/.html.stamp: $(ALL_RST_FILES) $(DOC_PREREQS)
$(SPHINXBUILD) -b html -d $(DOCBUILDDIR)/html_doctrees $(ALLSPHINXOPTS) $(DOCBUILDDIR)/html $(SPHINXBUILD) -b html -d $(DOCBUILDDIR)/html_doctrees $(ALLSPHINXOPTS) $(DOCBUILDDIR)/html
touch $@ touch $@
sphinx-texinfo: $(DOCBUILDDIR)/.texi.stamp sphinx-texinfo: $(DOCBUILDDIR)/.texi.stamp
$(DOCBUILDDIR)/.texi.stamp: $(ALL_RST_FILES) doc-prereqs $(DOCBUILDDIR)/.texi.stamp: $(ALL_RST_FILES) $(DOC_PREREQS)
$(SPHINXBUILD) -b texinfo -d $(DOCBUILDDIR)/texinfo_doctrees $(ALLSPHINXOPTS) $(DOCBUILDDIR)/texinfo $(SPHINXBUILD) -b texinfo -d $(DOCBUILDDIR)/texinfo_doctrees $(ALLSPHINXOPTS) $(DOCBUILDDIR)/texinfo
touch $@ touch $@
sphinx-info: sphinx-texinfo sphinx-info: $(DOCBUILDDIR)/.info.stamp
$(DOCBUILDDIR)/.info.stamp: $(DOCBUILDDIR)/.texi.stamp $(DOC_PREREQS)
$(MAKE) -C $(DOCBUILDDIR)/texinfo info $(MAKE) -C $(DOCBUILDDIR)/texinfo info
touch $@
# Use the man page converter that is available. We should never depend # Use the man page converter that is available. We should never depend
# on MAN_ROFF_FILES if a converter is not available. # on MAN_ROFF_FILES if a converter is not available.
@ -112,6 +117,11 @@ build-man:
install-man: install-man:
@echo "No sphinx, will not install man pages." @echo "No sphinx, will not install man pages."
else else
# it should be safe to depend on the stamp file, because it is created
# after all roff files are moved into place.
${MAN_GZIP_FILES}: ${DOCBUILDDIR}/.roff.stamp
build-man: ${MAN_GZIP_FILES} build-man: ${MAN_GZIP_FILES}
install-man: ${MAN_GZIP_FILES} install-man: ${MAN_GZIP_FILES}
mkdir -m0755 -p "$(DESTDIR)$(mandir)/man1" mkdir -m0755 -p "$(DESTDIR)$(mandir)/man1"
@ -127,7 +137,7 @@ ifneq ($(HAVE_SPHINX)$(HAVE_MAKEINFO),11)
build-info: build-info:
@echo "Missing sphinx or makeinfo, not building info pages" @echo "Missing sphinx or makeinfo, not building info pages"
else else
build-info: sphinx-info build-info: $(DOCBUILDDIR)/.info.stamp
endif endif
ifneq ($(HAVE_SPHINX)$(HAVE_MAKEINFO)$(HAVE_INSTALL_INFO),111) ifneq ($(HAVE_SPHINX)$(HAVE_MAKEINFO)$(HAVE_INSTALL_INFO),111)
@ -145,5 +155,5 @@ $(dir)/config.dox: version.stamp
echo "INPUT=${srcdir}/lib/notmuch.h" >> $@ echo "INPUT=${srcdir}/lib/notmuch.h" >> $@
CLEAN := $(CLEAN) $(DOCBUILDDIR) $(DOCBUILDDIR)/.roff.stamp $(DOCBUILDDIR)/.texi.stamp CLEAN := $(CLEAN) $(DOCBUILDDIR) $(DOCBUILDDIR)/.roff.stamp $(DOCBUILDDIR)/.texi.stamp
CLEAN := $(CLEAN) $(DOCBUILDDIR)/.html.stamp CLEAN := $(CLEAN) $(DOCBUILDDIR)/.html.stamp $(DOCBUILDDIR)/.info.stamp
CLEAN := $(CLEAN) $(MAN_GZIP_FILES) $(MAN_ROFF_FILES) $(dir)/conf.pyc $(dir)/config.dox CLEAN := $(CLEAN) $(MAN_GZIP_FILES) $(MAN_ROFF_FILES) $(dir)/conf.pyc $(dir)/config.dox

View file

@ -14,7 +14,7 @@ master_doc = 'index'
# General information about the project. # General information about the project.
project = u'notmuch' project = u'notmuch'
copyright = u'2009-2021, Carl Worth and many others' copyright = u'2009-2022, Carl Worth and many others'
location = os.path.dirname(__file__) location = os.path.dirname(__file__)

View file

@ -42,7 +42,7 @@ Supported options for **address** include
neither ``--output=sender`` nor ``--output=recipients`` is neither ``--output=sender`` nor ``--output=recipients`` is
given, ``--output=sender`` is implied. given, ``--output=sender`` is implied.
**sender** sender
Output all addresses from the *From* header. Output all addresses from the *From* header.
Note: Searching for **sender** should be much faster than Note: Searching for **sender** should be much faster than
@ -50,17 +50,17 @@ Supported options for **address** include
cached directly in the database whereas other addresses need cached directly in the database whereas other addresses need
to be fetched from message files. to be fetched from message files.
**recipients** recipients
Output all addresses from the *To*, *Cc* and *Bcc* headers. Output all addresses from the *To*, *Cc* and *Bcc* headers.
**count** count
Print the count of how many times was the address encountered Print the count of how many times was the address encountered
during search. during search.
Note: With this option, addresses are printed only after the Note: With this option, addresses are printed only after the
whole search is finished. This may take long time. whole search is finished. This may take long time.
**address** address
Output only the email addresses instead of the full mailboxes Output only the email addresses instead of the full mailboxes
with names and email addresses. This option has no effect on with names and email addresses. This option has no effect on
the JSON or S-Expression output formats. the JSON or S-Expression output formats.
@ -69,17 +69,17 @@ Supported options for **address** include
Control the deduplication of results. Control the deduplication of results.
**no** no
Output all occurrences of addresses in the matching Output all occurrences of addresses in the matching
messages. This is not applicable with ``--output=count``. messages. This is not applicable with ``--output=count``.
**mailbox** mailbox
Deduplicate addresses based on the full, case sensitive name Deduplicate addresses based on the full, case sensitive name
and email address, or mailbox. This is effectively the same as and email address, or mailbox. This is effectively the same as
piping the ``--deduplicate=no`` output to **sort | uniq**, except piping the ``--deduplicate=no`` output to **sort | uniq**, except
for the order of results. This is the default. for the order of results. This is the default.
**address** address
Deduplicate addresses based on the case insensitive address Deduplicate addresses based on the case insensitive address
part of the mailbox. Of all the variants (with different name part of the mailbox. Of all the variants (with different name
or case), print the one occurring most frequently among the or case), print the one occurring most frequently among the

View file

@ -55,14 +55,14 @@ The available configuration items are described below. Non-absolute
paths are presumed relative to `$HOME` for items in section paths are presumed relative to `$HOME` for items in section
**database**. **database**.
**database.path** database.path
Notmuch will store its database here, (in Notmuch will store its database here, (in
sub-directory named ``.notmuch`` if **database.mail\_root** sub-directory named ``.notmuch`` if **database.mail\_root**
is unset). is unset).
Default: see :ref:`database` Default: see :ref:`database`
**database.mail_root** database.mail_root
The top-level directory where your mail currently exists and to The top-level directory where your mail currently exists and to
where mail will be delivered in the future. Files should be where mail will be delivered in the future. Files should be
individual email messages. individual email messages.
@ -72,7 +72,7 @@ paths are presumed relative to `$HOME` for items in section
Default: For compatibility with older configurations, the value of Default: For compatibility with older configurations, the value of
database.path is used if **database.mail\_root** is unset. database.path is used if **database.mail\_root** is unset.
**database.backup_dir** database.backup_dir
Directory to store tag dumps when upgrading database. Directory to store tag dumps when upgrading database.
History: this configuration value was introduced in notmuch 0.32. History: this configuration value was introduced in notmuch 0.32.
@ -80,7 +80,7 @@ paths are presumed relative to `$HOME` for items in section
Default: A sibling directory of the Xapian database called Default: A sibling directory of the Xapian database called
`backups`. `backups`.
**database.hook_dir** database.hook_dir
Directory containing hooks run by notmuch commands. See Directory containing hooks run by notmuch commands. See
:any:`notmuch-hooks(5)`. :any:`notmuch-hooks(5)`.
@ -88,7 +88,7 @@ paths are presumed relative to `$HOME` for items in section
Default: See HOOKS, below. Default: See HOOKS, below.
**database.autocommit** database.autocommit
How often to commit transactions to disk. `0` means wait until How often to commit transactions to disk. `0` means wait until
command completes, otherwise an integer `n` specifies to commit to command completes, otherwise an integer `n` specifies to commit to
@ -96,30 +96,30 @@ paths are presumed relative to `$HOME` for items in section
History: this configuration value was introduced in notmuch 0.33. History: this configuration value was introduced in notmuch 0.33.
**user.name** user.name
Your full name. Your full name.
Default: ``$NAME`` variable if set, otherwise read from Default: ``$NAME`` variable if set, otherwise read from
``/etc/passwd``. ``/etc/passwd``.
**user.primary\_email** user.primary\_email
Your primary email address. Your primary email address.
Default: ``$EMAIL`` variable if set, otherwise constructed from Default: ``$EMAIL`` variable if set, otherwise constructed from
the username and hostname of the current machine. the username and hostname of the current machine.
**user.other\_email** user.other\_email
A list of other email addresses at which you receive email. A list of other email addresses at which you receive email.
Default: not set. Default: not set.
**new.tags** new.tags
A list of tags that will be added to all messages incorporated by A list of tags that will be added to all messages incorporated by
**notmuch new**. **notmuch new**.
Default: ``unread;inbox``. Default: ``unread;inbox``.
**new.ignore** new.ignore
A list to specify files and directories that will not be searched 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: for messages by :any:`notmuch-new(1)`. Each entry in the list is either:
@ -137,7 +137,7 @@ paths are presumed relative to `$HOME` for items in section
Default: empty list. Default: empty list.
**search.exclude\_tags** search.exclude\_tags
A list of tags that will be excluded from search results by A list of tags that will be excluded from search results by
default. Using an excluded tag in a query will override that default. Using an excluded tag in a query will override that
exclusion. exclusion.
@ -145,7 +145,21 @@ paths are presumed relative to `$HOME` for items in section
Default: empty list. Note that :any:`notmuch-setup(1)` puts Default: empty list. Note that :any:`notmuch-setup(1)` puts
``deleted;spam`` here when creating new configuration file. ``deleted;spam`` here when creating new configuration file.
**maildir.synchronize\_flags** .. _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) If true, then the following maildir flags (in message filenames)
will be synchronized with the corresponding notmuch tags: will be synchronized with the corresponding notmuch tags:
@ -178,7 +192,7 @@ paths are presumed relative to `$HOME` for items in section
Default: ``true``. Default: ``true``.
**index.decrypt** index.decrypt
Policy for decrypting encrypted messages during indexing. Must be Policy for decrypting encrypted messages during indexing. Must be
one of: ``false``, ``auto``, ``nostash``, or ``true``. one of: ``false``, ``auto``, ``nostash``, or ``true``.
@ -231,7 +245,7 @@ paths are presumed relative to `$HOME` for items in section
Default: ``auto``. Default: ``auto``.
**index.header.<prefix>** index.header.<prefix>
Define the query prefix <prefix>, based on a mail header. For Define the query prefix <prefix>, based on a mail header. For
example ``index.header.List=List-Id`` will add a probabilistic example ``index.header.List=List-Id`` will add a probabilistic
prefix ``List:`` that searches the ``List-Id`` field. User prefix ``List:`` that searches the ``List-Id`` field. User
@ -240,18 +254,18 @@ paths are presumed relative to `$HOME` for items in section
supported. See :any:`notmuch-search-terms(7)` for a list of existing supported. See :any:`notmuch-search-terms(7)` for a list of existing
prefixes, and an explanation of probabilistic prefixes. prefixes, and an explanation of probabilistic prefixes.
**built_with.<name>** built_with.<name>
Compile time feature <name>. Current possibilities include Compile time feature <name>. Current possibilities include
"retry_lock" (configure option, included by default). "retry_lock" (configure option, included by default).
(since notmuch 0.30, "compact" and "field_processor" are (since notmuch 0.30, "compact" and "field_processor" are
always included.) always included.)
**query.<name>** query.<name>
Expansion for named query called <name>. See Expansion for named query called <name>. See
:any:`notmuch-search-terms(7)` for more information about named :any:`notmuch-search-terms(7)` for more information about named
queries. queries.
**squery.<name>** squery.<name>
Expansion for named query called <name>, using s-expression syntax. See Expansion for named query called <name>, using s-expression syntax. See
:any:`notmuch-sexp-queries(7)` for more information about s-expression :any:`notmuch-sexp-queries(7)` for more information about s-expression
queries. queries.

View file

@ -28,13 +28,13 @@ Supported options for **count** include
.. option:: --output=(messages|threads|files) .. option:: --output=(messages|threads|files)
**messages** messages
Output the number of matching messages. This is the default. Output the number of matching messages. This is the default.
**threads** threads
Output the number of matching threads. Output the number of matching threads.
**files** files
Output the number of files associated with matching Output the number of files associated with matching
messages. This may be bigger than the number of matching messages. This may be bigger than the number of matching
messages due to duplicates (i.e. multiple files having the messages due to duplicates (i.e. multiple files having the

View file

@ -39,7 +39,7 @@ Supported options for **dump** include
Notmuch restore supports two plain text dump formats, both with Notmuch restore supports two plain text dump formats, both with
one message-id per line, followed by a list of tags. one message-id per line, followed by a list of tags.
**batch-tag** batch-tag
The default **batch-tag** dump format is intended to more The default **batch-tag** dump format is intended to more
robust against malformed message-ids and tags containing robust against malformed message-ids and tags containing
whitespace or non-\ :manpage:`ascii(7)` characters. Each line whitespace or non-\ :manpage:`ascii(7)` characters. Each line
@ -58,7 +58,7 @@ Supported options for **dump** include
:any:`notmuch-tag(1)`; note that the single message-id query is :any:`notmuch-tag(1)`; note that the single message-id query is
mandatory for :any:`notmuch-restore(1)`. mandatory for :any:`notmuch-restore(1)`.
**sup** sup
The **sup** dump file format is specifically chosen to be The **sup** dump file format is specifically chosen to be
compatible with the format of files produced by compatible with the format of files produced by
:manpage:`sup-dump(1)`. So if you've previously been using sup :manpage:`sup-dump(1)`. So if you've previously been using sup
@ -77,18 +77,18 @@ Supported options for **dump** include
Control what kind of metadata is included in the output. Control what kind of metadata is included in the output.
**config** config
Output configuration data stored in the database. Each line Output configuration data stored in the database. Each line
starts with "#@ ", followed by a space separated key-value starts with "#@ ", followed by a space separated key-value
pair. Both key and value are hex encoded if needed. pair. Both key and value are hex encoded if needed.
**properties** properties
Output per-message (key,value) metadata. Each line starts Output per-message (key,value) metadata. Each line starts
with "#= ", followed by a message id, and a space separated with "#= ", followed by a message id, and a space separated
list of key=value pairs. Ids, keys and values are hex encoded list of key=value pairs. Ids, keys and values are hex encoded
if needed. See :any:`notmuch-properties(7)` for more details. if needed. See :any:`notmuch-properties(7)` for more details.
**tags** tags
Output per-message boolean metadata, namely tags. See *format* above Output per-message boolean metadata, namely tags. See *format* above
for description of the output. for description of the output.

View file

@ -14,7 +14,7 @@ DESCRIPTION
**notmuch insert** reads a message from standard input and delivers it **notmuch insert** reads a message from standard input and delivers it
into the maildir directory given by configuration option into the maildir directory given by configuration option
**database.path**, then incorporates the message into the notmuch **database.mail_root**, then incorporates the message into the notmuch
database. It is an alternative to using a separate tool to deliver the database. It is an alternative to using a separate tool to deliver the
message then running :any:`notmuch-new(1)` afterwards. message then running :any:`notmuch-new(1)` afterwards.
@ -38,7 +38,7 @@ Supported options for **insert** include
.. option:: --folder=<folder> .. option:: --folder=<folder>
Deliver the message to the specified folder, relative to the Deliver the message to the specified folder, relative to the
top-level directory given by the value of **database.path**. The top-level directory given by the value of **database.mail_root**. The
default is the empty string, which means delivering to the default is the empty string, which means delivering to the
top-level directory. top-level directory.

View file

@ -40,22 +40,22 @@ Supported options for **reply** include
.. option:: --format=(default|json|sexp|headers-only) .. option:: --format=(default|json|sexp|headers-only)
**default** default
Includes subject and quoted message body as an RFC 2822 Includes subject and quoted message body as an RFC 2822
message. message.
**json** json
Produces JSON output containing headers for a reply message Produces JSON output containing headers for a reply message
and the contents of the original message. This output can be and the contents of the original message. This output can be
used by a client to create a reply message intelligently. used by a client to create a reply message intelligently.
**sexp** sexp
Produces S-Expression output containing headers for a reply Produces S-Expression output containing headers for a reply
message and the contents of the original message. This output message and the contents of the original message. This output
can be used by a client to create a reply message can be used by a client to create a reply message
intelligently. intelligently.
**headers-only** headers-only
Only produces In-Reply-To, References, To, Cc, and Bcc Only produces In-Reply-To, References, To, Cc, and Bcc
headers. headers.
@ -67,10 +67,10 @@ Supported options for **reply** include
.. option:: --reply-to=(all|sender) .. option:: --reply-to=(all|sender)
**all** (default) all (default)
Replies to all addresses. Replies to all addresses.
**sender** sender
Replies only to the sender. If replying to user's own message Replies only to the sender. If replying to user's own message
(Reply-to: or From: header is one of the user's configured (Reply-to: or From: header is one of the user's configured
email addresses), try To:, Cc:, and Bcc: headers in this email addresses), try To:, Cc:, and Bcc: headers in this

View file

@ -32,14 +32,14 @@ Supported options for **restore** include
line specifying a message-id and a set of tags. For details of the line specifying a message-id and a set of tags. For details of the
actual formats, see :any:`notmuch-dump(1)`. actual formats, see :any:`notmuch-dump(1)`.
**sup** sup
The **sup** dump file format is specifically chosen to be The **sup** dump file format is specifically chosen to be
compatible with the format of files produced by sup-dump. So compatible with the format of files produced by sup-dump. So
if you've previously been using sup for mail, then the if you've previously been using sup for mail, then the
**notmuch restore** command provides you a way to import all **notmuch restore** command provides you a way to import all
of your tags (or labels as sup calls them). of your tags (or labels as sup calls them).
**batch-tag** batch-tag
The **batch-tag** dump format is intended to more robust The **batch-tag** dump format is intended to more robust
against malformed message-ids and tags containing whitespace against malformed message-ids and tags containing whitespace
or non-\ **ascii(7)** characters. See :any:`notmuch-dump(1)` for or non-\ **ascii(7)** characters. See :any:`notmuch-dump(1)` for
@ -49,7 +49,7 @@ Supported options for **restore** include
changes if the **maildir.synchronize\_flags** configuration changes if the **maildir.synchronize\_flags** configuration
option is enabled. See :any:`notmuch-config(1)` for details. option is enabled. See :any:`notmuch-config(1)` for details.
**auto** auto
This option (the default) tries to guess the format from the This option (the default) tries to guess the format from the
input. For correctly formed input in either supported format, input. For correctly formed input in either supported format,
this heuristic, based the fact that batch-tag format contains this heuristic, based the fact that batch-tag format contains
@ -59,18 +59,18 @@ Supported options for **restore** include
Control what kind of metadata is restored. Control what kind of metadata is restored.
**config** config
Restore configuration data to the database. Each configuration Restore configuration data to the database. Each configuration
line starts with "#@ ", followed by a space separated line starts with "#@ ", followed by a space separated
key-value pair. Both key and value are hex encoded if needed. key-value pair. Both key and value are hex encoded if needed.
**properties** properties
Restore per-message (key,value) metadata. Each line starts Restore per-message (key,value) metadata. Each line starts
with "#= ", followed by a message id, and a space separated with "#= ", followed by a message id, and a space separated
list of key=value pairs. Ids, keys and values are hex encoded list of key=value pairs. Ids, keys and values are hex encoded
if needed. See :any:`notmuch-properties(7)` for more details. if needed. See :any:`notmuch-properties(7)` for more details.
**tags** tags
Restore per-message metadata, namely tags. See *format* above Restore per-message metadata, namely tags. See *format* above
for more details. for more details.

View file

@ -43,7 +43,7 @@ Supported options for **search** include
.. option:: --output=(summary|threads|messages|files|tags) .. option:: --output=(summary|threads|messages|files|tags)
**summary** summary
Output a summary of each thread with any message matching the Output a summary of each thread with any message matching the
search terms. The summary includes the thread ID, date, the search terms. The summary includes the thread ID, date, the
number of messages in the thread (both the number matched and number of messages in the thread (both the number matched and
@ -52,19 +52,19 @@ Supported options for **search** include
for some messages, the total number of files is printed in for some messages, the total number of files is printed in
parentheses (see below for an example). parentheses (see below for an example).
**threads** threads
Output the thread IDs of all threads with any message matching Output the thread IDs of all threads with any message matching
the search terms, either one per line (``--format=text``), the search terms, either one per line (``--format=text``),
separated by null characters (``--format=text0``), as a JSON array separated by null characters (``--format=text0``), as a JSON array
(``--format=json``), or an S-Expression list (``--format=sexp``). (``--format=json``), or an S-Expression list (``--format=sexp``).
**messages** messages
Output the message IDs of all messages matching the search Output the message IDs of all messages matching the search
terms, either one per line (``--format=text``), separated by null terms, either one per line (``--format=text``), separated by null
characters (``--format=text0``), as a JSON array (``--format=json``), characters (``--format=text0``), as a JSON array (``--format=json``),
or as an S-Expression list (``--format=sexp``). or as an S-Expression list (``--format=sexp``).
**files** files
Output the filenames of all messages matching the search Output the filenames of all messages matching the search
terms, either one per line (``--format=text``), separated by null terms, either one per line (``--format=text``), separated by null
characters (``--format=text0``), as a JSON array (``--format=json``), characters (``--format=text0``), as a JSON array (``--format=json``),
@ -78,7 +78,7 @@ Supported options for **search** include
in other directories that are included in the output, although in other directories that are included in the output, although
these files alone would not match the search. these files alone would not match the search.
**tags** tags
Output all tags that appear on any message matching the search Output all tags that appear on any message matching the search
terms, either one per line (``--format=text``), separated by null terms, either one per line (``--format=text``), separated by null
characters (``--format=text0``), as a JSON array (``--format=json``), characters (``--format=text0``), as a JSON array (``--format=json``),
@ -115,20 +115,20 @@ Supported options for **search** include
terms. This option specifies whether to omit excluded messages in terms. This option specifies whether to omit excluded messages in
the search process. the search process.
**true** (default) true (default)
Prevent excluded messages from matching the search terms. Prevent excluded messages from matching the search terms.
**all** all
Additionally prevent excluded messages from appearing in Additionally prevent excluded messages from appearing in
displayed results, in effect behaving as though the excluded displayed results, in effect behaving as though the excluded
messages do not exist. messages do not exist.
**false** false
Allow excluded messages to match search terms and appear in Allow excluded messages to match search terms and appear in
displayed results. Excluded messages are still marked in the displayed results. Excluded messages are still marked in the
relevant outputs. relevant outputs.
**flag** flag
Only has an effect when ``--output=summary``. The output is Only has an effect when ``--output=summary``. The output is
almost identical to **false**, but the "match count" is the almost identical to **false**, but the "match count" is the
number of matching non-excluded messages in the thread, rather number of matching non-excluded messages in the thread, rather

View file

@ -36,7 +36,7 @@ Supported options for **show** include
.. option:: --format=(text|json|sexp|mbox|raw) .. option:: --format=(text|json|sexp|mbox|raw)
**text** (default for messages) text (default for messages)
The default plain-text format has all text-content MIME parts The default plain-text format has all text-content MIME parts
decoded. Various components in the output, (**message**, decoded. Various components in the output, (**message**,
**header**, **body**, **attachment**, and MIME **part**), will **header**, **body**, **attachment**, and MIME **part**), will
@ -46,7 +46,7 @@ Supported options for **show** include
'}'), to either open or close the component. For a multipart '}'), to either open or close the component. For a multipart
MIME message, these parts will be nested. MIME message, these parts will be nested.
**json** json
The output is formatted with Javascript Object Notation The output is formatted with Javascript Object Notation
(JSON). This format is more robust than the text format for (JSON). This format is more robust than the text format for
automated processing. The nested structure of multipart MIME automated processing. The nested structure of multipart MIME
@ -58,7 +58,7 @@ Supported options for **show** include
as UTF-8 and any message content included in the output will as UTF-8 and any message content included in the output will
be charset-converted to UTF-8. be charset-converted to UTF-8.
**sexp** sexp
The output is formatted as the Lisp s-expression (sexp) The output is formatted as the Lisp s-expression (sexp)
equivalent of the JSON format above. Objects are formatted as equivalent of the JSON format above. Objects are formatted as
property lists whose keys are keywords (symbols preceded by a property lists whose keys are keywords (symbols preceded by a
@ -66,7 +66,7 @@ Supported options for **show** include
formatted as ``nil``. As for JSON, the s-expression output is formatted as ``nil``. As for JSON, the s-expression output is
always encoded as UTF-8. always encoded as UTF-8.
**mbox** mbox
All matching messages are output in the traditional, Unix mbox All matching messages are output in the traditional, Unix mbox
format with each message being prefixed by a line beginning format with each message being prefixed by a line beginning
with "From " and a blank line separating each message. Lines with "From " and a blank line separating each message. Lines
@ -77,7 +77,7 @@ Supported options for **show** include
http://homepage.ntlworld.com/jonathan.deboynepollard/FGA/mail-mbox-formats.html http://homepage.ntlworld.com/jonathan.deboynepollard/FGA/mail-mbox-formats.html
**raw** (default if ``--part`` is given) raw (default if ``--part`` is given)
Write the raw bytes of the given MIME part of a message to Write the raw bytes of the given MIME part of a message to
standard out. For this format, it is an error to specify a standard out. For this format, it is an error to specify a
query that matches more than one message. query that matches more than one message.
@ -221,6 +221,13 @@ email messages. For this, use a search term of "thread:<thread-id>" as
can be seen in the first column of output from the can be seen in the first column of output from the
:any:`notmuch-search(1)` command. :any:`notmuch-search(1)` command.
CONFIGURATION
=============
Structured output (json / sexp) is influenced by the configuration
option :ref:`show.extra_headers <show.extra_headers>`. See
:any:`notmuch-config(1)` for details.
EXIT STATUS EXIT STATUS
=========== ===========

View file

@ -19,7 +19,7 @@ must have executable permissions.
The currently available hooks are described below. The currently available hooks are described below.
**pre-new** pre-new
This hook is invoked by the :any:`notmuch-new(1)` command before This hook is invoked by the :any:`notmuch-new(1)` command before
scanning or importing new messages into the database. If this hook scanning or importing new messages into the database. If this hook
exits with a non-zero status, notmuch will abort further exits with a non-zero status, notmuch will abort further
@ -28,7 +28,7 @@ The currently available hooks are described below.
Typically this hook is used for fetching or delivering new mail to Typically this hook is used for fetching or delivering new mail to
be imported into the database. be imported into the database.
**post-new** post-new
This hook is invoked by the :any:`notmuch-new(1)` command after This hook is invoked by the :any:`notmuch-new(1)` command after
new messages have been imported into the database and initial tags 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 have been applied. The hook will not be run if there have been any
@ -37,7 +37,7 @@ The currently available hooks are described below.
Typically this hook is used to perform additional query-based Typically this hook is used to perform additional query-based
tagging on the imported messages. tagging on the imported messages.
**post-insert** post-insert
This hook is invoked by the :any:`notmuch-insert(1)` command after This hook is invoked by the :any:`notmuch-insert(1)` command after
the message has been delivered, added to the database, and initial the message has been delivered, added to the database, and initial
tags have been applied. The hook will not be run if there have tags have been applied. The hook will not be run if there have

View file

@ -55,7 +55,7 @@ MESSAGE PROPERTIES
The following properties are set by notmuch internally in the course The following properties are set by notmuch internally in the course
of its normal activity. of its normal activity.
**index.decryption** index.decryption
If a message contains encrypted content, and notmuch tries to If a message contains encrypted content, and notmuch tries to
decrypt that content during indexing, it will add the property decrypt that content during indexing, it will add the property
``index.decryption=success`` when the cleartext was successfully ``index.decryption=success`` when the cleartext was successfully
@ -75,8 +75,7 @@ of its normal activity.
:any:`notmuch-config(1)`), then this property will not be set on that :any:`notmuch-config(1)`), then this property will not be set on that
message. message.
**session-key** session-key
When :any:`notmuch-show(1)` or :any:`notmuch-reply(1)` encounters When :any:`notmuch-show(1)` or :any:`notmuch-reply(1)` encounters
a message with an encrypted part, if notmuch finds a a message with an encrypted part, if notmuch finds a
``session-key`` property associated with the message, it will try ``session-key`` property associated with the message, it will try
@ -111,8 +110,7 @@ of its normal activity.
example, an AES-128 key might be stashed in a notmuch property as: example, an AES-128 key might be stashed in a notmuch property as:
``session-key=7:14B16AF65536C28AF209828DFE34C9E0``. ``session-key=7:14B16AF65536C28AF209828DFE34C9E0``.
**index.repaired** index.repaired
Some messages arrive in forms that are confusing to view; they can Some messages arrive in forms that are confusing to view; they can
be mangled by mail transport agents, or the sending mail user be mangled by mail transport agents, or the sending mail user
agent may structure them in a way that is confusing. If notmuch agent may structure them in a way that is confusing. If notmuch

View file

@ -21,7 +21,7 @@ build of notmuch supports it with
:: ::
$ notmuch config get built_with.sexpr_query $ notmuch config get built_with.sexp_queries
S-EXPRESSIONS S-EXPRESSIONS
@ -31,10 +31,12 @@ An *s-expression* is either an atom, or list of whitespace delimited
s-expressions inside parentheses. Atoms are either s-expressions inside parentheses. Atoms are either
*basic value* *basic value*
A basic value is an unquoted string containing no whitespace, double quotes, or A basic value is an unquoted string containing no whitespace, double quotes, or
parentheses. parentheses.
*quoted string* *quoted string*
Double quotes (") delimit strings possibly containing whitespace Double quotes (") delimit strings possibly containing whitespace
or parentheses. These can contain double quote characters by or parentheses. These can contain double quote characters by
escaping with backslash. E.g. ``"this is a quote \""``. escaping with backslash. E.g. ``"this is a quote \""``.
@ -48,9 +50,11 @@ a *field*, *logical operation*, or *modifier*, and 0 or more
subqueries. subqueries.
``*`` ``*``
"*" matches any non-empty string in the current field. "*" matches any non-empty string in the current field.
``()`` ``()``
The empty list matches all messages The empty list matches all messages
*term* *term*
@ -62,19 +66,23 @@ subqueries.
phrase splitting see :any:`fields`. phrase splitting see :any:`fields`.
``(`` *field* |q1| |q2| ... |qn| ``)`` ``(`` *field* |q1| |q2| ... |qn| ``)``
Restrict the queries |q1| to |qn| to *field*, and combine with *and* Restrict the queries |q1| to |qn| to *field*, and combine with *and*
(for most fields) or *or*. See :any:`fields` for more information. (for most fields) or *or*. See :any:`fields` for more information.
``(`` *operator* |q1| |q2| ... |qn| ``)`` ``(`` *operator* |q1| |q2| ... |qn| ``)``
Combine queries |q1| to |qn|. Currently supported operators are Combine queries |q1| to |qn|. Currently supported operators are
``and``, ``or``, and ``not``. ``(not`` |q1| ... |qn| ``)`` is equivalent ``and``, ``or``, and ``not``. ``(not`` |q1| ... |qn| ``)`` is equivalent
to ``(and (not`` |q1| ``) ... (not`` |qn| ``))``. to ``(and (not`` |q1| ``) ... (not`` |qn| ``))``.
``(`` *modifier* |q1| |q2| ... |qn| ``)`` ``(`` *modifier* |q1| |q2| ... |qn| ``)``
Combine queries |q1| to |qn|, and reinterpret the result (e.g. as a regular expression). Combine queries |q1| to |qn|, and reinterpret the result (e.g. as a regular expression).
See :any:`modifiers` for more information. See :any:`modifiers` for more information.
``(macro (`` |p1| ... |pn| ``) body)`` ``(macro (`` |p1| ... |pn| ``) body)``
Define saved query with parameter substitution. The syntax is Define saved query with parameter substitution. The syntax is
recognized only in saved s-expression queries (see ``squery.*`` in recognized only in saved s-expression queries (see ``squery.*`` in
:any:`notmuch-config(1)`). Parameter names in ``body`` must be :any:`notmuch-config(1)`). Parameter names in ``body`` must be
@ -164,26 +172,31 @@ MODIFIERS
that are neither operators nor fields. that are neither operators nor fields.
``(infix`` *atom* ``)`` ``(infix`` *atom* ``)``
Interpret *atom* as an infix notmuch query (see Interpret *atom* as an infix notmuch query (see
:any:`notmuch-search-terms(7)`). Not supported inside fields. :any:`notmuch-search-terms(7)`). Not supported inside fields.
``(matching`` |q1| |q2| ... |qn| ``)`` ``(of`` |q1| |q2| ... |qn| ``)`` ``(matching`` |q1| |q2| ... |qn| ``)`` ``(of`` |q1| |q2| ... |qn| ``)``
Match all messages have the same values of the current field as Match all messages have the same values of the current field as
those matching all of |q1| ... |qn|. Supported in most term [#not-path]_ or those matching all of |q1| ... |qn|. Supported in most term [#not-path]_ or
phrase fields. Most commonly used in the ``thread`` field. phrase fields. Most commonly used in the ``thread`` field.
``(query`` *atom* ``)`` ``(query`` *atom* ``)``
Expand to the saved query named by *atom*. See Expand to the saved query named by *atom*. See
:any:`notmuch-config(1)` for more. Note that the saved query must :any:`notmuch-config(1)` for more. Note that the saved query must
be in infix syntax (:any:`notmuch-search-terms(7)`). Not supported be in infix syntax (:any:`notmuch-search-terms(7)`). Not supported
inside fields. inside fields.
``(regex`` *atom* ``)`` ``(rx`` *atom* ``)`` ``(regex`` *atom* ``)`` ``(rx`` *atom* ``)``
Interpret *atom* as a POSIX.2 regular expression (see Interpret *atom* as a POSIX.2 regular expression (see
:manpage:`regex(7)`). This applies in term fields and a subset [#not-phrase]_ of :manpage:`regex(7)`). This applies in term fields and a subset [#not-phrase]_ of
phrase fields (see :any:`field-table`). phrase fields (see :any:`field-table`).
``(starts-with`` *subword* ``)`` ``(starts-with`` *subword* ``)``
Matches any term starting with *subword*. This applies in either Matches any term starting with *subword*. This applies in either
phrase or term :any:`fields <fields>`, or outside of fields [#not-body]_. Note that phrase or term :any:`fields <fields>`, or outside of fields [#not-body]_. Note that
a ``starts-with`` query cannot be part of a phrase. The a ``starts-with`` query cannot be part of a phrase. The
@ -193,63 +206,80 @@ EXAMPLES
======== ========
``Wizard`` ``Wizard``
Match all messages containing the word "wizard", ignoring case. Match all messages containing the word "wizard", ignoring case.
``added`` ``added``
Match all messages containing "added", but also those containing "add", "additional", Match all messages containing "added", but also those containing "add", "additional",
"Additional", "adds", etc... via stemming. "Additional", "adds", etc... via stemming.
``(and Bob Marley)`` ``(and Bob Marley)``
Match messages containing words "Bob" and "Marley", or their stems Match messages containing words "Bob" and "Marley", or their stems
The words need not be adjacent. The words need not be adjacent.
``(not Bob Marley)`` ``(not Bob Marley)``
Match messages containing neither "Bob" nor "Marley", nor their stems, Match messages containing neither "Bob" nor "Marley", nor their stems,
``"quick fox"`` ``quick-fox`` ``quick@fox`` ``"quick fox"`` ``quick-fox`` ``quick@fox``
Match the *phrase* "quick" followed by "fox" in phrase fields (or Match the *phrase* "quick" followed by "fox" in phrase fields (or
outside a field). Match the literal string in a term field. outside a field). Match the literal string in a term field.
``(folder (of (id 1234@invalid)))`` ``(folder (of (id 1234@invalid)))``
Match any message in the same folder as the one with Message-Id "1234@invalid" Match any message in the same folder as the one with Message-Id "1234@invalid"
``(id 1234@invalid blah@test)`` ``(id 1234@invalid blah@test)``
Matches Message-Id "1234@invalid" *or* Message-Id "blah@test" Matches Message-Id "1234@invalid" *or* Message-Id "blah@test"
``(and (infix "date:2009-11-18..2009-11-18") (tag unread))`` ``(and (infix "date:2009-11-18..2009-11-18") (tag unread))``
Match messages in the given date range with tag unread. Match messages in the given date range with tag unread.
``(starts-with prelim)`` ``(starts-with prelim)``
Match any words starting with "prelim". Match any words starting with "prelim".
``(subject quick "brown fox")`` ``(subject quick "brown fox")``
Match messages whose subject contains "quick" (anywhere, stemmed) and Match messages whose subject contains "quick" (anywhere, stemmed) and
the phrase "brown fox". the phrase "brown fox".
``(subject (starts-with prelim))`` ``(subject (starts-with prelim))``
Matches any word starting with "prelim", inside a message subject. Matches any word starting with "prelim", inside a message subject.
``(subject (starts-wih quick) "brown fox")`` ``(subject (starts-wih quick) "brown fox")``
Match messages whose subject contains "quick brown fox", but also Match messages whose subject contains "quick brown fox", but also
"brown fox quicksand". "brown fox quicksand".
``(thread (of (id 1234@invalid)))`` ``(thread (of (id 1234@invalid)))``
Match any message in the same thread as the one with Message-Id "1234@invalid" Match any message in the same thread as the one with Message-Id "1234@invalid"
``(thread (matching (from bob@example.com) (to bob@example.com)))`` ``(thread (matching (from bob@example.com) (to bob@example.com)))``
Match any (messages in) a thread containing a message from Match any (messages in) a thread containing a message from
"bob@example.com" and a (possibly distinct) message to "bob at "bob@example.com" and a (possibly distinct) message to "bob at
example.com") example.com")
``(to (or bob@example.com mallory@example.org))`` ``(or (to bob@example.com) (to mallory@example.org))`` ``(to (or bob@example.com mallory@example.org))`` ``(or (to bob@example.com) (to mallory@example.org))``
Match in the "To" or "Cc" headers, "bob@example.com", Match in the "To" or "Cc" headers, "bob@example.com",
"mallory@example.org", and also "bob@example.com.au" since it "mallory@example.org", and also "bob@example.com.au" since it
contains the adjacent triple "bob", "example", "com". contains the adjacent triple "bob", "example", "com".
``(not (to *))`` ``(not (to *))``
Match messages with an empty or invalid 'To' and 'Cc' field. Match messages with an empty or invalid 'To' and 'Cc' field.
``(List *)`` ``(List *)``
Match messages with a non-empty List-Id header, assuming Match messages with a non-empty List-Id header, assuming
configuration ``index.header.List=List-Id`` configuration ``index.header.List=List-Id``
@ -306,10 +336,10 @@ NOTES
in notmuch, this modifier is not supported in the in notmuch, this modifier is not supported in the
``path`` field. ``path`` field.
.. |q1| replace:: :math:`q_1` .. |q1| replace:: `q`\ :sub:`1`
.. |q2| replace:: :math:`q_2` .. |q2| replace:: `q`\ :sub:`2`
.. |qn| replace:: :math:`q_n` .. |qn| replace:: `q`\ :sub:`n`
.. |p1| replace:: :math:`p_1` .. |p1| replace:: `p`\ :sub:`1`
.. |p2| replace:: :math:`p_2` .. |p2| replace:: `p`\ :sub:`2`
.. |pn| replace:: :math:`p_n` .. |pn| replace:: `p`\ :sub:`n`

View file

@ -56,7 +56,7 @@ notmuch-hello key bindings
``<tab>`` ``<tab>``
Move to the next widget (button or text entry field) Move to the next widget (button or text entry field)
``<backspace>`` ``<backtab>``
Move to the previous widget. Move to the previous widget.
``<return>`` ``<return>``
@ -175,6 +175,16 @@ variables.
:index:`notmuch-search-oldest-first` :index:`notmuch-search-oldest-first`
Display the oldest threads at the top of the buffer Display the oldest threads at the top of the buffer
It is also possible to customize how the name of buffers containing
search results is formatted using the following variables:
:index:`notmuch-search-buffer-name-format`
|docstring::notmuch-search-buffer-name-format|
:index:`notmuch-saved-search-buffer-name-format`
|docstring::notmuch-saved-search-buffer-name-format|
.. _notmuch-show: .. _notmuch-show:
notmuch-show notmuch-show
@ -222,6 +232,9 @@ Display of messages can be controlled by the following variables
:index:`notmuch-message-headers-visible` :index:`notmuch-message-headers-visible`
|docstring::notmuch-message-headers-visible| |docstring::notmuch-message-headers-visible|
:index:`notmuch-show-header-line`
|docstring::notmuch-show-header-line|
.. _show-copy: .. _show-copy:
Copy to kill-ring Copy to kill-ring

View file

@ -42,7 +42,7 @@ emacs_mua := $(dir)/notmuch-emacs-mua
emacs_mua_desktop := $(dir)/notmuch-emacs-mua.desktop emacs_mua_desktop := $(dir)/notmuch-emacs-mua.desktop
emacs_images := \ emacs_images := \
$(srcdir)/$(dir)/notmuch-logo.png $(srcdir)/$(dir)/notmuch-logo.svg
emacs_bytecode = $(emacs_sources:.el=.elc) emacs_bytecode = $(emacs_sources:.el=.elc)
emacs_docstrings = $(emacs_sources:.el=.rsti) emacs_docstrings = $(emacs_sources:.el=.rsti)

View file

@ -45,7 +45,7 @@ Otherwise respect `fill-column'."
:group 'coolj :group 'coolj
:type 'boolean) :type 'boolean)
(defcustom coolj-line-prefix-regexp "^\\(>+ \\)*" (defcustom coolj-line-prefix-regexp "^\\(>+ ?\\)*"
"Regular expression that matches line prefixes." "Regular expression that matches line prefixes."
:group 'coolj :group 'coolj
:type 'regexp) :type 'regexp)

View file

@ -198,7 +198,7 @@ fields of the search."
(defvar notmuch-hello-indent 4 (defvar notmuch-hello-indent 4
"How much to indent non-headers.") "How much to indent non-headers.")
(defimage notmuch-hello-logo ((:type png :file "notmuch-logo.png"))) (defimage notmuch-hello-logo ((:type svg :file "notmuch-logo.svg")))
(defcustom notmuch-show-logo t (defcustom notmuch-show-logo t
"Should the notmuch logo be shown?" "Should the notmuch logo be shown?"
@ -486,11 +486,14 @@ diagonal."
(defun notmuch-hello-widget-search (widget &rest _ignore) (defun notmuch-hello-widget-search (widget &rest _ignore)
(cl-case (widget-get widget :notmuch-search-type) (cl-case (widget-get widget :notmuch-search-type)
(tree (tree
(let ((n (notmuch-search-format-buffer-name (widget-value widget) "tree" t)))
(notmuch-tree (widget-get widget :notmuch-search-terms) (notmuch-tree (widget-get widget :notmuch-search-terms)
nil nil nil nil nil nil nil nil n nil nil nil
(widget-get widget :notmuch-search-oldest-first))) (widget-get widget :notmuch-search-oldest-first))))
(unthreaded (unthreaded
(notmuch-unthreaded (widget-get widget :notmuch-search-terms))) (let ((n (notmuch-search-format-buffer-name (widget-value widget)
"unthreaded" t)))
(notmuch-unthreaded (widget-get widget :notmuch-search-terms) nil nil n)))
(t (t
(notmuch-search (widget-get widget :notmuch-search-terms) (notmuch-search (widget-get widget :notmuch-search-terms)
(widget-get widget :notmuch-search-oldest-first))))) (widget-get widget :notmuch-search-oldest-first)))))
@ -557,7 +560,8 @@ with any properties in the original saved-search.
The values :show-empty-searches, :filter and :filter-count from The values :show-empty-searches, :filter and :filter-count from
options will be handled as specified for options will be handled as specified for
`notmuch-hello-insert-searches'." `notmuch-hello-insert-searches'. :disable-includes can be used to
turn off the default exclude processing in `notmuch-count(1)'"
(with-temp-buffer (with-temp-buffer
(dolist (elem query-list nil) (dolist (elem query-list nil)
(let ((count-query (or (notmuch-saved-search-get elem :count-query) (let ((count-query (or (notmuch-saved-search-get elem :count-query)
@ -570,7 +574,11 @@ options will be handled as specified for
(plist-get options :filter)))) (plist-get options :filter))))
"\n"))) "\n")))
(unless (= (notmuch--call-process-region (point-min) (point-max) notmuch-command (unless (= (notmuch--call-process-region (point-min) (point-max) notmuch-command
t t nil "count" "--batch") 0) t t nil "count"
(if (plist-get options :disable-excludes)
"--exclude=false"
"--exclude=true")
"--batch") 0)
(notmuch-logged-error (notmuch-logged-error
"notmuch count --batch failed" "notmuch count --batch failed"
"Please check that the notmuch CLI is new enough to support `count "Please check that the notmuch CLI is new enough to support `count
@ -702,7 +710,6 @@ with `notmuch-hello-query-counts'."
;; that when we modify map it does not modify widget-keymap). ;; that when we modify map it does not modify widget-keymap).
(let ((map (make-composed-keymap (list (make-sparse-keymap) widget-keymap)))) (let ((map (make-composed-keymap (list (make-sparse-keymap) widget-keymap))))
(set-keymap-parent map notmuch-common-keymap) (set-keymap-parent map notmuch-common-keymap)
(define-key map (kbd "<C-tab>") 'widget-backward)
map) map)
"Keymap for \"notmuch hello\" buffers.") "Keymap for \"notmuch hello\" buffers.")
@ -786,7 +793,7 @@ Complete list of currently available key bindings:
:help-echo "Refresh" :help-echo "Refresh"
(notmuch-hello-nice-number (notmuch-hello-nice-number
(string-to-number (string-to-number
(car (notmuch--process-lines notmuch-command "count"))))) (car (notmuch--process-lines notmuch-command "count" "--exclude=false")))))
(widget-insert " messages.\n"))) (widget-insert " messages.\n")))
(defun notmuch-hello-insert-saved-searches () (defun notmuch-hello-insert-saved-searches ()
@ -918,7 +925,8 @@ following:
nil nil
:initially-hidden (not notmuch-show-all-tags-list) :initially-hidden (not notmuch-show-all-tags-list)
:hide-tags notmuch-hello-hide-tags :hide-tags notmuch-hello-hide-tags
:filter notmuch-hello-tag-list-make-query)) :filter notmuch-hello-tag-list-make-query
:disable-excludes t))
(defun notmuch-hello-insert-footer () (defun notmuch-hello-insert-footer ()
"Insert the notmuch-hello footer." "Insert the notmuch-hello footer."

27
emacs/notmuch-logo.svg Normal file
View file

@ -0,0 +1,27 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" width="100" height="100"
viewbox="0 0 100 100" fill="#000" stroke-width="2">
<circle cx="50" cy="5" r="5" />
<g transform="translate(50 20) rotate(20)">
<circle cx="-47" cy="0" r="3" />
<circle cx="47" cy="0" r="3" />
<path d="M-47 -1 L0 -3 L47 -1 L47 1 L0 3 L-47 1 Z" />
</g>
<path d="M49 4 L45 88
A5 5 0 0 1 40 93 L20 93 A5 5 0 0 0 15 100
L85 100
A5 5 0 0 0 80 93 L60 93 A5 5 0 0 1 55 88
L55 90 L51 4 Z" />
<g fill="#fff" stroke="#000">
<rect x="7" y="33" width="30" height="18" />
<line x1="7" y1="51" x2="18" y2="41" />
<line x1="37" y1="51" x2="26" y2="41" />
<polyline points="7 33 22 44 37 33" fill="none" />
</g>
<path d="M-18 0 A24 20 0 0 0 18 0" transform="translate(22 51.0)" />
<path d="M-18 0 A24 20 0 0 0 18 0" transform="translate(78 71.5)" />
<g fill="none" stroke="#000">
<path d="M9 53.0 l 12 -42 l 2 0 l 12 42" />
<path d="M91 73.5 l-12 -42 l-2 0 l-12 42" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View file

@ -149,6 +149,7 @@ Otherwise set it according to `notmuch-fcc-dirs'."
(buf (current-buffer)) (buf (current-buffer))
(mml-externalize-attachments message-fcc-externalize-attachments)) (mml-externalize-attachments message-fcc-externalize-attachments))
(with-current-buffer (get-buffer-create " *message temp*") (with-current-buffer (get-buffer-create " *message temp*")
(message-clone-locals buf) ;; for message-encoded-mail-cache
(erase-buffer) (erase-buffer)
(insert-buffer-substring buf) (insert-buffer-substring buf)
,@body))) ,@body)))
@ -158,7 +159,10 @@ Otherwise set it according to `notmuch-fcc-dirs'."
This should be called on a temporary copy. This should be called on a temporary copy.
This is taken from the function message-do-fcc." This is taken from the function message-do-fcc."
(if (not message-encoded-mail-cache)
(message-encode-message-body) (message-encode-message-body)
(erase-buffer)
(insert message-encoded-mail-cache))
(save-restriction (save-restriction
(message-narrow-to-headers) (message-narrow-to-headers)
(mail-encode-encoded-word-buffer)) (mail-encode-encoded-word-buffer))
@ -179,12 +183,12 @@ This is a rearranged version of message mode's message-do-fcc."
(setq file (message-fetch-field "fcc" t))) (setq file (message-fetch-field "fcc" t)))
(when file (when file
(with-temporary-notmuch-message-buffer (with-temporary-notmuch-message-buffer
(notmuch-maildir-setup-message-for-saving)
(save-restriction (save-restriction
(message-narrow-to-headers) (message-narrow-to-headers)
(while (setq file (message-fetch-field "fcc" t)) (while (setq file (message-fetch-field "fcc" t))
(push file files) (push file files)
(message-remove-header "fcc" nil t))) (message-remove-header "fcc" nil t)))
(notmuch-maildir-setup-message-for-saving)
;; Process FCC operations. ;; Process FCC operations.
(mapc #'notmuch-fcc-handler files) (mapc #'notmuch-fcc-handler files)
(kill-buffer (current-buffer))))))) (kill-buffer (current-buffer)))))))

View file

@ -84,6 +84,11 @@ visible for any given message."
:type 'boolean :type 'boolean
:group 'notmuch-show) :group 'notmuch-show)
(defcustom notmuch-show-header-line t
"Show a header line with the current message's subject."
:type 'boolean
:group 'notmuch-show)
(defcustom notmuch-show-relative-dates t (defcustom notmuch-show-relative-dates t
"Display relative dates in the message summary line." "Display relative dates in the message summary line."
:type 'boolean :type 'boolean
@ -1345,11 +1350,12 @@ If no messages match the query return NIL."
(notmuch-show-mapc (notmuch-show-mapc
(lambda () (notmuch-show-set-prop :orig-tags (notmuch-show-get-tags)))) (lambda () (notmuch-show-set-prop :orig-tags (notmuch-show-get-tags))))
;; Set the header line to the subject of the first message. ;; Set the header line to the subject of the first message.
(when notmuch-show-header-line
(setq header-line-format (setq header-line-format
(replace-regexp-in-string "%" "%%" (replace-regexp-in-string "%" "%%"
(notmuch-sanitize (notmuch-sanitize
(notmuch-show-strip-re (notmuch-show-strip-re
(notmuch-show-get-subject))))) (notmuch-show-get-subject))))))
(run-hooks 'notmuch-show-hook) (run-hooks 'notmuch-show-hook)
(if state (if state
(notmuch-show-apply-state state) (notmuch-show-apply-state state)

View file

@ -241,7 +241,7 @@ DATA is the content of an SVG picture (e.g., as returned by
"Return SVG data representing a star icon. "Return SVG data representing a star icon.
This can be used with `notmuch-tag-format-image-data'." This can be used with `notmuch-tag-format-image-data'."
"<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?> "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>
<svg version=\"1.1\" width=\"16\" height=\"16\"> <svg version=\"1.1\" width=\"16\" height=\"16\" xmlns=\"http://www.w3.org/2000/svg\">
<g transform=\"translate(-242.81601,-315.59635)\"> <g transform=\"translate(-242.81601,-315.59635)\">
<path <path
d=\"m 290.25762,334.31206 -17.64143,-11.77975 -19.70508,7.85447 5.75171,-20.41814 -13.55925,-16.31348 21.19618,-0.83936 11.325,-17.93675 7.34825,19.89939 20.55849,5.22795 -16.65471,13.13786 z\" d=\"m 290.25762,334.31206 -17.64143,-11.77975 -19.70508,7.85447 5.75171,-20.41814 -13.55925,-16.31348 21.19618,-0.83936 11.325,-17.93675 7.34825,19.89939 20.55849,5.22795 -16.65471,13.13786 z\"
@ -254,7 +254,7 @@ This can be used with `notmuch-tag-format-image-data'."
"Return SVG data representing an empty star icon. "Return SVG data representing an empty star icon.
This can be used with `notmuch-tag-format-image-data'." This can be used with `notmuch-tag-format-image-data'."
"<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?> "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>
<svg version=\"1.1\" width=\"16\" height=\"16\"> <svg version=\"1.1\" width=\"16\" height=\"16\" xmlns=\"http://www.w3.org/2000/svg\">
<g transform=\"translate(-242.81601,-315.59635)\"> <g transform=\"translate(-242.81601,-315.59635)\">
<path <path
d=\"m 290.25762,334.31206 -17.64143,-11.77975 -19.70508,7.85447 5.75171,-20.41814 -13.55925,-16.31348 21.19618,-0.83936 11.325,-17.93675 7.34825,19.89939 20.55849,5.22795 -16.65471,13.13786 z\" d=\"m 290.25762,334.31206 -17.64143,-11.77975 -19.70508,7.85447 5.75171,-20.41814 -13.55925,-16.31348 21.19618,-0.83936 11.325,-17.93675 7.34825,19.89939 20.55849,5.22795 -16.65471,13.13786 z\"
@ -267,7 +267,7 @@ This can be used with `notmuch-tag-format-image-data'."
"Return SVG data representing a tag icon. "Return SVG data representing a tag icon.
This can be used with `notmuch-tag-format-image-data'." This can be used with `notmuch-tag-format-image-data'."
"<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?> "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>
<svg version=\"1.1\" width=\"16\" height=\"16\"> <svg version=\"1.1\" width=\"16\" height=\"16\" xmlns=\"http://www.w3.org/2000/svg\">
<g transform=\"translate(0,-1036.3622)\"> <g transform=\"translate(0,-1036.3622)\">
<path <path
d=\"m 0.44642857,1040.9336 12.50000043,0 2.700893,3.6161 -2.700893,3.616 -12.50000043,0 z\" d=\"m 0.44642857,1040.9336 12.50000043,0 2.700893,3.6161 -2.700893,3.616 -12.50000043,0 z\"
@ -429,17 +429,9 @@ initial input in the minibuffer."
(set-keymap-parent map crm-local-completion-map) (set-keymap-parent map crm-local-completion-map)
(define-key map " " 'self-insert-command) (define-key map " " 'self-insert-command)
map))) map)))
(delete "" (completing-read-multiple (completing-read-multiple prompt tag-list
prompt
;; Append the separator to each completion so when the
;; user completes a tag they can immediately begin
;; entering another. `completing-read-multiple'
;; ultimately splits the input on crm-separator, so we
;; don't need to strip this back off (we just need to
;; delete "empty" entries caused by trailing spaces).
(mapcar (lambda (tag-op) (concat tag-op crm-separator)) tag-list)
nil nil initial-input nil nil initial-input
'notmuch-read-tag-changes-history)))) 'notmuch-read-tag-changes-history)))
;;; Tagging ;;; Tagging

View file

@ -179,7 +179,7 @@ Note that the author string should not contain whitespace
(:foreground "dark blue")) (:foreground "dark blue"))
(t (t
(:bold t))) (:bold t)))
"Face used in tree mode for the date in messages matching the query." "Face used in tree mode for the author in messages matching the query."
:group 'notmuch-tree :group 'notmuch-tree
:group 'notmuch-faces) :group 'notmuch-faces)
@ -236,7 +236,7 @@ Note that the author string should not contain whitespace
(defface notmuch-tree-no-match-author-face (defface notmuch-tree-no-match-author-face
nil nil
"Face used in tree mode for the date in messages matching the query." "Face used in tree mode for non-matching authors."
:group 'notmuch-tree :group 'notmuch-tree
:group 'notmuch-faces) :group 'notmuch-faces)
@ -1191,11 +1191,11 @@ The arguments are:
(setq query (notmuch-read-query (concat "Notmuch " (setq query (notmuch-read-query (concat "Notmuch "
(if unthreaded "unthreaded " "tree ") (if unthreaded "unthreaded " "tree ")
"view search: ")))) "view search: "))))
(let ((buffer (get-buffer-create (generate-new-buffer-name (let* ((name
(or buffer-name (or buffer-name
(concat "*notmuch-" (notmuch-search-buffer-title query
(if unthreaded "unthreaded-" "tree-") (if unthreaded "unthreaded" "tree"))))
query "*"))))) (buffer (get-buffer-create (generate-new-buffer-name name)))
(inhibit-read-only t)) (inhibit-read-only t))
(pop-to-buffer-same-window buffer)) (pop-to-buffer-same-window buffer))
;; Don't track undo information for this buffer ;; Don't track undo information for this buffer
@ -1206,6 +1206,9 @@ The arguments are:
(defun notmuch-unthreaded (&optional query query-context target buffer-name (defun notmuch-unthreaded (&optional query query-context target buffer-name
open-target) open-target)
"Display threads matching QUERY in unthreaded view.
See function NOTMUCH-TREE for documentation of the arguments"
(interactive) (interactive)
(notmuch-tree query query-context target buffer-name open-target t)) (notmuch-tree query query-context target buffer-name open-target t))

View file

@ -535,12 +535,12 @@ thread."
(message "End of search results.")))) (message "End of search results."))))
(defun notmuch-tree-from-search-current-query () (defun notmuch-tree-from-search-current-query ()
"Call notmuch tree with the current query." "Tree view of current query."
(interactive) (interactive)
(notmuch-tree notmuch-search-query-string)) (notmuch-tree notmuch-search-query-string))
(defun notmuch-unthreaded-from-search-current-query () (defun notmuch-unthreaded-from-search-current-query ()
"Call notmuch tree with the current query." "Unthreaded view of current query."
(interactive) (interactive)
(notmuch-unthreaded notmuch-search-query-string)) (notmuch-unthreaded notmuch-search-query-string))
@ -880,6 +880,14 @@ sets the :orig-tag property."
(setq notmuch-search-target-thread "found") (setq notmuch-search-target-thread "found")
(goto-char pos)))) (goto-char pos))))
(defvar-local notmuch--search-hook-run nil
"Flag used to ensure the notmuch-search-hook is only run once per buffer")
(defun notmuch--search-hook-wrapper ()
(unless notmuch--search-hook-run
(setq notmuch--search-hook-run t)
(run-hooks 'notmuch-search-hook)))
(defun notmuch-search-process-filter (proc string) (defun notmuch-search-process-filter (proc string)
"Process and filter the output of \"notmuch search\"." "Process and filter the output of \"notmuch search\"."
(let ((results-buf (process-buffer proc)) (let ((results-buf (process-buffer proc))
@ -892,7 +900,9 @@ sets the :orig-tag property."
(goto-char (point-max)) (goto-char (point-max))
(insert string)) (insert string))
(notmuch-sexp-parse-partial-list 'notmuch-search-append-result (notmuch-sexp-parse-partial-list 'notmuch-search-append-result
results-buf))))) results-buf))
(with-current-buffer results-buf
(notmuch--search-hook-wrapper)))))
;;; Commands (and some helper functions used by them) ;;; Commands (and some helper functions used by them)
@ -905,7 +915,39 @@ See `notmuch-tag' for information on the format of TAG-CHANGES."
(notmuch-search-get-tags-region (point-min) (point-max)) "Tag all"))) (notmuch-search-get-tags-region (point-min) (point-max)) "Tag all")))
(notmuch-search-tag tag-changes (point-min) (point-max) t)) (notmuch-search-tag tag-changes (point-min) (point-max) t))
(defun notmuch-search-buffer-title (query) (defcustom notmuch-search-buffer-name-format "*notmuch-%t-%s*"
"Format for the name of search results buffers.
In this spec, %s will be replaced by a description of the search
query and %t by its type (search, tree or unthreaded). The
buffer name is formatted using `format-spec': see its docstring
for additional parameters for the s and t format specifiers.
See also `notmuch-saved-search-buffer-name-format'"
:type 'string
:group 'notmuch-search)
(defcustom notmuch-saved-search-buffer-name-format "*notmuch-saved-%t-%s*"
"Format for the name of search results buffers for saved searches.
In this spec, %s will be replaced by the saved search name and %t
by its type (search, tree or unthreaded). The buffer name is
formatted using `format-spec': see its docstring for additional
parameters for the s and t format specifiers.
See also `notmuch-search-buffer-name-format'"
:type 'string
:group 'notmuch-search)
(defun notmuch-search-format-buffer-name (query type saved)
"Compose a buffer name for the given QUERY, TYPE (search, tree,
unthreaded) and whether it's SAVED (t or nil)."
(let ((fmt (if saved
notmuch-saved-search-buffer-name-format
notmuch-search-buffer-name-format)))
(format-spec fmt `((?t . ,(or type "search")) (?s . ,query)))))
(defun notmuch-search-buffer-title (query &optional type)
"Returns the title for a buffer with notmuch search results." "Returns the title for a buffer with notmuch search results."
(let* ((saved-search (let* ((saved-search
(let (longest (let (longest
@ -920,19 +962,20 @@ See `notmuch-tag' for information on the format of TAG-CHANGES."
do (setq longest tuple)) do (setq longest tuple))
longest)) longest))
(saved-search-name (notmuch-saved-search-get saved-search :name)) (saved-search-name (notmuch-saved-search-get saved-search :name))
(saved-search-type (notmuch-saved-search-get saved-search :search-type))
(saved-search-query (notmuch-saved-search-get saved-search :query))) (saved-search-query (notmuch-saved-search-get saved-search :query)))
(cond ((and saved-search (equal saved-search-query query)) (cond ((and saved-search (equal saved-search-query query))
;; Query is the same as saved search (ignoring case) ;; Query is the same as saved search (ignoring case)
(concat "*notmuch-saved-search-" saved-search-name "*")) (notmuch-search-format-buffer-name saved-search-name
saved-search-type
t))
(saved-search (saved-search
(concat "*notmuch-search-" (let ((query (replace-regexp-in-string
(replace-regexp-in-string
(concat "^" (regexp-quote saved-search-query)) (concat "^" (regexp-quote saved-search-query))
(concat "[ " saved-search-name " ]") (concat "[ " saved-search-name " ]")
query) query)))
"*")) (notmuch-search-format-buffer-name query saved-search-type t)))
(t (t (notmuch-search-format-buffer-name query type nil)))))
(concat "*notmuch-search-" query "*")))))
(defun notmuch-read-query (prompt) (defun notmuch-read-query (prompt)
"Read a notmuch-query from the minibuffer with completion. "Read a notmuch-query from the minibuffer with completion.
@ -1036,8 +1079,7 @@ the configured default sort order."
(process-put proc 'parse-buf (process-put proc 'parse-buf
(generate-new-buffer " *notmuch search parse*")) (generate-new-buffer " *notmuch search parse*"))
(set-process-filter proc 'notmuch-search-process-filter) (set-process-filter proc 'notmuch-search-process-filter)
(set-process-query-on-exit-flag proc nil)))) (set-process-query-on-exit-flag proc nil))))))
(run-hooks 'notmuch-search-hook)))
(defun notmuch-search-refresh-view () (defun notmuch-search-refresh-view ()
"Refresh the current view. "Refresh the current view.

View file

@ -32,7 +32,7 @@ notmuch_built_with (const char *name)
return HAVE_XAPIAN_DB_RETRY_LOCK; return HAVE_XAPIAN_DB_RETRY_LOCK;
} else if (STRNCMP_LITERAL (name, "session_key") == 0) { } else if (STRNCMP_LITERAL (name, "session_key") == 0) {
return true; return true;
} else if (STRNCMP_LITERAL (name, "sexpr_query") == 0) { } else if (STRNCMP_LITERAL (name, "sexp_queries") == 0) {
return HAVE_SFSEXP; return HAVE_SFSEXP;
} else { } else {
return false; return false;

View file

@ -435,11 +435,6 @@ _notmuch_config_load_from_file (notmuch_database_t *notmuch,
for (gchar **keys_p = keys; *keys_p; keys_p++) { for (gchar **keys_p = keys; *keys_p; keys_p++) {
char *absolute_key = talloc_asprintf (notmuch, "%s.%s", *grp, *keys_p); char *absolute_key = talloc_asprintf (notmuch, "%s.%s", *grp, *keys_p);
char *normalized_val; char *normalized_val;
val = g_key_file_get_value (file, *grp, *keys_p, NULL);
if (! val) {
status = NOTMUCH_STATUS_FILE_ERROR;
goto DONE;
}
/* If we opened from a given path, do not overwrite it */ /* If we opened from a given path, do not overwrite it */
if (strcmp (absolute_key, "database.path") == 0 && if (strcmp (absolute_key, "database.path") == 0 &&
@ -447,6 +442,12 @@ _notmuch_config_load_from_file (notmuch_database_t *notmuch,
notmuch->xapian_db) notmuch->xapian_db)
continue; continue;
val = g_key_file_get_string (file, *grp, *keys_p, NULL);
if (! val) {
status = NOTMUCH_STATUS_FILE_ERROR;
goto DONE;
}
normalized_val = _expand_path (notmuch, absolute_key, val); normalized_val = _expand_path (notmuch, absolute_key, val);
_notmuch_string_map_set (notmuch->config, absolute_key, normalized_val); _notmuch_string_map_set (notmuch->config, absolute_key, normalized_val);
g_free (val); g_free (val);
@ -596,6 +597,8 @@ _notmuch_config_key_to_string (notmuch_config_key_t key)
return "user.name"; return "user.name";
case NOTMUCH_CONFIG_AUTOCOMMIT: case NOTMUCH_CONFIG_AUTOCOMMIT:
return "database.autocommit"; return "database.autocommit";
case NOTMUCH_CONFIG_EXTRA_HEADERS:
return "show.extra_headers";
default: default:
return NULL; return NULL;
} }
@ -643,6 +646,7 @@ _notmuch_config_default (notmuch_database_t *notmuch, notmuch_config_key_t key)
return ""; return "";
case NOTMUCH_CONFIG_AUTOCOMMIT: case NOTMUCH_CONFIG_AUTOCOMMIT:
return "8000"; return "8000";
case NOTMUCH_CONFIG_EXTRA_HEADERS:
case NOTMUCH_CONFIG_HOOK_DIR: case NOTMUCH_CONFIG_HOOK_DIR:
case NOTMUCH_CONFIG_BACKUP_DIR: case NOTMUCH_CONFIG_BACKUP_DIR:
case NOTMUCH_CONFIG_OTHER_EMAIL: case NOTMUCH_CONFIG_OTHER_EMAIL:
@ -657,6 +661,10 @@ notmuch_status_t
_notmuch_config_load_defaults (notmuch_database_t *notmuch) _notmuch_config_load_defaults (notmuch_database_t *notmuch)
{ {
notmuch_config_key_t key; notmuch_config_key_t key;
notmuch_status_t status = NOTMUCH_STATUS_SUCCESS;
if (notmuch->config == NULL)
notmuch->config = _notmuch_string_map_create (notmuch);
for (key = NOTMUCH_CONFIG_FIRST; for (key = NOTMUCH_CONFIG_FIRST;
key < NOTMUCH_CONFIG_LAST; key < NOTMUCH_CONFIG_LAST;
@ -666,11 +674,14 @@ _notmuch_config_load_defaults (notmuch_database_t *notmuch)
val = _notmuch_string_map_get (notmuch->config, key_string); val = _notmuch_string_map_get (notmuch->config, key_string);
if (! val) { if (! val) {
if (key == NOTMUCH_CONFIG_MAIL_ROOT && (notmuch->params & NOTMUCH_PARAM_SPLIT))
status = NOTMUCH_STATUS_NO_MAIL_ROOT;
_notmuch_string_map_set (notmuch->config, key_string, _notmuch_config_default (notmuch, _notmuch_string_map_set (notmuch->config, key_string, _notmuch_config_default (notmuch,
key)); key));
} }
} }
return NOTMUCH_STATUS_SUCCESS; return status;
} }
const char * const char *

View file

@ -160,11 +160,12 @@ operator&= (_notmuch_features &a, _notmuch_features b)
/* /*
* Configuration options for xapian database fields */ * Configuration options for xapian database fields */
typedef enum notmuch_field_flags { typedef enum {
NOTMUCH_FIELD_NO_FLAGS = 0, NOTMUCH_FIELD_NO_FLAGS = 0,
NOTMUCH_FIELD_EXTERNAL = 1 << 0, NOTMUCH_FIELD_EXTERNAL = 1 << 0,
NOTMUCH_FIELD_PROBABILISTIC = 1 << 1, NOTMUCH_FIELD_PROBABILISTIC = 1 << 1,
NOTMUCH_FIELD_PROCESSOR = 1 << 2, NOTMUCH_FIELD_PROCESSOR = 1 << 2,
NOTMUCH_FIELD_STRIP_TRAILING_SLASH = 1 << 3,
} notmuch_field_flag_t; } notmuch_field_flag_t;
/* /*
@ -191,12 +192,17 @@ operator& (notmuch_field_flag_t a, notmuch_field_flag_t b)
Xapian::QueryParser::FLAG_PURE_NOT) Xapian::QueryParser::FLAG_PURE_NOT)
/* /*
* Which parameters were explicit when the database was opened */ * explicit and implied parameters to open */
typedef enum { typedef enum {
NOTMUCH_PARAM_NONE = 0, NOTMUCH_PARAM_NONE = 0,
/* database passed explicitely */
NOTMUCH_PARAM_DATABASE = 1 << 0, NOTMUCH_PARAM_DATABASE = 1 << 0,
/* config file passed explicitely */
NOTMUCH_PARAM_CONFIG = 1 << 1, NOTMUCH_PARAM_CONFIG = 1 << 1,
/* profile name passed explicitely */
NOTMUCH_PARAM_PROFILE = 1 << 2, NOTMUCH_PARAM_PROFILE = 1 << 2,
/* split (e.g. XDG) configuration */
NOTMUCH_PARAM_SPLIT = 1 << 3,
} notmuch_open_param_t; } notmuch_open_param_t;
/* /*
@ -374,5 +380,10 @@ notmuch_status_t
_notmuch_sexp_string_to_xapian_query (notmuch_database_t *notmuch, const char *querystr, _notmuch_sexp_string_to_xapian_query (notmuch_database_t *notmuch, const char *querystr,
Xapian::Query &output); Xapian::Query &output);
#endif #endif
/* parse-time-vrp.h */
notmuch_status_t
_notmuch_date_strings_to_query (Xapian::valueno slot, const std::string &from, const std::string &to,
Xapian::Query &output, std::string &msg);
#endif #endif
#endif #endif

View file

@ -311,6 +311,8 @@ notmuch_status_to_string (notmuch_status_t status)
return "Database exists, not recreated"; return "Database exists, not recreated";
case NOTMUCH_STATUS_BAD_QUERY_SYNTAX: case NOTMUCH_STATUS_BAD_QUERY_SYNTAX:
return "Syntax error in query"; return "Syntax error in query";
case NOTMUCH_STATUS_NO_MAIL_ROOT:
return "No mail root found";
default: default:
case NOTMUCH_STATUS_LAST_STATUS: case NOTMUCH_STATUS_LAST_STATUS:
return "Unknown error status value"; return "Unknown error status value";
@ -590,8 +592,10 @@ notmuch_database_compact (const char *path,
notmuch_database_t *notmuch = NULL; notmuch_database_t *notmuch = NULL;
char *message = NULL; char *message = NULL;
ret = notmuch_database_open_verbose (path, ret = notmuch_database_open_with_config (path,
NOTMUCH_DATABASE_MODE_READ_WRITE, NOTMUCH_DATABASE_MODE_READ_WRITE,
"",
NULL,
&notmuch, &notmuch,
&message); &message);
if (ret) { if (ret) {
@ -751,6 +755,8 @@ notmuch_database_destroy (notmuch_database_t *notmuch)
notmuch->date_range_processor = NULL; notmuch->date_range_processor = NULL;
delete notmuch->last_mod_range_processor; delete notmuch->last_mod_range_processor;
notmuch->last_mod_range_processor = NULL; notmuch->last_mod_range_processor = NULL;
delete notmuch->stemmer;
notmuch->stemmer = NULL;
talloc_free (notmuch); talloc_free (notmuch);

View file

@ -20,6 +20,10 @@
#include "notmuch-private.h" #include "notmuch-private.h"
struct _notmuch_indexopts {
_notmuch_crypto_t crypto;
};
notmuch_indexopts_t * notmuch_indexopts_t *
notmuch_database_get_default_indexopts (notmuch_database_t *db) notmuch_database_get_default_indexopts (notmuch_database_t *db)
{ {

View file

@ -121,7 +121,7 @@ typedef enum {
*/ */
#define NOTMUCH_MESSAGE_ID_MAX (200 - sizeof (NOTMUCH_METADATA_THREAD_ID_PREFIX)) #define NOTMUCH_MESSAGE_ID_MAX (200 - sizeof (NOTMUCH_METADATA_THREAD_ID_PREFIX))
typedef enum _notmuch_private_status { typedef enum {
/* First, copy all the public status values. */ /* First, copy all the public status values. */
NOTMUCH_PRIVATE_STATUS_SUCCESS = NOTMUCH_STATUS_SUCCESS, NOTMUCH_PRIVATE_STATUS_SUCCESS = NOTMUCH_STATUS_SUCCESS,
NOTMUCH_PRIVATE_STATUS_OUT_OF_MEMORY = NOTMUCH_STATUS_OUT_OF_MEMORY, NOTMUCH_PRIVATE_STATUS_OUT_OF_MEMORY = NOTMUCH_STATUS_OUT_OF_MEMORY,
@ -173,7 +173,7 @@ typedef enum _notmuch_private_status {
(notmuch_status_t) private_status) (notmuch_status_t) private_status)
/* Flags shared by various lookup functions. */ /* Flags shared by various lookup functions. */
typedef enum _notmuch_find_flags { typedef enum {
/* Lookup without creating any documents. This is the default /* Lookup without creating any documents. This is the default
* behavior. */ * behavior. */
NOTMUCH_FIND_LOOKUP = 0, NOTMUCH_FIND_LOOKUP = 0,
@ -711,9 +711,7 @@ _notmuch_thread_create (void *ctx,
/* indexopts.c */ /* indexopts.c */
struct _notmuch_indexopts { struct _notmuch_indexopts;
_notmuch_crypto_t crypto;
};
#define CONFIG_HEADER_PREFIX "index.header." #define CONFIG_HEADER_PREFIX "index.header."

View file

@ -58,7 +58,7 @@ NOTMUCH_BEGIN_DECLS
* version in Makefile.local. * version in Makefile.local.
*/ */
#define LIBNOTMUCH_MAJOR_VERSION 5 #define LIBNOTMUCH_MAJOR_VERSION 5
#define LIBNOTMUCH_MINOR_VERSION 5 #define LIBNOTMUCH_MINOR_VERSION 6
#define LIBNOTMUCH_MICRO_VERSION 0 #define LIBNOTMUCH_MICRO_VERSION 0
@ -112,7 +112,7 @@ typedef int notmuch_bool_t;
* A zero value (NOTMUCH_STATUS_SUCCESS) indicates that the function * A zero value (NOTMUCH_STATUS_SUCCESS) indicates that the function
* completed without error. Any other value indicates an error. * completed without error. Any other value indicates an error.
*/ */
typedef enum _notmuch_status { typedef enum {
/** /**
* No error occurred. * No error occurred.
*/ */
@ -224,6 +224,10 @@ typedef enum _notmuch_status {
* Syntax error in query * Syntax error in query
*/ */
NOTMUCH_STATUS_BAD_QUERY_SYNTAX, NOTMUCH_STATUS_BAD_QUERY_SYNTAX,
/**
* No mail root could be deduced from parameters and environment
*/
NOTMUCH_STATUS_NO_MAIL_ROOT,
/** /**
* Not an actual status value. Just a way to find out how many * Not an actual status value. Just a way to find out how many
* valid status values there are. * valid status values there are.
@ -323,7 +327,7 @@ typedef enum {
* config_path="" and error_message=NULL * config_path="" and error_message=NULL
* @deprecated Deprecated as of libnotmuch 5.4 (notmuch 0.32) * @deprecated Deprecated as of libnotmuch 5.4 (notmuch 0.32)
*/ */
/* NOTMUCH_DEPRECATED(5, 4) */ NOTMUCH_DEPRECATED(5, 4)
notmuch_status_t notmuch_status_t
notmuch_database_open (const char *path, notmuch_database_open (const char *path,
notmuch_database_mode_t mode, notmuch_database_mode_t mode,
@ -335,7 +339,7 @@ notmuch_database_open (const char *path,
* @deprecated Deprecated as of libnotmuch 5.4 (notmuch 0.32) * @deprecated Deprecated as of libnotmuch 5.4 (notmuch 0.32)
* *
*/ */
/* NOTMUCH_DEPRECATED(5, 4) */ NOTMUCH_DEPRECATED(5, 4)
notmuch_status_t notmuch_status_t
notmuch_database_open_verbose (const char *path, notmuch_database_open_verbose (const char *path,
notmuch_database_mode_t mode, notmuch_database_mode_t mode,
@ -1686,7 +1690,7 @@ notmuch_message_reindex (notmuch_message_t *message,
/** /**
* Message flags. * Message flags.
*/ */
typedef enum _notmuch_message_flag { typedef enum {
NOTMUCH_MESSAGE_FLAG_MATCH, NOTMUCH_MESSAGE_FLAG_MATCH,
NOTMUCH_MESSAGE_FLAG_EXCLUDED, NOTMUCH_MESSAGE_FLAG_EXCLUDED,
@ -2532,7 +2536,7 @@ notmuch_config_list_destroy (notmuch_config_list_t *config_list);
/** /**
* Configuration keys known to libnotmuch * Configuration keys known to libnotmuch
*/ */
typedef enum _notmuch_config_key { typedef enum {
NOTMUCH_CONFIG_FIRST, NOTMUCH_CONFIG_FIRST,
NOTMUCH_CONFIG_DATABASE_PATH = NOTMUCH_CONFIG_FIRST, NOTMUCH_CONFIG_DATABASE_PATH = NOTMUCH_CONFIG_FIRST,
NOTMUCH_CONFIG_MAIL_ROOT, NOTMUCH_CONFIG_MAIL_ROOT,
@ -2546,6 +2550,7 @@ typedef enum _notmuch_config_key {
NOTMUCH_CONFIG_OTHER_EMAIL, NOTMUCH_CONFIG_OTHER_EMAIL,
NOTMUCH_CONFIG_USER_NAME, NOTMUCH_CONFIG_USER_NAME,
NOTMUCH_CONFIG_AUTOCOMMIT, NOTMUCH_CONFIG_AUTOCOMMIT,
NOTMUCH_CONFIG_EXTRA_HEADERS,
NOTMUCH_CONFIG_LAST NOTMUCH_CONFIG_LAST
} notmuch_config_key_t; } notmuch_config_key_t;

View file

@ -19,9 +19,8 @@ notmuch_database_open (const char *path,
char *status_string = NULL; char *status_string = NULL;
notmuch_status_t status; notmuch_status_t status;
status = notmuch_database_open_verbose (path, mode, database, status = notmuch_database_open_with_config (path, mode, "", NULL,
&status_string); database, &status_string);
if (status_string) { if (status_string) {
fputs (status_string, stderr); fputs (status_string, stderr);
free (status_string); free (status_string);
@ -187,11 +186,10 @@ _db_dir_exists (const char *database_path, char **message)
} }
static notmuch_status_t static notmuch_status_t
_choose_database_path (void *ctx, _choose_database_path (notmuch_database_t *notmuch,
const char *profile, const char *profile,
GKeyFile *key_file, GKeyFile *key_file,
const char **database_path, const char **database_path,
bool *split,
char **message) char **message)
{ {
if (! *database_path) { if (! *database_path) {
@ -199,24 +197,24 @@ _choose_database_path (void *ctx,
} }
if (! *database_path && key_file) { if (! *database_path && key_file) {
char *path = g_key_file_get_value (key_file, "database", "path", NULL); char *path = g_key_file_get_string (key_file, "database", "path", NULL);
if (path) { if (path) {
if (path[0] == '/') if (path[0] == '/')
*database_path = talloc_strdup (ctx, path); *database_path = talloc_strdup (notmuch, path);
else else
*database_path = talloc_asprintf (ctx, "%s/%s", getenv ("HOME"), path); *database_path = talloc_asprintf (notmuch, "%s/%s", getenv ("HOME"), path);
g_free (path); g_free (path);
} }
} }
if (! *database_path) { if (! *database_path) {
notmuch_status_t status; notmuch_status_t status;
*database_path = _xdg_dir (ctx, "XDG_DATA_HOME", ".local/share", profile); *database_path = _xdg_dir (notmuch, "XDG_DATA_HOME", ".local/share", profile);
status = _db_dir_exists (*database_path, message); status = _db_dir_exists (*database_path, message);
if (status) { if (status) {
*database_path = NULL; *database_path = NULL;
} else { } else {
*split = true; notmuch->params |= NOTMUCH_PARAM_SPLIT;
} }
} }
@ -227,7 +225,7 @@ _choose_database_path (void *ctx,
if (! *database_path) { if (! *database_path) {
notmuch_status_t status; notmuch_status_t status;
*database_path = talloc_asprintf (ctx, "%s/mail", getenv ("HOME")); *database_path = talloc_asprintf (notmuch, "%s/mail", getenv ("HOME"));
status = _db_dir_exists (*database_path, message); status = _db_dir_exists (*database_path, message);
if (status) { if (status) {
*database_path = NULL; *database_path = NULL;
@ -511,11 +509,9 @@ notmuch_database_open_with_config (const char *database_path,
char **status_string) char **status_string)
{ {
notmuch_status_t status = NOTMUCH_STATUS_SUCCESS; notmuch_status_t status = NOTMUCH_STATUS_SUCCESS;
void *local = talloc_new (NULL);
notmuch_database_t *notmuch = NULL; notmuch_database_t *notmuch = NULL;
char *message = NULL; char *message = NULL;
GKeyFile *key_file = NULL; GKeyFile *key_file = NULL;
bool split = false;
_notmuch_init (); _notmuch_init ();
@ -531,8 +527,8 @@ notmuch_database_open_with_config (const char *database_path,
goto DONE; goto DONE;
} }
if ((status = _choose_database_path (local, profile, key_file, if ((status = _choose_database_path (notmuch, profile, key_file,
&database_path, &split, &database_path,
&message))) &message)))
goto DONE; goto DONE;
@ -550,8 +546,6 @@ notmuch_database_open_with_config (const char *database_path,
status = _finish_open (notmuch, profile, mode, key_file, &message); status = _finish_open (notmuch, profile, mode, key_file, &message);
DONE: DONE:
talloc_free (local);
if (key_file) if (key_file)
g_key_file_free (key_file); g_key_file_free (key_file);
@ -613,9 +607,7 @@ notmuch_database_create_with_config (const char *database_path,
const char *notmuch_path = NULL; const char *notmuch_path = NULL;
char *message = NULL; char *message = NULL;
GKeyFile *key_file = NULL; GKeyFile *key_file = NULL;
void *local = talloc_new (NULL);
int err; int err;
bool split = false;
_notmuch_init (); _notmuch_init ();
@ -631,8 +623,8 @@ notmuch_database_create_with_config (const char *database_path,
goto DONE; goto DONE;
} }
if ((status = _choose_database_path (local, profile, key_file, if ((status = _choose_database_path (notmuch, profile, key_file,
&database_path, &split, &message))) &database_path, &message)))
goto DONE; goto DONE;
status = _db_dir_exists (database_path, &message); status = _db_dir_exists (database_path, &message);
@ -641,39 +633,36 @@ notmuch_database_create_with_config (const char *database_path,
_set_database_path (notmuch, database_path); _set_database_path (notmuch, database_path);
if (key_file && ! split) { if (key_file && ! (notmuch->params & NOTMUCH_PARAM_SPLIT)) {
char *mail_root = notmuch_canonicalize_file_name ( char *mail_root = notmuch_canonicalize_file_name (
g_key_file_get_value (key_file, "database", "mail_root", NULL)); g_key_file_get_string (key_file, "database", "mail_root", NULL));
char *db_path = notmuch_canonicalize_file_name (database_path); char *db_path = notmuch_canonicalize_file_name (database_path);
split = (mail_root && (0 != strcmp (mail_root, db_path))); if (mail_root && (0 != strcmp (mail_root, db_path)))
notmuch->params |= NOTMUCH_PARAM_SPLIT;
free (mail_root); free (mail_root);
free (db_path); free (db_path);
} }
if (split) { if (notmuch->params & NOTMUCH_PARAM_SPLIT) {
notmuch_path = database_path; notmuch_path = database_path;
} else { } else {
if (! (notmuch_path = talloc_asprintf (local, "%s/%s", database_path, ".notmuch"))) { if (! (notmuch_path = talloc_asprintf (notmuch, "%s/%s", database_path, ".notmuch"))) {
status = NOTMUCH_STATUS_OUT_OF_MEMORY; status = NOTMUCH_STATUS_OUT_OF_MEMORY;
goto DONE; goto DONE;
} }
err = mkdir (notmuch_path, 0755); err = mkdir (notmuch_path, 0755);
if (err) { if (err) {
if (errno == EEXIST) { if (errno != EEXIST) {
status = NOTMUCH_STATUS_DATABASE_EXISTS;
talloc_free (notmuch);
notmuch = NULL;
} else {
IGNORE_RESULT (asprintf (&message, "Error: Cannot create directory %s: %s.\n", IGNORE_RESULT (asprintf (&message, "Error: Cannot create directory %s: %s.\n",
notmuch_path, strerror (errno))); notmuch_path, strerror (errno)));
status = NOTMUCH_STATUS_FILE_ERROR; status = NOTMUCH_STATUS_FILE_ERROR;
}
goto DONE; goto DONE;
} }
} }
}
if (! (notmuch->xapian_path = talloc_asprintf (notmuch, "%s/%s", notmuch_path, "xapian"))) { if (! (notmuch->xapian_path = talloc_asprintf (notmuch, "%s/%s", notmuch_path, "xapian"))) {
status = NOTMUCH_STATUS_OUT_OF_MEMORY; status = NOTMUCH_STATUS_OUT_OF_MEMORY;
@ -712,8 +701,6 @@ notmuch_database_create_with_config (const char *database_path,
} }
DONE: DONE:
talloc_free (local);
if (key_file) if (key_file)
g_key_file_free (key_file); g_key_file_free (key_file);
@ -813,11 +800,9 @@ notmuch_database_load_config (const char *database_path,
char **status_string) char **status_string)
{ {
notmuch_status_t status = NOTMUCH_STATUS_SUCCESS, warning = NOTMUCH_STATUS_SUCCESS; notmuch_status_t status = NOTMUCH_STATUS_SUCCESS, warning = NOTMUCH_STATUS_SUCCESS;
void *local = talloc_new (NULL);
notmuch_database_t *notmuch = NULL; notmuch_database_t *notmuch = NULL;
char *message = NULL; char *message = NULL;
GKeyFile *key_file = NULL; GKeyFile *key_file = NULL;
bool split = false;
_notmuch_init (); _notmuch_init ();
@ -839,8 +824,8 @@ notmuch_database_load_config (const char *database_path,
goto DONE; goto DONE;
} }
status = _choose_database_path (local, profile, key_file, status = _choose_database_path (notmuch, profile, key_file,
&database_path, &split, &message); &database_path, &message);
switch (status) { switch (status) {
case NOTMUCH_STATUS_NO_DATABASE: case NOTMUCH_STATUS_NO_DATABASE:
case NOTMUCH_STATUS_SUCCESS: case NOTMUCH_STATUS_SUCCESS:
@ -875,8 +860,6 @@ notmuch_database_load_config (const char *database_path,
goto DONE; goto DONE;
DONE: DONE:
talloc_free (local);
if (status_string) if (status_string)
*status_string = message; *status_string = message;

View file

@ -32,6 +32,8 @@ typedef enum {
SEXP_FLAG_EXPAND = 1 << 6, SEXP_FLAG_EXPAND = 1 << 6,
SEXP_FLAG_DO_EXPAND = 1 << 7, SEXP_FLAG_DO_EXPAND = 1 << 7,
SEXP_FLAG_ORPHAN = 1 << 8, SEXP_FLAG_ORPHAN = 1 << 8,
SEXP_FLAG_RANGE = 1 << 9,
SEXP_FLAG_PATHNAME = 1 << 10,
} _sexp_flag_t; } _sexp_flag_t;
/* /*
@ -66,16 +68,21 @@ static _sexp_prefix_t prefixes[] =
SEXP_FLAG_FIELD | SEXP_FLAG_WILDCARD | SEXP_FLAG_EXPAND }, SEXP_FLAG_FIELD | SEXP_FLAG_WILDCARD | SEXP_FLAG_EXPAND },
{ "body", Xapian::Query::OP_AND, Xapian::Query::MatchAll, { "body", Xapian::Query::OP_AND, Xapian::Query::MatchAll,
SEXP_FLAG_FIELD }, SEXP_FLAG_FIELD },
{ "date", Xapian::Query::OP_INVALID, Xapian::Query::MatchAll,
SEXP_FLAG_RANGE },
{ "from", Xapian::Query::OP_AND, Xapian::Query::MatchAll, { "from", Xapian::Query::OP_AND, Xapian::Query::MatchAll,
SEXP_FLAG_FIELD | SEXP_FLAG_WILDCARD | SEXP_FLAG_REGEX | SEXP_FLAG_EXPAND }, SEXP_FLAG_FIELD | SEXP_FLAG_WILDCARD | SEXP_FLAG_REGEX | SEXP_FLAG_EXPAND },
{ "folder", Xapian::Query::OP_OR, Xapian::Query::MatchNothing, { "folder", Xapian::Query::OP_OR, Xapian::Query::MatchNothing,
SEXP_FLAG_FIELD | SEXP_FLAG_BOOLEAN | SEXP_FLAG_WILDCARD | SEXP_FLAG_REGEX | SEXP_FLAG_EXPAND }, SEXP_FLAG_FIELD | SEXP_FLAG_BOOLEAN | SEXP_FLAG_WILDCARD | SEXP_FLAG_REGEX | SEXP_FLAG_EXPAND |
SEXP_FLAG_PATHNAME },
{ "id", Xapian::Query::OP_OR, Xapian::Query::MatchNothing, { "id", Xapian::Query::OP_OR, Xapian::Query::MatchNothing,
SEXP_FLAG_FIELD | SEXP_FLAG_BOOLEAN | SEXP_FLAG_WILDCARD | SEXP_FLAG_REGEX }, SEXP_FLAG_FIELD | SEXP_FLAG_BOOLEAN | SEXP_FLAG_WILDCARD | SEXP_FLAG_REGEX },
{ "infix", Xapian::Query::OP_INVALID, Xapian::Query::MatchAll, { "infix", Xapian::Query::OP_INVALID, Xapian::Query::MatchAll,
SEXP_FLAG_SINGLE | SEXP_FLAG_ORPHAN }, SEXP_FLAG_SINGLE | SEXP_FLAG_ORPHAN },
{ "is", Xapian::Query::OP_AND, Xapian::Query::MatchAll, { "is", Xapian::Query::OP_AND, Xapian::Query::MatchAll,
SEXP_FLAG_FIELD | SEXP_FLAG_BOOLEAN | SEXP_FLAG_WILDCARD | SEXP_FLAG_REGEX | SEXP_FLAG_EXPAND }, SEXP_FLAG_FIELD | SEXP_FLAG_BOOLEAN | SEXP_FLAG_WILDCARD | SEXP_FLAG_REGEX | SEXP_FLAG_EXPAND },
{ "lastmod", Xapian::Query::OP_INVALID, Xapian::Query::MatchAll,
SEXP_FLAG_RANGE },
{ "matching", Xapian::Query::OP_AND, Xapian::Query::MatchAll, { "matching", Xapian::Query::OP_AND, Xapian::Query::MatchAll,
SEXP_FLAG_DO_EXPAND }, SEXP_FLAG_DO_EXPAND },
{ "mid", Xapian::Query::OP_OR, Xapian::Query::MatchNothing, { "mid", Xapian::Query::OP_OR, Xapian::Query::MatchNothing,
@ -89,7 +96,8 @@ static _sexp_prefix_t prefixes[] =
{ "or", Xapian::Query::OP_OR, Xapian::Query::MatchNothing, { "or", Xapian::Query::OP_OR, Xapian::Query::MatchNothing,
SEXP_FLAG_NONE }, SEXP_FLAG_NONE },
{ "path", Xapian::Query::OP_OR, Xapian::Query::MatchNothing, { "path", Xapian::Query::OP_OR, Xapian::Query::MatchNothing,
SEXP_FLAG_FIELD | SEXP_FLAG_BOOLEAN | SEXP_FLAG_WILDCARD | SEXP_FLAG_REGEX }, SEXP_FLAG_FIELD | SEXP_FLAG_BOOLEAN | SEXP_FLAG_WILDCARD | SEXP_FLAG_REGEX |
SEXP_FLAG_PATHNAME },
{ "property", Xapian::Query::OP_AND, Xapian::Query::MatchAll, { "property", Xapian::Query::OP_AND, Xapian::Query::MatchAll,
SEXP_FLAG_FIELD | SEXP_FLAG_BOOLEAN | SEXP_FLAG_WILDCARD | SEXP_FLAG_REGEX | SEXP_FLAG_EXPAND }, SEXP_FLAG_FIELD | SEXP_FLAG_BOOLEAN | SEXP_FLAG_WILDCARD | SEXP_FLAG_REGEX | SEXP_FLAG_EXPAND },
{ "query", Xapian::Query::OP_INVALID, Xapian::Query::MatchNothing, { "query", Xapian::Query::OP_INVALID, Xapian::Query::MatchNothing,
@ -446,6 +454,79 @@ _sexp_expand_param (notmuch_database_t *notmuch, const _sexp_prefix_t *parent,
return NOTMUCH_STATUS_BAD_QUERY_SYNTAX; return NOTMUCH_STATUS_BAD_QUERY_SYNTAX;
} }
static notmuch_status_t
_sexp_parse_range (notmuch_database_t *notmuch, const _sexp_prefix_t *prefix,
const sexp_t *sx, Xapian::Query &output)
{
const char *from, *to;
std::string msg;
/* empty range matches everything */
if (! sx) {
output = Xapian::Query::MatchAll;
return NOTMUCH_STATUS_SUCCESS;
}
if (sx->ty == SEXP_LIST) {
_notmuch_database_log (notmuch, "expected atom as first argument of '%s'\n", prefix->name);
return NOTMUCH_STATUS_BAD_QUERY_SYNTAX;
}
from = sx->val;
to = from;
if (sx->next) {
if (sx->next->ty == SEXP_LIST) {
_notmuch_database_log (notmuch, "expected atom as second argument of '%s'\n",
prefix->name);
return NOTMUCH_STATUS_BAD_QUERY_SYNTAX;
}
if (sx->next->next) {
_notmuch_database_log (notmuch, "'%s' expects maximum of two arguments\n", prefix->name);
return NOTMUCH_STATUS_BAD_QUERY_SYNTAX;
}
to = sx->next->val;
}
if (strcmp (prefix->name, "date") == 0) {
notmuch_status_t status;
status = _notmuch_date_strings_to_query (NOTMUCH_VALUE_TIMESTAMP, from, to, output, msg);
if (status) {
if (! msg.empty ())
_notmuch_database_log (notmuch, "%s\n", msg.c_str ());
}
return status;
}
if (strcmp (prefix->name, "lastmod") == 0) {
long from_idx, to_idx;
try {
from_idx = std::stol (from);
} catch (std::logic_error &e) {
_notmuch_database_log (notmuch, "bad 'from' revision: '%s'\n", from);
return NOTMUCH_STATUS_BAD_QUERY_SYNTAX;
}
try {
to_idx = std::stol (to);
} catch (std::logic_error &e) {
_notmuch_database_log (notmuch, "bad 'to' revision: '%s'\n", to);
return NOTMUCH_STATUS_BAD_QUERY_SYNTAX;
}
output = Xapian::Query (Xapian::Query::OP_VALUE_RANGE, NOTMUCH_VALUE_LAST_MOD,
Xapian::sortable_serialise (from_idx),
Xapian::sortable_serialise (to_idx));
return NOTMUCH_STATUS_SUCCESS;
}
_notmuch_database_log (notmuch, "unimplimented range prefix: '%s'\n", prefix->name);
return NOTMUCH_STATUS_BAD_QUERY_SYNTAX;
}
/* Here we expect the s-expression to be a proper list, with first /* Here we expect the s-expression to be a proper list, with first
* element defining and operation, or as a special case the empty * element defining and operation, or as a special case the empty
* list */ * list */
@ -467,8 +548,13 @@ _sexp_to_xapian_query (notmuch_database_t *notmuch, const _sexp_prefix_t *parent
return _sexp_parse_wildcard (notmuch, parent, env, "", output); return _sexp_parse_wildcard (notmuch, parent, env, "", output);
} }
char *atom = sx->val;
if (parent && parent->flags & SEXP_FLAG_PATHNAME)
strip_trailing (atom, '/');
if (parent && (parent->flags & SEXP_FLAG_BOOLEAN)) { if (parent && (parent->flags & SEXP_FLAG_BOOLEAN)) {
output = Xapian::Query (term_prefix + sx->val); output = Xapian::Query (term_prefix + atom);
return NOTMUCH_STATUS_SUCCESS; return NOTMUCH_STATUS_SUCCESS;
} }
@ -519,7 +605,7 @@ _sexp_to_xapian_query (notmuch_database_t *notmuch, const _sexp_prefix_t *parent
for (_sexp_prefix_t *prefix = prefixes; prefix && prefix->name; prefix++) { for (_sexp_prefix_t *prefix = prefixes; prefix && prefix->name; prefix++) {
if (strcmp (prefix->name, sx->list->val) == 0) { if (strcmp (prefix->name, sx->list->val) == 0) {
if (prefix->flags & SEXP_FLAG_FIELD) { if (prefix->flags & (SEXP_FLAG_FIELD | SEXP_FLAG_RANGE)) {
if (parent) { if (parent) {
_notmuch_database_log (notmuch, "nested field: '%s' inside '%s'\n", _notmuch_database_log (notmuch, "nested field: '%s' inside '%s'\n",
prefix->name, parent->name); prefix->name, parent->name);
@ -541,6 +627,9 @@ _sexp_to_xapian_query (notmuch_database_t *notmuch, const _sexp_prefix_t *parent
return NOTMUCH_STATUS_BAD_QUERY_SYNTAX; return NOTMUCH_STATUS_BAD_QUERY_SYNTAX;
} }
if (prefix->flags & SEXP_FLAG_RANGE)
return _sexp_parse_range (notmuch, prefix, sx->list->next, output);
if (strcmp (prefix->name, "infix") == 0) { if (strcmp (prefix->name, "infix") == 0) {
return _sexp_parse_infix (notmuch, sx->list->next, output); return _sexp_parse_infix (notmuch, sx->list->next, output);
} }

View file

@ -24,21 +24,27 @@
#include "parse-time-vrp.h" #include "parse-time-vrp.h"
#include "parse-time-string.h" #include "parse-time-string.h"
Xapian::Query notmuch_status_t
ParseTimeRangeProcessor::operator() (const std::string &begin, const std::string &end) _notmuch_date_strings_to_query (Xapian::valueno slot,
const std::string &begin, const std::string &end,
Xapian::Query &output, std::string &msg)
{ {
double from = DBL_MIN, to = DBL_MAX; double from = DBL_MIN, to = DBL_MAX;
time_t parsed_time, now; time_t parsed_time, now;
std::string str; std::string str;
/* Use the same 'now' for begin and end. */ /* Use the same 'now' for begin and end. */
if (time (&now) == (time_t) -1) if (time (&now) == (time_t) -1) {
throw Xapian::QueryParserError ("unable to get current time"); msg = "unable to get current time";
return NOTMUCH_STATUS_ILLEGAL_ARGUMENT;
}
if (! begin.empty ()) { if (! begin.empty ()) {
if (parse_time_string (begin.c_str (), &parsed_time, &now, PARSE_TIME_ROUND_DOWN)) if (parse_time_string (begin.c_str (), &parsed_time, &now, PARSE_TIME_ROUND_DOWN)) {
throw Xapian::QueryParserError ("Didn't understand date specification '" + begin + "'"); msg = "Didn't understand date specification '" + begin + "'";
else return NOTMUCH_STATUS_BAD_QUERY_SYNTAX;
}
from = (double) parsed_time; from = (double) parsed_time;
} }
@ -48,15 +54,30 @@ ParseTimeRangeProcessor::operator() (const std::string &begin, const std::string
else else
str = end; str = end;
if (parse_time_string (str.c_str (), &parsed_time, &now, PARSE_TIME_ROUND_UP_INCLUSIVE)) if (parse_time_string (str.c_str (), &parsed_time, &now, PARSE_TIME_ROUND_UP_INCLUSIVE)) {
throw Xapian::QueryParserError ("Didn't understand date specification '" + str + "'"); msg = "Didn't understand date specification '" + str + "'";
else return NOTMUCH_STATUS_BAD_QUERY_SYNTAX;
}
to = (double) parsed_time; to = (double) parsed_time;
} }
return Xapian::Query (Xapian::Query::OP_VALUE_RANGE, slot, output = Xapian::Query (Xapian::Query::OP_VALUE_RANGE, slot,
Xapian::sortable_serialise (from), Xapian::sortable_serialise (from),
Xapian::sortable_serialise (to)); Xapian::sortable_serialise (to));
return NOTMUCH_STATUS_SUCCESS;
}
Xapian::Query
ParseTimeRangeProcessor::operator() (const std::string &begin, const std::string &end)
{
Xapian::Query output;
std::string msg;
if (_notmuch_date_strings_to_query (slot, begin, end, output, msg))
throw Xapian::QueryParserError (msg);
return output;
} }
/* XXX TODO: is throwing an exception the right thing to do here? */ /* XXX TODO: is throwing an exception the right thing to do here? */

View file

@ -46,7 +46,7 @@ prefix_t prefix_table[] = {
{ "mid", "Q", NOTMUCH_FIELD_EXTERNAL | { "mid", "Q", NOTMUCH_FIELD_EXTERNAL |
NOTMUCH_FIELD_PROCESSOR }, NOTMUCH_FIELD_PROCESSOR },
{ "path", "P", NOTMUCH_FIELD_EXTERNAL | { "path", "P", NOTMUCH_FIELD_EXTERNAL |
NOTMUCH_FIELD_PROCESSOR }, NOTMUCH_FIELD_PROCESSOR | NOTMUCH_FIELD_STRIP_TRAILING_SLASH },
{ "property", "XPROPERTY", NOTMUCH_FIELD_EXTERNAL }, { "property", "XPROPERTY", NOTMUCH_FIELD_EXTERNAL },
/* /*
* Unconditionally add ':' to reduce potential ambiguity with * Unconditionally add ':' to reduce potential ambiguity with
@ -55,7 +55,7 @@ prefix_t prefix_table[] = {
* discussion. * discussion.
*/ */
{ "folder", "XFOLDER:", NOTMUCH_FIELD_EXTERNAL | { "folder", "XFOLDER:", NOTMUCH_FIELD_EXTERNAL |
NOTMUCH_FIELD_PROCESSOR }, NOTMUCH_FIELD_PROCESSOR | NOTMUCH_FIELD_STRIP_TRAILING_SLASH },
{ "date", NULL, NOTMUCH_FIELD_EXTERNAL | { "date", NULL, NOTMUCH_FIELD_EXTERNAL |
NOTMUCH_FIELD_PROCESSOR }, NOTMUCH_FIELD_PROCESSOR },
{ "query", NULL, NOTMUCH_FIELD_EXTERNAL | { "query", NULL, NOTMUCH_FIELD_EXTERNAL |

View file

@ -235,7 +235,15 @@ RegexpFieldProcessor::operator() (const std::string & str)
return parser.parse_query (query_str, NOTMUCH_QUERY_PARSER_FLAGS, term_prefix); return parser.parse_query (query_str, NOTMUCH_QUERY_PARSER_FLAGS, term_prefix);
} else { } else {
/* Boolean prefix */ /* Boolean prefix */
std::string term = term_prefix + str; std::string query_str;
std::string term;
if (str.length () > 1 && str.at (str.size () - 1) == '/')
query_str = str.substr (0, str.size () - 1);
else
query_str = str;
term = term_prefix + query_str;
return Xapian::Query (term); return Xapian::Query (term);
} }
} }

View file

@ -65,7 +65,7 @@ struct sprinter;
struct notmuch_show_params; struct notmuch_show_params;
typedef struct notmuch_show_format { typedef struct notmuch_show_format {
struct sprinter *(*new_sprinter)(const void *ctx, FILE *stream); struct sprinter *(*new_sprinter)(notmuch_database_t * db, FILE *stream);
notmuch_status_t (*part)(const void *ctx, struct sprinter *sprinter, notmuch_status_t (*part)(const void *ctx, struct sprinter *sprinter,
struct mime_node *node, int indent, struct mime_node *node, int indent,
const struct notmuch_show_params *params); const struct notmuch_show_params *params);
@ -426,13 +426,13 @@ mime_node_seek_dfs (mime_node_t *node, int n);
const _notmuch_message_crypto_t * const _notmuch_message_crypto_t *
mime_node_get_message_crypto_status (mime_node_t *node); mime_node_get_message_crypto_status (mime_node_t *node);
typedef enum dump_formats { typedef enum {
DUMP_FORMAT_AUTO, DUMP_FORMAT_AUTO,
DUMP_FORMAT_BATCH_TAG, DUMP_FORMAT_BATCH_TAG,
DUMP_FORMAT_SUP DUMP_FORMAT_SUP
} dump_format_t; } dump_format_t;
typedef enum dump_includes { typedef enum {
DUMP_INCLUDE_TAGS = 1, DUMP_INCLUDE_TAGS = 1,
DUMP_INCLUDE_CONFIG = 2, DUMP_INCLUDE_CONFIG = 2,
DUMP_INCLUDE_PROPERTIES = 4 DUMP_INCLUDE_PROPERTIES = 4
@ -499,11 +499,10 @@ int notmuch_minimal_options (const char *subcommand_name,
struct _notmuch_client_indexing_cli_choices { struct _notmuch_client_indexing_cli_choices {
int decrypt_policy; int decrypt_policy;
bool decrypt_policy_set; bool decrypt_policy_set;
notmuch_indexopts_t *opts;
}; };
extern struct _notmuch_client_indexing_cli_choices indexing_cli_choices; extern struct _notmuch_client_indexing_cli_choices indexing_cli_choices;
extern const notmuch_opt_desc_t notmuch_shared_indexing_options []; extern const notmuch_opt_desc_t notmuch_shared_indexing_options [];
notmuch_status_t notmuch_status_t
notmuch_process_shared_indexing_options (notmuch_database_t *notmuch); notmuch_process_shared_indexing_options (notmuch_indexopts_t *opts);
#endif #endif

View file

@ -383,7 +383,10 @@ _config_set_list (notmuch_conffile_t *config,
const char *list[], const char *list[],
size_t length) size_t length)
{ {
if (length > 1)
g_key_file_set_string_list (config->key_file, group, key, list, length); g_key_file_set_string_list (config->key_file, group, key, list, length);
else
g_key_file_set_string (config->key_file, group, key, list[0]);
} }
void void
@ -680,9 +683,9 @@ _notmuch_config_list_built_with ()
printf ("%sretry_lock=%s\n", printf ("%sretry_lock=%s\n",
BUILT_WITH_PREFIX, BUILT_WITH_PREFIX,
notmuch_built_with ("retry_lock") ? "true" : "false"); notmuch_built_with ("retry_lock") ? "true" : "false");
printf ("%ssexpr_query=%s\n", printf ("%ssexp_queries=%s\n",
BUILT_WITH_PREFIX, BUILT_WITH_PREFIX,
notmuch_built_with ("sexpr_query") ? "true" : "false"); notmuch_built_with ("sexp_queries") ? "true" : "false");
} }
static int static int

View file

@ -461,6 +461,8 @@ notmuch_insert_command (notmuch_database_t *notmuch, int argc, char *argv[])
char *maildir; char *maildir;
char *newpath; char *newpath;
int opt_index; int opt_index;
notmuch_indexopts_t *indexopts = notmuch_database_get_default_indexopts (notmuch);
void *local = talloc_new (NULL); void *local = talloc_new (NULL);
notmuch_opt_desc_t options[] = { notmuch_opt_desc_t options[] = {
@ -550,7 +552,7 @@ notmuch_insert_command (notmuch_database_t *notmuch, int argc, char *argv[])
return EXIT_FAILURE; return EXIT_FAILURE;
} }
status = notmuch_process_shared_indexing_options (notmuch); status = notmuch_process_shared_indexing_options (indexopts);
if (status != NOTMUCH_STATUS_SUCCESS) { if (status != NOTMUCH_STATUS_SUCCESS) {
fprintf (stderr, "Error: Failed to process index options. (%s)\n", fprintf (stderr, "Error: Failed to process index options. (%s)\n",
notmuch_status_to_string (status)); notmuch_status_to_string (status));
@ -558,7 +560,7 @@ notmuch_insert_command (notmuch_database_t *notmuch, int argc, char *argv[])
} }
/* Index the message. */ /* Index the message. */
status = add_file (notmuch, newpath, tag_ops, synchronize_flags, keep, indexing_cli_choices.opts); status = add_file (notmuch, newpath, tag_ops, synchronize_flags, keep, indexopts);
/* Commit changes. */ /* Commit changes. */
close_status = notmuch_database_close (notmuch); close_status = notmuch_database_close (notmuch);

View file

@ -45,6 +45,7 @@ typedef struct {
const char *db_path; const char *db_path;
const char *mail_root; const char *mail_root;
notmuch_indexopts_t *indexopts;
int output_is_a_tty; int output_is_a_tty;
enum verbosity verbosity; enum verbosity verbosity;
bool debug; bool debug;
@ -376,7 +377,7 @@ add_file (notmuch_database_t *notmuch, const char *filename,
if (status) if (status)
goto DONE; goto DONE;
status = notmuch_database_index_file (notmuch, filename, indexing_cli_choices.opts, &message); status = notmuch_database_index_file (notmuch, filename, state->indexopts, &message);
switch (status) { switch (status) {
/* Success. */ /* Success. */
case NOTMUCH_STATUS_SUCCESS: case NOTMUCH_STATUS_SUCCESS:
@ -600,11 +601,12 @@ add_files (notmuch_database_t *notmuch,
continue; continue;
} }
/* Ignore the .notmuch directory and any "tmp" directory /* Ignore any top level .notmuch directory and any "tmp" directory
* that appears within a maildir. * that appears within a maildir.
*/ */
if ((is_maildir && strcmp (entry->d_name, "tmp") == 0) || if ((is_maildir && strcmp (entry->d_name, "tmp") == 0) ||
strcmp (entry->d_name, ".notmuch") == 0) (strcmp (entry->d_name, ".notmuch") == 0
&& (strcmp (path, state->mail_root)) == 0))
continue; continue;
next = talloc_asprintf (notmuch, "%s/%s", path, entry->d_name); next = talloc_asprintf (notmuch, "%s/%s", path, entry->d_name);
@ -1150,6 +1152,8 @@ notmuch_new_command (notmuch_database_t *notmuch, int argc, char *argv[])
else if (verbose) else if (verbose)
add_files_state.verbosity = VERBOSITY_VERBOSE; add_files_state.verbosity = VERBOSITY_VERBOSE;
add_files_state.indexopts = notmuch_database_get_default_indexopts (notmuch);
add_files_state.new_tags = notmuch_config_get_values (notmuch, NOTMUCH_CONFIG_NEW_TAGS); add_files_state.new_tags = notmuch_config_get_values (notmuch, NOTMUCH_CONFIG_NEW_TAGS);
if (print_status_database ( if (print_status_database (
@ -1217,7 +1221,7 @@ notmuch_new_command (notmuch_database_t *notmuch, int argc, char *argv[])
if (notmuch == NULL) if (notmuch == NULL)
return EXIT_FAILURE; return EXIT_FAILURE;
status = notmuch_process_shared_indexing_options (notmuch); status = notmuch_process_shared_indexing_options (add_files_state.indexopts);
if (status != NOTMUCH_STATUS_SUCCESS) { if (status != NOTMUCH_STATUS_SUCCESS) {
fprintf (stderr, "Error: Failed to process index options. (%s)\n", fprintf (stderr, "Error: Failed to process index options. (%s)\n",
notmuch_status_to_string (status)); notmuch_status_to_string (status));

View file

@ -90,6 +90,7 @@ notmuch_reindex_command (notmuch_database_t *notmuch, int argc, char *argv[])
int opt_index; int opt_index;
int ret; int ret;
notmuch_status_t status; notmuch_status_t status;
notmuch_indexopts_t *indexopts = notmuch_database_get_default_indexopts (notmuch);
/* Set up our handler for SIGINT */ /* Set up our handler for SIGINT */
memset (&action, 0, sizeof (struct sigaction)); memset (&action, 0, sizeof (struct sigaction));
@ -110,7 +111,7 @@ notmuch_reindex_command (notmuch_database_t *notmuch, int argc, char *argv[])
notmuch_process_shared_options (notmuch, argv[0]); notmuch_process_shared_options (notmuch, argv[0]);
status = notmuch_process_shared_indexing_options (notmuch); status = notmuch_process_shared_indexing_options (indexopts);
if (status != NOTMUCH_STATUS_SUCCESS) { if (status != NOTMUCH_STATUS_SUCCESS) {
fprintf (stderr, "Error: Failed to process index options. (%s)\n", fprintf (stderr, "Error: Failed to process index options. (%s)\n",
notmuch_status_to_string (status)); notmuch_status_to_string (status));
@ -128,7 +129,7 @@ notmuch_reindex_command (notmuch_database_t *notmuch, int argc, char *argv[])
return EXIT_FAILURE; return EXIT_FAILURE;
} }
ret = reindex_query (notmuch, query_string, indexing_cli_choices.opts); ret = reindex_query (notmuch, query_string, indexopts);
notmuch_database_destroy (notmuch); notmuch_database_destroy (notmuch);

View file

@ -209,6 +209,30 @@ _is_from_line (const char *line)
return 0; return 0;
} }
/* Output extra headers if configured with the `show.extra_headers'
* configuration option
*/
static void
format_extra_headers_sprinter (sprinter_t *sp, GMimeMessage *message)
{
GMimeHeaderList *header_list = g_mime_object_get_header_list (GMIME_OBJECT (message));
for (notmuch_config_values_t *extra_headers = notmuch_config_get_values (
sp->notmuch, NOTMUCH_CONFIG_EXTRA_HEADERS);
notmuch_config_values_valid (extra_headers);
notmuch_config_values_move_to_next (extra_headers)) {
GMimeHeader *header;
const char *header_name = notmuch_config_values_get (extra_headers);
header = g_mime_header_list_get_header (header_list, header_name);
if (header == NULL)
continue;
sp->map_key (sp, g_mime_header_get_name (header));
sp->string (sp, g_mime_header_get_value (header));
}
}
void void
format_headers_sprinter (sprinter_t *sp, GMimeMessage *message, format_headers_sprinter (sprinter_t *sp, GMimeMessage *message,
bool reply, const _notmuch_message_crypto_t *msg_crypto) bool reply, const _notmuch_message_crypto_t *msg_crypto)
@ -269,6 +293,9 @@ format_headers_sprinter (sprinter_t *sp, GMimeMessage *message,
sp->string (sp, g_mime_message_get_date_string (sp, message)); sp->string (sp, g_mime_message_get_date_string (sp, message));
} }
/* Output extra headers the user has configured, if any */
if (! reply)
format_extra_headers_sprinter (sp, message);
sp->end (sp); sp->end (sp);
talloc_free (local); talloc_free (local);
} }

View file

@ -141,21 +141,18 @@ const notmuch_opt_desc_t notmuch_shared_indexing_options [] = {
notmuch_status_t notmuch_status_t
notmuch_process_shared_indexing_options (notmuch_database_t *notmuch) notmuch_process_shared_indexing_options (notmuch_indexopts_t *opts)
{ {
if (indexing_cli_choices.opts == NULL) if (opts == NULL)
indexing_cli_choices.opts = notmuch_database_get_default_indexopts (notmuch); return NOTMUCH_STATUS_NULL_POINTER;
if (indexing_cli_choices.decrypt_policy_set) { if (indexing_cli_choices.decrypt_policy_set) {
notmuch_status_t status; notmuch_status_t status;
if (indexing_cli_choices.opts == NULL) status = notmuch_indexopts_set_decrypt_policy (opts,
return NOTMUCH_STATUS_OUT_OF_MEMORY;
status = notmuch_indexopts_set_decrypt_policy (indexing_cli_choices.opts,
indexing_cli_choices.decrypt_policy); indexing_cli_choices.decrypt_policy);
if (status != NOTMUCH_STATUS_SUCCESS) { if (status != NOTMUCH_STATUS_SUCCESS) {
fprintf (stderr, "Error: Failed to set index decryption policy to %d. (%s)\n", fprintf (stderr, "Error: Failed to set index decryption policy to %d. (%s)\n",
indexing_cli_choices.decrypt_policy, notmuch_status_to_string (status)); indexing_cli_choices.decrypt_policy, notmuch_status_to_string (status));
notmuch_indexopts_destroy (indexing_cli_choices.opts);
indexing_cli_choices.opts = NULL;
return status; return status;
} }
} }

View file

@ -172,7 +172,7 @@ json_separator (struct sprinter *sp)
} }
struct sprinter * struct sprinter *
sprinter_json_create (const void *ctx, FILE *stream) sprinter_json_create (notmuch_database_t *db, FILE *stream)
{ {
static const struct sprinter_json template = { static const struct sprinter_json template = {
.vtable = { .vtable = {
@ -192,11 +192,12 @@ sprinter_json_create (const void *ctx, FILE *stream)
}; };
struct sprinter_json *res; struct sprinter_json *res;
res = talloc (ctx, struct sprinter_json); res = talloc (db, struct sprinter_json);
if (! res) if (! res)
return NULL; return NULL;
*res = template; *res = template;
res->vtable.notmuch = db;
res->stream = stream; res->stream = stream;
return &res->vtable; return &res->vtable;
} }

View file

@ -207,7 +207,7 @@ sexp_separator (struct sprinter *sp)
} }
struct sprinter * struct sprinter *
sprinter_sexp_create (const void *ctx, FILE *stream) sprinter_sexp_create (notmuch_database_t *db, FILE *stream)
{ {
static const struct sprinter_sexp template = { static const struct sprinter_sexp template = {
.vtable = { .vtable = {
@ -227,11 +227,12 @@ sprinter_sexp_create (const void *ctx, FILE *stream)
}; };
struct sprinter_sexp *res; struct sprinter_sexp *res;
res = talloc (ctx, struct sprinter_sexp); res = talloc (db, struct sprinter_sexp);
if (! res) if (! res)
return NULL; return NULL;
*res = template; *res = template;
res->vtable.notmuch = db;
res->stream = stream; res->stream = stream;
return &res->vtable; return &res->vtable;
} }

View file

@ -114,7 +114,7 @@ text_map_key (unused (struct sprinter *sp), unused (const char *key))
} }
struct sprinter * struct sprinter *
sprinter_text_create (const void *ctx, FILE *stream) sprinter_text_create (notmuch_database_t *db, FILE *stream)
{ {
static const struct sprinter_text template = { static const struct sprinter_text template = {
.vtable = { .vtable = {
@ -134,21 +134,22 @@ sprinter_text_create (const void *ctx, FILE *stream)
}; };
struct sprinter_text *res; struct sprinter_text *res;
res = talloc (ctx, struct sprinter_text); res = talloc (db, struct sprinter_text);
if (! res) if (! res)
return NULL; return NULL;
*res = template; *res = template;
res->vtable.notmuch = db;
res->stream = stream; res->stream = stream;
return &res->vtable; return &res->vtable;
} }
struct sprinter * struct sprinter *
sprinter_text0_create (const void *ctx, FILE *stream) sprinter_text0_create (notmuch_database_t *db, FILE *stream)
{ {
struct sprinter *sp; struct sprinter *sp;
sp = sprinter_text_create (ctx, stream); sp = sprinter_text_create (db, stream);
if (! sp) if (! sp)
return NULL; return NULL;

View file

@ -9,6 +9,11 @@
* (strings, integers and booleans). * (strings, integers and booleans).
*/ */
typedef struct sprinter { typedef struct sprinter {
/*
* Open notmuch database
*/
notmuch_database_t *notmuch;
/* Start a new map/dictionary structure. This should be followed by /* Start a new map/dictionary structure. This should be followed by
* a sequence of alternating calls to map_key and one of the * a sequence of alternating calls to map_key and one of the
* value-printing functions until the map is ended by end. * value-printing functions until the map is ended by end.
@ -65,20 +70,20 @@ typedef struct sprinter {
/* Create a new unstructured printer that emits the default text format /* Create a new unstructured printer that emits the default text format
* for "notmuch search". */ * for "notmuch search". */
struct sprinter * struct sprinter *
sprinter_text_create (const void *ctx, FILE *stream); sprinter_text_create (notmuch_database_t *db, FILE *stream);
/* Create a new unstructured printer that emits the text format for /* Create a new unstructured printer that emits the text format for
* "notmuch search", with each field separated by a null character * "notmuch search", with each field separated by a null character
* instead of the newline character. */ * instead of the newline character. */
struct sprinter * struct sprinter *
sprinter_text0_create (const void *ctx, FILE *stream); sprinter_text0_create (notmuch_database_t *db, FILE *stream);
/* Create a new structure printer that emits JSON. */ /* Create a new structure printer that emits JSON. */
struct sprinter * struct sprinter *
sprinter_json_create (const void *ctx, FILE *stream); sprinter_json_create (notmuch_database_t *db, FILE *stream);
/* Create a new structure printer that emits S-Expressions. */ /* Create a new structure printer that emits S-Expressions. */
struct sprinter * struct sprinter *
sprinter_sexp_create (const void *ctx, FILE *stream); sprinter_sexp_create (notmuch_database_t *db, FILE *stream);
#endif // NOTMUCH_SPRINTER_H #endif // NOTMUCH_SPRINTER_H

View file

@ -51,7 +51,7 @@ cat <<EOF > EXPECTED
built_with.compact=something built_with.compact=something
built_with.field_processor=something built_with.field_processor=something
built_with.retry_lock=something built_with.retry_lock=something
built_with.sexpr_query=something built_with.sexp_queries=something
database.autocommit=8000 database.autocommit=8000
database.mail_root=MAIL_DIR database.mail_root=MAIL_DIR
database.path=MAIL_DIR database.path=MAIL_DIR
@ -67,6 +67,35 @@ user.primary_email=test_suite@notmuchmail.org
EOF EOF
test_expect_equal_file EXPECTED OUTPUT test_expect_equal_file EXPECTED OUTPUT
test_begin_subtest "Round trip config item with leading spaces"
test_subtest_known_broken
notmuch config set foo.bar " thing"
output=$(notmuch config get foo.bar)
test_expect_equal "${output}" " thing"
test_begin_subtest "Round trip config item with leading tab"
test_subtest_known_broken
notmuch config set foo.bar " thing"
output=$(notmuch config get foo.bar)
test_expect_equal "${output}" " thing"
test_begin_subtest "Round trip config item with embedded tab"
notmuch config set foo.bar "thing other"
output=$(notmuch config get foo.bar)
test_expect_equal "${output}" "thing other"
test_begin_subtest "Round trip config item with embedded backslash"
notmuch config set foo.bar 'thing\other'
output=$(notmuch config get foo.bar)
test_expect_equal "${output}" "thing\other"
test_begin_subtest "Round trip config item with embedded NL/CR"
notmuch config set foo.bar 'thing
other'
output=$(notmuch config get foo.bar)
test_expect_equal "${output}" "thing
other"
test_begin_subtest "Top level --config=FILE option" test_begin_subtest "Top level --config=FILE option"
cp "${NOTMUCH_CONFIG}" alt-config cp "${NOTMUCH_CONFIG}" alt-config
notmuch --config=alt-config config set user.name "Another Name" notmuch --config=alt-config config set user.name "Another Name"

View file

@ -23,6 +23,13 @@ EOF
expected_dir=$NOTMUCH_SRCDIR/test/setup.expected-output expected_dir=$NOTMUCH_SRCDIR/test/setup.expected-output
test_expect_equal_file ${expected_dir}/config-with-comments new-notmuch-config test_expect_equal_file ${expected_dir}/config-with-comments new-notmuch-config
test_begin_subtest "setup consistent with config-set for single items"
# note this relies on the config state from the previous test.
notmuch --config=new-notmuch-config config list > list.setup
notmuch --config=new-notmuch-config config set search.exclude_tags baz
notmuch --config=new-notmuch-config config list > list.config
test_expect_equal_file list.setup list.config
test_begin_subtest "notmuch with a config but without a database suggests notmuch new" test_begin_subtest "notmuch with a config but without a database suggests notmuch new"
notmuch 2>&1 | notmuch_dir_sanitize > OUTPUT notmuch 2>&1 | notmuch_dir_sanitize > OUTPUT
cat <<EOF > EXPECTED cat <<EOF > EXPECTED

View file

@ -329,6 +329,18 @@ notmuch config set new.tags "foo;;bar"
output=$(NOTMUCH_NEW --quiet 2>&1) output=$(NOTMUCH_NEW --quiet 2>&1)
test_expect_equal "$output" "" test_expect_equal "$output" ""
test_begin_subtest "leading/trailing whitespace in new.tags is ignored"
# avoid complications with leading spaces and "notmuch config"
sed -i 's/^tags=.*$/tags= fu bar ; ; bar /' notmuch-config
add_message
NOTMUCH_NEW --quiet
notmuch dump id:$gen_msg_id | sed 's/ --.*$//' > OUTPUT
cat <<EOF >EXPECTED
#notmuch-dump batch-tag:3 config,properties,tags
+bar +fu%20bar
EOF
test_expect_equal_file EXPECTED OUTPUT
test_begin_subtest "Tags starting with '-' in new.tags are forbidden" test_begin_subtest "Tags starting with '-' in new.tags are forbidden"
notmuch config set new.tags "-foo;bar" notmuch config set new.tags "-foo;bar"
output=$(NOTMUCH_NEW --debug 2>&1) output=$(NOTMUCH_NEW --debug 2>&1)
@ -339,6 +351,16 @@ test_expect_code 1 "NOTMUCH_NEW --debug 2>&1"
notmuch config set new.tags $OLDCONFIG notmuch config set new.tags $OLDCONFIG
test_begin_subtest ".notmuch only ignored at top level"
generate_message '[dir]=foo/bar/.notmuch/cur' '[subject]="Do not ignore, very important"'
NOTMUCH_NEW > OUTPUT
notmuch search subject:Do-not-ignore | notmuch_search_sanitize >> OUTPUT
cat <<EOF > EXPECTED
Added 1 new message to the database.
thread:XXX 2001-01-05 [1/1] Notmuch Test Suite; Do not ignore, very important (inbox unread)
EOF
test_expect_equal_file EXPECTED OUTPUT
test_begin_subtest "RFC822 group names are indexed" test_begin_subtest "RFC822 group names are indexed"
test_subtest_known_broken test_subtest_known_broken
generate_message [to]="undisclosed-recipients:" generate_message [to]="undisclosed-recipients:"
@ -368,31 +390,26 @@ chmod u+w ${MAIL_DIR}/.notmuch/xapian/*.*
test_expect_equal "$output" "A Xapian exception occurred opening database" test_expect_equal "$output" "A Xapian exception occurred opening database"
make_shim dif-shim<<EOF
#include <notmuch-test.h>
WRAP_DLFUNC(notmuch_status_t, notmuch_database_index_file, \
(notmuch_database_t *database, const char *filename, notmuch_indexopts_t *indexopts, notmuch_message_t **message))
if (unlink ("${MAIL_DIR}/vanish")) {
fprintf (stderr, "unlink failed\n");
exit (42);
}
return notmuch_database_index_file_orig (database, filename, indexopts, message);
}
EOF
test_begin_subtest "Handle files vanishing between scandir and add_file" test_begin_subtest "Handle files vanishing between scandir and add_file"
# A file for scandir to find. It won't get indexed, so can be empty. # A file for scandir to find. It won't get indexed, so can be empty.
touch ${MAIL_DIR}/vanish touch ${MAIL_DIR}/vanish
notmuch_with_shim dif-shim new 2>OUTPUT 1>/dev/null
# Breakpoint to remove the file before indexing
cat <<EOF > notmuch-new-vanish.gdb
set breakpoint pending on
set logging file notmuch-new-vanish-gdb.log
set logging on
break notmuch_database_index_file
commands
shell rm -f ${MAIL_DIR}/vanish
continue
end
run
EOF
${TEST_GDB} --batch-silent --return-child-result -x notmuch-new-vanish.gdb \
--args notmuch new 2>OUTPUT 1>/dev/null
echo "exit status: $?" >> OUTPUT echo "exit status: $?" >> OUTPUT
# Clean up the file in case gdb isn't available.
rm -f ${MAIL_DIR}/vanish
cat <<EOF > EXPECTED cat <<EOF > EXPECTED
Unexpected error with file ${MAIL_DIR}/vanish Unexpected error with file ${MAIL_DIR}/vanish
add_file: Something went wrong trying to read or write a file add_file: Something went wrong trying to read or write a file

40
test/T051-new-renames.sh Executable file
View file

@ -0,0 +1,40 @@
#!/usr/bin/env bash
test_description='"notmuch new" with directory renames'
. $(dirname "$0")/test-lib.sh || exit 1
for loop in {1..10}; do
rm -rf ${MAIL_DIR}
for i in {1..10}; do
generate_message '[dir]=foo' '[subject]="Message foo $i"'
done
for i in {1..10}; do
generate_message '[dir]=bar' '[subject]="Message bar $i"'
done
test_begin_subtest "Index the messages, round $loop"
output=$(NOTMUCH_NEW)
test_expect_equal "$output" "Added 20 new messages to the database."
all_files=$(notmuch search --output=files \*)
count_foo=$(notmuch count folder:foo)
test_begin_subtest "Rename folder"
mv ${MAIL_DIR}/foo ${MAIL_DIR}/baz
output=$(NOTMUCH_NEW)
test_expect_equal "$output" "No new mail. Detected $count_foo file renames."
test_begin_subtest "Rename folder back"
mv ${MAIL_DIR}/baz ${MAIL_DIR}/foo
output=$(NOTMUCH_NEW)
test_expect_equal "$output" "No new mail. Detected $count_foo file renames."
test_begin_subtest "Files remain the same"
output=$(notmuch search --output=files \*)
test_expect_equal "$output" "$all_files"
done
test_done

View file

@ -277,7 +277,7 @@ EOF
built_with.compact=something built_with.compact=something
built_with.field_processor=something built_with.field_processor=something
built_with.retry_lock=something built_with.retry_lock=something
built_with.sexpr_query=something built_with.sexp_queries=something
database.autocommit=8000 database.autocommit=8000
database.backup_dir database.backup_dir
database.hook_dir database.hook_dir
@ -318,7 +318,14 @@ to=m.header('To')
print(to) print(to)
EOF EOF
test_expect_equal_file EXPECTED OUTPUT test_expect_equal_file EXPECTED OUTPUT
;& # fall through ;;
*)
backup_database
test_begin_subtest ".notmuch without xapian/ handled gracefully ($config)"
rm -r $XAPIAN_PATH
test_expect_success "notmuch new"
restore_database
;;
esac esac
case $config in case $config in

View file

@ -102,22 +102,25 @@ output=$(sed 's/^\(A Xapian exception [^:]*\):.*$/\1/' OUTPUT)
test_expect_equal "${output}" "A Xapian exception occurred opening database" test_expect_equal "${output}" "A Xapian exception occurred opening database"
restore_database restore_database
cat <<EOF > count-files.gdb make_shim qsm-shim<<EOF
set breakpoint pending on #include <notmuch-test.h>
set logging file count-files-gdb.log
set logging on WRAP_DLFUNC (notmuch_status_t, notmuch_query_search_messages, (notmuch_query_t *query, notmuch_messages_t **messages))
break count_files
commands /* XXX WARNING THIS CORRUPTS THE DATABASE */
shell cp /dev/null ${MAIL_DIR}/.notmuch/xapian/postlist.* int fd = open ("target_postlist", O_WRONLY|O_TRUNC);
continue if (fd < 0)
end exit (8);
run close (fd);
return notmuch_query_search_messages_orig(query, messages);
}
EOF EOF
backup_database backup_database
test_begin_subtest "error message from query_search_messages" test_begin_subtest "error message from query_search_messages"
${TEST_GDB} --batch-silent --return-child-result -x count-files.gdb \ ln -s ${MAIL_DIR}/.notmuch/xapian/postlist.* target_postlist
--args notmuch count --output=files '*' 2>OUTPUT 1>/dev/null notmuch_with_shim qsm-shim count --output=files '*' 2>OUTPUT 1>/dev/null
cat <<EOF > EXPECTED cat <<EOF > EXPECTED
notmuch count: A Xapian exception occurred notmuch count: A Xapian exception occurred
A Xapian exception occurred performing query A Xapian exception occurred performing query

View file

@ -234,6 +234,18 @@ output=$(notmuch show --format=json id:$gen_msg_id)
test_json_nodes <<<"$output" \ test_json_nodes <<<"$output" \
'new_tags:[0][0][0]["tags"] = ["bar", "foo"]' 'new_tags:[0][0][0]["tags"] = ["bar", "foo"]'
test_begin_subtest "leading/trailing whitespace in new.tags is ignored"
# avoid complications with leading spaces and "notmuch config"
sed -i 's/^tags=.*$/tags= fu bar ; ; bar /' notmuch-config
gen_insert_msg
notmuch insert < $gen_msg_filename
notmuch dump id:$gen_msg_id | sed 's/ --.*$//' > OUTPUT
cat <<EOF >EXPECTED
#notmuch-dump batch-tag:3 config,properties,tags
+bar +fu%20bar
EOF
test_expect_equal_file EXPECTED OUTPUT
test_begin_subtest "Tags starting with '-' in new.tags are forbidden" test_begin_subtest "Tags starting with '-' in new.tags are forbidden"
notmuch config set new.tags "-foo;bar" notmuch config set new.tags "-foo;bar"
gen_insert_msg gen_insert_msg

View file

@ -185,6 +185,50 @@ notmuch search folder:'""' > EXPECTED
notmuch search --query=sexp '(folder "")' > OUTPUT notmuch search --query=sexp '(folder "")' > OUTPUT
test_expect_equal_file EXPECTED OUTPUT test_expect_equal_file EXPECTED OUTPUT
test_begin_subtest "Search by 'folder' with --output=files"
output=$(notmuch search --output=files --query=sexp '(folder bad/news)' | notmuch_search_files_sanitize)
test_expect_equal "$output" "MAIL_DIR/bad/news/msg-XXX
MAIL_DIR/duplicate/bad/news/msg-XXX"
test_begin_subtest "Search by 'folder' with --output=files (trailing /)"
output=$(notmuch search --output=files --query=sexp '(folder bad/news/)' | notmuch_search_files_sanitize)
test_expect_equal "$output" "MAIL_DIR/bad/news/msg-XXX
MAIL_DIR/duplicate/bad/news/msg-XXX"
test_begin_subtest "Search by 'folder' (multiple)"
output=$(notmuch search --query=sexp '(folder bad bad/news things/bad)' | notmuch_search_sanitize)
test_expect_equal "$output" "thread:XXX 2001-01-05 [1/1] Notmuch Test Suite; To the bone (inbox unread)
thread:XXX 2001-01-05 [1/1(2)] Notmuch Test Suite; Bears (inbox unread)
thread:XXX 2001-01-05 [1/1] Notmuch Test Suite; Bites, stings, sad feelings (inbox unread)"
test_begin_subtest "Search by 'folder' (multiple, trailing /)"
output=$(notmuch search --query=sexp '(folder bad bad/news/ things/bad)' | notmuch_search_sanitize)
test_expect_equal "$output" "thread:XXX 2001-01-05 [1/1] Notmuch Test Suite; To the bone (inbox unread)
thread:XXX 2001-01-05 [1/1(2)] Notmuch Test Suite; Bears (inbox unread)
thread:XXX 2001-01-05 [1/1] Notmuch Test Suite; Bites, stings, sad feelings (inbox unread)"
test_begin_subtest "Search by 'path' with --output=files"
output=$(notmuch search --output=files --query=sexp '(path bad/news)' | notmuch_search_files_sanitize)
test_expect_equal "$output" "MAIL_DIR/bad/news/msg-XXX
MAIL_DIR/duplicate/bad/news/msg-XXX"
test_begin_subtest "Search by 'path' with --output=files (trailing /)"
output=$(notmuch search --output=files --query=sexp '(path bad/news/)' | notmuch_search_files_sanitize)
test_expect_equal "$output" "MAIL_DIR/bad/news/msg-XXX
MAIL_DIR/duplicate/bad/news/msg-XXX"
test_begin_subtest "Search by 'path' specification (multiple)"
output=$(notmuch search --query=sexp '(path bad bad/news things/bad)' | notmuch_search_sanitize)
test_expect_equal "$output" "thread:XXX 2001-01-05 [1/1] Notmuch Test Suite; To the bone (inbox unread)
thread:XXX 2001-01-05 [1/1(2)] Notmuch Test Suite; Bears (inbox unread)
thread:XXX 2001-01-05 [1/1] Notmuch Test Suite; Bites, stings, sad feelings (inbox unread)"
test_begin_subtest "Search by 'path' specification (multiple, trailing /)"
output=$(notmuch search --query=sexp '(path bad bad/news/ things/bad)' | notmuch_search_sanitize)
test_expect_equal "$output" "thread:XXX 2001-01-05 [1/1] Notmuch Test Suite; To the bone (inbox unread)
thread:XXX 2001-01-05 [1/1(2)] Notmuch Test Suite; Bears (inbox unread)
thread:XXX 2001-01-05 [1/1] Notmuch Test Suite; Bites, stings, sad feelings (inbox unread)"
test_begin_subtest "Search by 'id'" test_begin_subtest "Search by 'id'"
add_message '[subject]="search by id"' '[date]="Sat, 01 Jan 2000 12:00:00 -0000"' add_message '[subject]="search by id"' '[date]="Sat, 01 Jan 2000 12:00:00 -0000"'
output=$(notmuch search --query=sexp "(id ${gen_msg_id})" | notmuch_search_sanitize) output=$(notmuch search --query=sexp "(id ${gen_msg_id})" | notmuch_search_sanitize)
@ -346,7 +390,7 @@ output=$(notmuch search --query=sexp '(attachment (starts-with not))' | notmuch_
test_expect_equal "$output" 'thread:XXX 2009-11-18 [2/2] Lars Kellogg-Stedman; [notmuch] "notmuch help" outputs to stderr? (attachment inbox signed unread)' test_expect_equal "$output" 'thread:XXX 2009-11-18 [2/2] Lars Kellogg-Stedman; [notmuch] "notmuch help" outputs to stderr? (attachment inbox signed unread)'
test_begin_subtest "starts-with, folder" test_begin_subtest "starts-with, folder"
notmuch search --output=files --query=sexp '(folder (starts-with bad))' | notmuch_dir_sanitize | sed 's/[0-9]*$/XXX/' > OUTPUT notmuch search --output=files --query=sexp '(folder (starts-with bad))' | notmuch_search_files_sanitize > OUTPUT
cat <<EOF > EXPECTED cat <<EOF > EXPECTED
MAIL_DIR/bad/msg-XXX MAIL_DIR/bad/msg-XXX
MAIL_DIR/bad/news/msg-XXX MAIL_DIR/bad/news/msg-XXX
@ -768,6 +812,144 @@ notmuch search date:2009-11-18..2009-11-18 and tag:unread > EXPECTED
notmuch search --query=sexp '(and (infix "date:2009-11-18..2009-11-18") (infix "tag:unread"))' > OUTPUT notmuch search --query=sexp '(and (infix "date:2009-11-18..2009-11-18") (infix "tag:unread"))' > OUTPUT
test_expect_equal_file EXPECTED OUTPUT test_expect_equal_file EXPECTED OUTPUT
test_begin_subtest "date query, empty"
notmuch search from:keithp | notmuch_search_sanitize > EXPECTED
notmuch search --query=sexp '(and (date) (from keithp))'| notmuch_search_sanitize > OUTPUT
test_expect_equal_file EXPECTED OUTPUT
test_begin_subtest "date query, one argument"
notmuch search date:2009-11-18 and from:keithp | notmuch_search_sanitize > EXPECTED
notmuch search --query=sexp '(and (date 2009-11-18) (from keithp))' | notmuch_search_sanitize > OUTPUT
test_expect_equal_file EXPECTED OUTPUT
test_begin_subtest "date query, two arguments"
notmuch search date:2009-11-17..2009-11-18 and from:keithp | notmuch_search_sanitize > EXPECTED
notmuch search --query=sexp '(and (date 2009-11-17 2009-11-18) (from keithp))' | notmuch_search_sanitize > OUTPUT
test_expect_equal_file EXPECTED OUTPUT
test_begin_subtest "date query, illegal nesting 1"
notmuch search --query=sexp '(to (date))' > OUTPUT 2>&1
cat <<EOF > EXPECTED
notmuch search: Syntax error in query
nested field: 'date' inside 'to'
EOF
test_expect_equal_file EXPECTED OUTPUT
test_begin_subtest "date query, illegal nesting 2"
notmuch search --query=sexp '(to (date 2021-11-18))' > OUTPUT 2>&1
cat <<EOF > EXPECTED
notmuch search: Syntax error in query
nested field: 'date' inside 'to'
EOF
test_expect_equal_file EXPECTED OUTPUT
test_begin_subtest "date query, illegal nesting 3"
notmuch search --query=sexp '(date (to))' > OUTPUT 2>&1
cat <<EOF > EXPECTED
notmuch search: Syntax error in query
expected atom as first argument of 'date'
EOF
test_expect_equal_file EXPECTED OUTPUT
test_begin_subtest "date query, illegal nesting 4"
notmuch search --query=sexp '(date today (to))' > OUTPUT 2>&1
cat <<EOF > EXPECTED
notmuch search: Syntax error in query
expected atom as second argument of 'date'
EOF
test_expect_equal_file EXPECTED OUTPUT
test_begin_subtest "date query, too many arguments"
notmuch search --query=sexp '(date yesterday and tommorow)' > OUTPUT 2>&1
cat <<EOF > EXPECTED
notmuch search: Syntax error in query
'date' expects maximum of two arguments
EOF
test_expect_equal_file EXPECTED OUTPUT
test_begin_subtest "date query, bad date"
notmuch search --query=sexp '(date "hawaiian-pizza-day")' > OUTPUT 2>&1
cat <<EOF > EXPECTED
notmuch search: Syntax error in query
Didn't understand date specification 'hawaiian-pizza-day'
EOF
test_expect_equal_file EXPECTED OUTPUT
test_begin_subtest "lastmod query, empty"
notmuch search from:keithp | notmuch_search_sanitize > EXPECTED
notmuch search --query=sexp '(and (lastmod) (from keithp))'| notmuch_search_sanitize > OUTPUT
test_expect_equal_file EXPECTED OUTPUT
test_begin_subtest "lastmod query, one argument"
notmuch tag +4EFC743A.3060609@april.org id:4EFC743A.3060609@april.org
revision=$(notmuch count --lastmod '*' | cut -f3)
notmuch search lastmod:$revision..$revision | notmuch_search_sanitize > EXPECTED
notmuch search --query=sexp "(and (lastmod $revision))" | notmuch_search_sanitize > OUTPUT
test_expect_equal_file EXPECTED OUTPUT
test_begin_subtest "lastmod query, two arguments"
notmuch tag +keithp from:keithp
revision2=$(notmuch count --lastmod '*' | cut -f3)
notmuch search lastmod:$revision..$revision2 | notmuch_search_sanitize > EXPECTED
notmuch search --query=sexp "(and (lastmod $revision $revision2))" | notmuch_search_sanitize > OUTPUT
test_expect_equal_file EXPECTED OUTPUT
test_begin_subtest "lastmod query, illegal nesting 1"
notmuch search --query=sexp '(to (lastmod))' > OUTPUT 2>&1
cat <<EOF > EXPECTED
notmuch search: Syntax error in query
nested field: 'lastmod' inside 'to'
EOF
test_expect_equal_file EXPECTED OUTPUT
test_begin_subtest "lastmod query, bad from revision"
notmuch search --query=sexp '(lastmod apples)' > OUTPUT 2>&1
cat <<EOF > EXPECTED
notmuch search: Syntax error in query
bad 'from' revision: 'apples'
EOF
test_expect_equal_file EXPECTED OUTPUT
test_begin_subtest "lastmod query, bad to revision"
notmuch search --query=sexp '(lastmod 0 apples)' > OUTPUT 2>&1
cat <<EOF > EXPECTED
notmuch search: Syntax error in query
bad 'to' revision: 'apples'
EOF
test_expect_equal_file EXPECTED OUTPUT
test_begin_subtest "lastmod query, illegal nesting 2"
notmuch search --query=sexp '(to (lastmod 2021-11-18))' > OUTPUT 2>&1
cat <<EOF > EXPECTED
notmuch search: Syntax error in query
nested field: 'lastmod' inside 'to'
EOF
test_expect_equal_file EXPECTED OUTPUT
test_begin_subtest "lastmod query, illegal nesting 3"
notmuch search --query=sexp '(lastmod (to))' > OUTPUT 2>&1
cat <<EOF > EXPECTED
notmuch search: Syntax error in query
expected atom as first argument of 'lastmod'
EOF
test_expect_equal_file EXPECTED OUTPUT
test_begin_subtest "lastmod query, illegal nesting 4"
notmuch search --query=sexp '(lastmod today (to))' > OUTPUT 2>&1
cat <<EOF > EXPECTED
notmuch search: Syntax error in query
expected atom as second argument of 'lastmod'
EOF
test_expect_equal_file EXPECTED OUTPUT
test_begin_subtest "lastmod query, too many arguments"
notmuch search --query=sexp '(lastmod yesterday and tommorow)' > OUTPUT 2>&1
cat <<EOF > EXPECTED
notmuch search: Syntax error in query
'lastmod' expects maximum of two arguments
EOF
test_expect_equal_file EXPECTED OUTPUT
test_begin_subtest "user header (unknown header)" test_begin_subtest "user header (unknown header)"
notmuch search --query=sexp '(FooBar)' >& OUTPUT notmuch search --query=sexp '(FooBar)' >& OUTPUT
cat <<EOF > EXPECTED cat <<EOF > EXPECTED

View file

@ -18,6 +18,12 @@ test_expect_equal "$output" "thread:XXX 2001-01-05 [1/1] Notmuch Test Suite; T
thread:XXX 2001-01-05 [1/1(2)] Notmuch Test Suite; Bears (inbox unread) thread:XXX 2001-01-05 [1/1(2)] Notmuch Test Suite; Bears (inbox unread)
thread:XXX 2001-01-05 [1/1] Notmuch Test Suite; Bites, stings, sad feelings (inbox unread)" thread:XXX 2001-01-05 [1/1] Notmuch Test Suite; Bites, stings, sad feelings (inbox unread)"
test_begin_subtest "search by path: specification (multiple)"
output=$(notmuch search path:bad path:bad/news path:things/bad | notmuch_search_sanitize)
test_expect_equal "$output" "thread:XXX 2001-01-05 [1/1] Notmuch Test Suite; To the bone (inbox unread)
thread:XXX 2001-01-05 [1/1(2)] Notmuch Test Suite; Bears (inbox unread)
thread:XXX 2001-01-05 [1/1] Notmuch Test Suite; Bites, stings, sad feelings (inbox unread)"
test_begin_subtest "Top level folder" test_begin_subtest "Top level folder"
output=$(notmuch search folder:'""' | notmuch_search_sanitize) output=$(notmuch search folder:'""' | notmuch_search_sanitize)
test_expect_equal "$output" "thread:XXX 2001-01-05 [1/1] Notmuch Test Suite; Top level (inbox unread)" test_expect_equal "$output" "thread:XXX 2001-01-05 [1/1] Notmuch Test Suite; Top level (inbox unread)"
@ -28,8 +34,13 @@ test_expect_equal "$output" "thread:XXX 2001-01-05 [1/1(2)] Notmuch Test Suite
test_begin_subtest "Folder search with --output=files" test_begin_subtest "Folder search with --output=files"
output=$(notmuch search --output=files folder:bad/news | notmuch_search_files_sanitize) output=$(notmuch search --output=files folder:bad/news | notmuch_search_files_sanitize)
test_expect_equal "$output" "MAIL_DIR/bad/news/msg-003 test_expect_equal "$output" "MAIL_DIR/bad/news/msg-XXX
MAIL_DIR/duplicate/bad/news/msg-003" MAIL_DIR/duplicate/bad/news/msg-XXX"
test_begin_subtest "Folder search with --output=files (trailing /)"
output=$(notmuch search --output=files folder:bad/news/ | notmuch_search_files_sanitize)
test_expect_equal "$output" "MAIL_DIR/bad/news/msg-XXX
MAIL_DIR/duplicate/bad/news/msg-XXX"
test_begin_subtest "After removing duplicate instance of matching path" test_begin_subtest "After removing duplicate instance of matching path"
rm -r "${MAIL_DIR}/bad/news" rm -r "${MAIL_DIR}/bad/news"
@ -39,7 +50,7 @@ test_expect_equal "$output" ""
test_begin_subtest "Folder search with --output=files part #2" test_begin_subtest "Folder search with --output=files part #2"
output=$(notmuch search --output=files folder:duplicate/bad/news | notmuch_search_files_sanitize) output=$(notmuch search --output=files folder:duplicate/bad/news | notmuch_search_files_sanitize)
test_expect_equal "$output" "MAIL_DIR/duplicate/bad/news/msg-003" test_expect_equal "$output" "MAIL_DIR/duplicate/bad/news/msg-XXX"
test_begin_subtest "After removing duplicate instance of matching path part #2" test_begin_subtest "After removing duplicate instance of matching path part #2"
output=$(notmuch search folder:duplicate/bad/news | notmuch_search_sanitize) output=$(notmuch search folder:duplicate/bad/news | notmuch_search_sanitize)
@ -120,6 +131,13 @@ test_expect_equal "$output" "MAIL_DIR/bar/17:2,
MAIL_DIR/bar/18:2, MAIL_DIR/bar/18:2,
MAIL_DIR/cur/51:2," MAIL_DIR/cur/51:2,"
test_begin_subtest "path: search (trailing /)"
output=$(notmuch search --output=files path:"bar/" | notmuch_search_files_sanitize | sort)
# cur/51:2, is a duplicate of bar/18:2,
test_expect_equal "$output" "MAIL_DIR/bar/17:2,
MAIL_DIR/bar/18:2,
MAIL_DIR/cur/51:2,"
test_begin_subtest "top level path: search" test_begin_subtest "top level path: search"
output=$(notmuch search --output=files path:'""' | notmuch_search_files_sanitize | sort) output=$(notmuch search --output=files path:'""' | notmuch_search_files_sanitize | sort)
test_expect_equal "$output" "MAIL_DIR/01:2, test_expect_equal "$output" "MAIL_DIR/01:2,

View file

@ -156,4 +156,46 @@ EOF
output=$(notmuch show --format=json --body=false --format-version=2 id:message-id@example.com) output=$(notmuch show --format=json --body=false --format-version=2 id:message-id@example.com)
test_expect_equal_json "$output" "$(cat EXPECTED)" test_expect_equal_json "$output" "$(cat EXPECTED)"
test_begin_subtest "show extra headers"
add_message "[subject]=\"extra-headers\"" "[date]=\"Sat, 01 Jan 2000 12:00:00 -0000\"" "[in-reply-to]=\"<parent@notmuch-test-suite>\"" "[body]=\"extra-headers test\""\
"[header]=\"Received: from mail.example.com (mail.example.com [1.1.1.1])
by mail.notmuchmail.org (some MTA) with ESMTP id 12345678
for <test_suite_other@notmuchmail.org>; Sat, 10 Apr 2010 07:54:51 -0400 (EDT)\"" \
notmuch config set show.extra_headers "in-reply-to;received"
output=$(notmuch show --format=json --body=false id:${gen_msg_id} | notmuch_json_show_sanitize)
cat <<EOF > EXPECTED
[
[
[
{
"crypto": {},
"date_relative": "2000-01-01",
"excluded": false,
"filename": [
"YYYYY"
],
"headers": {
"Date": "Sat, 01 Jan 2000 12:00:00 +0000",
"From": "Notmuch Test Suite <test_suite@notmuchmail.org>",
"In-Reply-To": "<parent@notmuch-test-suite>",
"Received": "from mail.example.com (mail.example.com [1.1.1.1])\tby mail.notmuchmail.org (some MTA) with ESMTP id 12345678\tfor <test_suite_other@notmuchmail.org>; Sat, 10 Apr 2010 07:54:51 -0400 (EDT)",
"Subject": "extra-headers",
"To": "Notmuch Test Suite <test_suite@notmuchmail.org>"
},
"id": "XXXXX",
"match": true,
"tags": [
"inbox",
"unread"
],
"timestamp": 946728000
},
[]
]
]
]
EOF
test_expect_equal_json "${output}" "$(cat EXPECTED)"
test_done test_done

View file

@ -47,4 +47,18 @@ filename=$(notmuch search --output=files "id:$id")
attachment_length=$(( $(base64 $NOTMUCH_SRCDIR/test/README | wc -c) - 1 )) attachment_length=$(( $(base64 $NOTMUCH_SRCDIR/test/README | wc -c) - 1 ))
test_expect_equal "$output" "((((:id \"$id\" :match t :excluded nil :filename (\"$filename\") :timestamp 946728000 :date_relative \"2000-01-01\" :tags (\"inbox\") :body ((:id 1 :content-type \"multipart/mixed\" :content ((:id 2 :content-type \"text/plain\" :content \"This is a test message with inline attachment with a filename\") (:id 3 :content-type \"application/octet-stream\" :content-disposition \"inline\" :filename \"README\" :content-transfer-encoding \"base64\" :content-length $attachment_length)))) :crypto () :headers (:Subject \"sexp-show-inline-attachment-filename\" :From \"Notmuch Test Suite <test_suite@notmuchmail.org>\" :To \"test_suite@notmuchmail.org\" :Date \"Sat, 01 Jan 2000 12:00:00 +0000\")) ())))" test_expect_equal "$output" "((((:id \"$id\" :match t :excluded nil :filename (\"$filename\") :timestamp 946728000 :date_relative \"2000-01-01\" :tags (\"inbox\") :body ((:id 1 :content-type \"multipart/mixed\" :content ((:id 2 :content-type \"text/plain\" :content \"This is a test message with inline attachment with a filename\") (:id 3 :content-type \"application/octet-stream\" :content-disposition \"inline\" :filename \"README\" :content-transfer-encoding \"base64\" :content-length $attachment_length)))) :crypto () :headers (:Subject \"sexp-show-inline-attachment-filename\" :From \"Notmuch Test Suite <test_suite@notmuchmail.org>\" :To \"test_suite@notmuchmail.org\" :Date \"Sat, 01 Jan 2000 12:00:00 +0000\")) ())))"
test_begin_subtest "show extra headers"
add_message "[subject]=\"extra-headers\"" "[date]=\"Sat, 01 Jan 2000 12:00:00 -0000\"" "[in-reply-to]=\"<parent@notmuch-test-suite>\"" "[body]=\"extra-headers test\""\
"[header]=\"Received: from mail.example.com (mail.example.com [1.1.1.1])
by mail.notmuchmail.org (some MTA) with ESMTP id 12345678
for <test_suite_other@notmuchmail.org>; Sat, 10 Apr 2010 07:54:51 -0400 (EDT)\"" \
notmuch config set show.extra_headers "in-reply-to;received"
notmuch show --format=sexp --body=false id:${gen_msg_id} | \
notmuch_dir_sanitize | sed 's/msg-[0-9]*/MSG/g'> OUTPUT
cat <<EOF > EXPECTED
((((:id "MSG@notmuch-test-suite" :match t :excluded nil :filename ("MAIL_DIR/MSG") :timestamp 946728000 :date_relative "2000-01-01" :tags ("inbox" "unread") :crypto () :headers (:Subject "extra-headers" :From "Notmuch Test Suite <test_suite@notmuchmail.org>" :To "Notmuch Test Suite <test_suite@notmuchmail.org>" :Date "Sat, 01 Jan 2000 12:00:00 +0000" :In-Reply-To "<parent@notmuch-test-suite>" :Received "from mail.example.com (mail.example.com [1.1.1.1])\011by mail.notmuchmail.org (some MTA) with ESMTP id 12345678\011for <test_suite_other@notmuchmail.org>; Sat, 10 Apr 2010 07:54:51 -0400 (EDT)")) ())))
EOF
test_expect_equal_file EXPECTED OUTPUT
test_done test_done

View file

@ -485,6 +485,31 @@ Sender <sender@example.com> writes:
EOF EOF
test_expect_equal_file EXPECTED OUTPUT test_expect_equal_file EXPECTED OUTPUT
test_begin_subtest "Reply with show.extra_headers set"
notmuch config set show.extra_headers Received
add_message '[from]="Sender <sender@example.com>"' \
[to]=test_suite_other@notmuchmail.org
test_emacs "(let ((message-hidden-headers '()))
(notmuch-search \"id:\\\"${gen_msg_id}\\\"\")
(notmuch-test-wait)
(notmuch-search-reply-to-thread)
(test-output))"
cat <<EOF >EXPECTED
From: Notmuch Test Suite <test_suite_other@notmuchmail.org>
To: Sender <sender@example.com>
Subject: Re: ${test_subtest_name}
In-Reply-To: <${gen_msg_id}>
Fcc: ${MAIL_DIR}/sent
References: <${gen_msg_id}>
--text follows this line--
Sender <sender@example.com> writes:
> This is just a test message (#${gen_msg_cnt})
EOF
notmuch config set show.extra_headers
test_expect_equal_file EXPECTED OUTPUT
test_begin_subtest "Reply from address in named group list within emacs" test_begin_subtest "Reply from address in named group list within emacs"
add_message '[from]="Sender <sender@example.com>"' \ add_message '[from]="Sender <sender@example.com>"' \
'[to]=group:test_suite@notmuchmail.org,someone@example.com\;' \ '[to]=group:test_suite@notmuchmail.org,someone@example.com\;' \
@ -680,7 +705,7 @@ References: <XXX>
--text follows this line-- --text follows this line--
test_suite@notmuchmail.org writes: test_suite@notmuchmail.org writes:
> This is just a test message (#7) > This is just a test message (#${gen_msg_cnt})
EOF EOF
test_expect_equal_file EXPECTED OUTPUT test_expect_equal_file EXPECTED OUTPUT

View file

@ -13,16 +13,29 @@ test_description='PGP/MIME signature verification and decryption'
test_require_emacs test_require_emacs
add_gnupg_home add_gnupg_home
test_begin_subtest "emacs delivery of signed message" test_begin_subtest "emacs delivery of signed message via fcc"
test_expect_success \ test_expect_success \
'emacs_fcc_message \ 'emacs_fcc_message \
"test signed message 001" \ "test signed message 001" \
"This is a test signed message." \ "This is a test signed message." \
"(mml-secure-message-sign)"' "(mml-secure-message-sign)"'
test_begin_subtest "emacs delivery of signed message via fcc and smtp"
emacs_deliver_message \
'signed message sent via SMTP' \
'This is a test that messages are sent via SMTP' \
"(add-hook 'message-send-mail-hook (lambda () (sleep-for 1)))
(mml-secure-message-sign)"
msg_file=$(notmuch search --output=files subject:signed-message-sent-via-SMTP)
test_expect_equal_message_body sent_message "$msg_file"
test_begin_subtest "signed part content-type indexing" test_begin_subtest "signed part content-type indexing"
output=$(notmuch search mimetype:multipart/signed and mimetype:application/pgp-signature | notmuch_search_sanitize) notmuch search mimetype:multipart/signed and mimetype:application/pgp-signature | notmuch_search_sanitize > OUTPUT
test_expect_equal "$output" "thread:XXX 2000-01-01 [1/1] Notmuch Test Suite; test signed message 001 (inbox signed)" cat <<EOF >EXPECTED
thread:XXX 2000-01-01 [1/1] Notmuch Test Suite; test signed message 001 (inbox signed)
thread:XXX 2000-01-01 [1/1] Notmuch Test Suite; signed message sent via SMTP (inbox signed)
EOF
test_expect_equal_file EXPECTED OUTPUT
test_begin_subtest "signature verification" test_begin_subtest "signature verification"
output=$(notmuch show --format=json --verify subject:"test signed message 001" \ output=$(notmuch show --format=json --verify subject:"test signed message 001" \

View file

@ -24,8 +24,8 @@ test_expect_equal "$output" "No new mail."
test_begin_subtest "Multiple files for same message" test_begin_subtest "Multiple files for same message"
cat <<EOF >EXPECTED cat <<EOF >EXPECTED
MAIL_DIR/msg-001 MAIL_DIR/msg-XXX
MAIL_DIR/spam/msg-001 MAIL_DIR/spam/msg-XXX
EOF EOF
notmuch search --output=files id:$id_x | notmuch_search_files_sanitize >OUTPUT notmuch search --output=files id:$id_x | notmuch_search_files_sanitize >OUTPUT
test_expect_equal_file EXPECTED OUTPUT test_expect_equal_file EXPECTED OUTPUT

View file

@ -0,0 +1,48 @@
#!/usr/bin/env bash
test_description="python bindings (notmuch test suite)"
. $(dirname "$0")/test-lib.sh || exit 1
if [ $NOTMUCH_HAVE_PYTHON3_CFFI -eq 0 -o $NOTMUCH_HAVE_PYTHON3_PYTEST -eq 0 ]; then
test_done
fi
add_email_corpus
cat <<EOF > recurse.py
from notmuch2 import Database
def show_msgs(msgs, level):
print('{:s} {:s}'.format(' ' * level*4, type(msgs).__name__))
for msg in msgs:
print('{:s} {:s}'.format(' ' * (level*4+2), type(msg).__name__))
replies=msg.replies()
show_msgs(replies, level+1)
db = Database(config=Database.CONFIG.SEARCH)
msg=db.find("87ocn0qh6d.fsf@yoom.home.cworth.org")
threads = db.threads(query="thread:"+msg.threadid)
thread = next (threads)
show_msgs(thread, 0)
EOF
test_begin_subtest "recursive traversal of replies (no crash)"
test_python < recurse.py
error=$?
test_expect_equal "${error}" 0
test_begin_subtest "recursive traversal of replies (output)"
test_python < recurse.py
tail -n 10 < OUTPUT > OUTPUT.sample
cat <<EOF > EXPECTED
OwnedMessage
MessageIter
OwnedMessage
MessageIter
OwnedMessage
MessageIter
OwnedMessage
MessageIter
OwnedMessage
MessageIter
EOF
test_expect_equal_file EXPECTED OUTPUT.sample
test_done

View file

@ -68,6 +68,16 @@ test_emacs '(notmuch-hello)
notmuch tag -$tag '*' notmuch tag -$tag '*'
test_expect_equal_file $EXPECTED/notmuch-hello-long-names OUTPUT test_expect_equal_file $EXPECTED/notmuch-hello-long-names OUTPUT
test_begin_subtest "All tags show up"
tag=exclude_me
notmuch tag +$tag '*'
notmuch config set search.exclude_tags $tag
test_emacs '(notmuch-hello)
(test-output)'
notmuch tag -$tag '*'
test_expect_equal_file $EXPECTED/notmuch-hello-all-tags OUTPUT
test_done
test_begin_subtest "notmuch-hello with nonexistent CWD" test_begin_subtest "notmuch-hello with nonexistent CWD"
test_emacs ' test_emacs '
(notmuch-hello) (notmuch-hello)

View file

@ -220,7 +220,9 @@ test_emacs '(notmuch-show "id:basic-encrypted@crypto.notmuchmail.org")
test_expect_equal_file $EXPECTED/notmuch-show-decrypted-message OUTPUT test_expect_equal_file $EXPECTED/notmuch-show-decrypted-message OUTPUT
test_begin_subtest "show encrypted rfc822 message" test_begin_subtest "show encrypted rfc822 message"
if ${TEST_EMACS} --quick --batch --eval '(kill-emacs (if (version< emacs-version "28") 0 1))'; then
test_subtest_known_broken test_subtest_known_broken
fi
test_emacs '(notmuch-show "id:encrypted-rfc822-attachment@crypto.notmuchmail.org") test_emacs '(notmuch-show "id:encrypted-rfc822-attachment@crypto.notmuchmail.org")
(test-visible-output)' (test-visible-output)'
test_expect_code 1 'fgrep "!!!" OUTPUT' test_expect_code 1 'fgrep "!!!" OUTPUT'

View file

@ -9,10 +9,8 @@ test_begin_subtest "building database"
test_expect_success "NOTMUCH_NEW" test_expect_success "NOTMUCH_NEW"
cat <<EOF > c_head cat <<EOF > c_head
#include <stdio.h>
#include <notmuch.h>
#include <notmuch-test.h> #include <notmuch-test.h>
#include <talloc.h>
int main (int argc, char** argv) int main (int argc, char** argv)
{ {
notmuch_database_t *db; notmuch_database_t *db;
@ -82,7 +80,7 @@ cat <<EOF > EXPECTED
== stdout == == stdout ==
0 0
== stderr == == stderr ==
A Xapian exception occurred at lib/database.cc:XXX: Database has been closed A Xapian exception occurred at database.cc:XXX: Database has been closed
EOF EOF
test_expect_equal_file EXPECTED OUTPUT test_expect_equal_file EXPECTED OUTPUT
@ -147,7 +145,7 @@ cat <<EOF > EXPECTED
== stdout == == stdout ==
1 1
== stderr == == stderr ==
A Xapian exception occurred at lib/database.cc:XXX: Database has been closed A Xapian exception occurred at database.cc:XXX: Database has been closed
EOF EOF
test_expect_equal_file EXPECTED OUTPUT test_expect_equal_file EXPECTED OUTPUT

View file

@ -9,10 +9,8 @@ test_begin_subtest "building database"
test_expect_success "NOTMUCH_NEW" test_expect_success "NOTMUCH_NEW"
cat <<EOF > c_head cat <<EOF > c_head
#include <stdio.h>
#include <notmuch.h>
#include <notmuch-test.h> #include <notmuch-test.h>
#include <talloc.h>
int main (int argc, char** argv) int main (int argc, char** argv)
{ {
notmuch_database_t *db; notmuch_database_t *db;
@ -53,7 +51,7 @@ cat <<EOF > EXPECTED
== stdout == == stdout ==
1 1
== stderr == == stderr ==
A Xapian exception occurred at lib/directory.cc:XXX: Database has been closed A Xapian exception occurred at directory.cc:XXX: Database has been closed
EOF EOF
test_expect_equal_file EXPECTED OUTPUT test_expect_equal_file EXPECTED OUTPUT
@ -70,7 +68,7 @@ cat <<EOF > EXPECTED
== stdout == == stdout ==
1 1
== stderr == == stderr ==
A Xapian exception occurred at lib/directory.cc:XXX: Database has been closed A Xapian exception occurred at directory.cc:XXX: Database has been closed
EOF EOF
test_expect_equal_file EXPECTED OUTPUT test_expect_equal_file EXPECTED OUTPUT

View file

@ -9,10 +9,8 @@ test_begin_subtest "building database"
test_expect_success "NOTMUCH_NEW" test_expect_success "NOTMUCH_NEW"
cat <<EOF > c_head cat <<EOF > c_head
#include <stdio.h>
#include <notmuch.h>
#include <notmuch-test.h> #include <notmuch-test.h>
#include <talloc.h>
int main (int argc, char** argv) int main (int argc, char** argv)
{ {
notmuch_database_t *db; notmuch_database_t *db;

View file

@ -19,9 +19,8 @@ cat <<'EOF' > c_tail
EOF EOF
cat <<EOF > c_head0 cat <<EOF > c_head0
#include <stdio.h>
#include <notmuch.h>
#include <notmuch-test.h> #include <notmuch-test.h>
int main (int argc, char** argv) int main (int argc, char** argv)
{ {
notmuch_database_t *db; notmuch_database_t *db;

View file

@ -24,9 +24,8 @@ cat <<'EOF' > c_tail
EOF EOF
cat <<EOF > c_head cat <<EOF > c_head
#include <stdio.h>
#include <notmuch.h>
#include <notmuch-test.h> #include <notmuch-test.h>
int main (int argc, char** argv) int main (int argc, char** argv)
{ {
notmuch_database_t *db; notmuch_database_t *db;

View file

@ -23,8 +23,6 @@ EOF
} }
cat <<EOF > c_head cat <<EOF > c_head
#include <string.h>
#include <stdlib.h>
#include <notmuch-test.h> #include <notmuch-test.h>
int main (int argc, char** argv) int main (int argc, char** argv)
@ -272,6 +270,29 @@ EOF
test_expect_equal_file EXPECTED OUTPUT test_expect_equal_file EXPECTED OUTPUT
restore_database restore_database
test_begin_subtest "notmuch_config_get_values (ignore leading/trailing whitespace)"
cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR} ${NOTMUCH_CONFIG} %NULL%
{
notmuch_config_values_t *values;
EXPECT0(notmuch_config_set (db, NOTMUCH_CONFIG_NEW_TAGS, " a ; b c ; d "));
for (values = notmuch_config_get_values (db, NOTMUCH_CONFIG_NEW_TAGS);
notmuch_config_values_valid (values);
notmuch_config_values_move_to_next (values))
{
puts (notmuch_config_values_get (values));
}
}
EOF
cat <<'EOF' >EXPECTED
== stdout ==
a
b c
d
== stderr ==
EOF
test_expect_equal_file EXPECTED OUTPUT
restore_database
test_begin_subtest "notmuch_config_get_values_string" test_begin_subtest "notmuch_config_get_values_string"
cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR} ${NOTMUCH_CONFIG} %NULL% cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR} ${NOTMUCH_CONFIG} %NULL%
{ {
@ -418,6 +439,7 @@ cat <<'EOF' >EXPECTED
09: 'NULL' 09: 'NULL'
10: 'USER_FULL_NAME' 10: 'USER_FULL_NAME'
11: '8000' 11: '8000'
12: 'NULL'
== stderr == == stderr ==
EOF EOF
unset MAILDIR unset MAILDIR
@ -616,8 +638,6 @@ cp notmuch-config.bak notmuch-config
test_expect_equal_file EXPECTED OUTPUT test_expect_equal_file EXPECTED OUTPUT
cat <<EOF > c_head2 cat <<EOF > c_head2
#include <string.h>
#include <stdlib.h>
#include <notmuch-test.h> #include <notmuch-test.h>
int main (int argc, char** argv) int main (int argc, char** argv)
@ -730,6 +750,7 @@ cat <<'EOF' >EXPECTED
09: 'test_suite_other@notmuchmail.org;test_suite@otherdomain.org' 09: 'test_suite_other@notmuchmail.org;test_suite@otherdomain.org'
10: 'Notmuch Test Suite' 10: 'Notmuch Test Suite'
11: '8000' 11: '8000'
12: 'NULL'
== stderr == == stderr ==
EOF EOF
test_expect_equal_file EXPECTED OUTPUT test_expect_equal_file EXPECTED OUTPUT
@ -763,6 +784,7 @@ cat <<'EOF' >EXPECTED
09: 'NULL' 09: 'NULL'
10: 'USER_FULL_NAME' 10: 'USER_FULL_NAME'
11: '8000' 11: '8000'
12: 'NULL'
== stderr == == stderr ==
EOF EOF
test_expect_equal_file EXPECTED OUTPUT.clean test_expect_equal_file EXPECTED OUTPUT.clean
@ -839,6 +861,7 @@ maildir.synchronize_flags true
new.ignore sekrit_junk new.ignore sekrit_junk
new.tags unread;inbox new.tags unread;inbox
search.exclude_tags foo;bar;fub search.exclude_tags foo;bar;fub
show.extra_headers (null)
test.key1 testvalue1 test.key1 testvalue1
test.key2 testvalue2 test.key2 testvalue2
user.name Notmuch Test Suite user.name Notmuch Test Suite
@ -953,6 +976,7 @@ EOF
test_expect_equal_file EXPECTED OUTPUT test_expect_equal_file EXPECTED OUTPUT
test_begin_subtest "open: database parameter overrides implicit config" test_begin_subtest "open: database parameter overrides implicit config"
cp $NOTMUCH_CONFIG ${NOTMUCH_CONFIG}.bak
notmuch config set database.path ${MAIL_DIR}/nonexistent notmuch config set database.path ${MAIL_DIR}/nonexistent
cat c_head3 - c_tail3 <<'EOF' | test_C ${MAIL_DIR} cat c_head3 - c_tail3 <<'EOF' | test_C ${MAIL_DIR}
const char *path = NULL; const char *path = NULL;
@ -963,6 +987,7 @@ cat c_head3 - c_tail3 <<'EOF' | test_C ${MAIL_DIR}
path = notmuch_database_get_path (db); path = notmuch_database_get_path (db);
printf ("path: %s\n", path ? path : "(null)"); printf ("path: %s\n", path ? path : "(null)");
EOF EOF
cp ${NOTMUCH_CONFIG}.bak ${NOTMUCH_CONFIG}
cat <<EOF> EXPECTED cat <<EOF> EXPECTED
== stdout == == stdout ==
status: 0 status: 0
@ -973,4 +998,43 @@ EOF
notmuch_dir_sanitize < OUTPUT > OUTPUT.clean notmuch_dir_sanitize < OUTPUT > OUTPUT.clean
test_expect_equal_file EXPECTED OUTPUT.clean test_expect_equal_file EXPECTED OUTPUT.clean
cat <<EOF > c_body
notmuch_status_t st = notmuch_database_open_with_config(NULL,
NOTMUCH_DATABASE_MODE_READ_ONLY,
"", NULL, &db, NULL);
printf ("status == SUCCESS: %d\n", st == NOTMUCH_STATUS_SUCCESS);
if (db) {
const char *mail_root = NULL;
mail_root = notmuch_config_get (db, NOTMUCH_CONFIG_MAIL_ROOT);
printf ("mail_root: %s\n", mail_root ? mail_root : "(null)");
}
EOF
cat <<EOF> EXPECTED.common
== stdout ==
status == SUCCESS: 0
db == NULL: 1
== stderr ==
EOF
test_begin_subtest "open/error: config=empty with no mail root in db "
old_NOTMUCH_CONFIG=${NOTMUCH_CONFIG}
unset NOTMUCH_CONFIG
cat c_head3 c_body c_tail3 | test_C
export NOTMUCH_CONFIG=${old_NOTMUCH_CONFIG}
notmuch_dir_sanitize < OUTPUT > OUTPUT.clean
test_expect_equal_file EXPECTED.common OUTPUT.clean
test_begin_subtest "open/error: config=empty with no mail root in db (xdg)"
old_NOTMUCH_CONFIG=${NOTMUCH_CONFIG}
unset NOTMUCH_CONFIG
backup_database
mkdir -p home/.local/share/notmuch
mv mail/.notmuch home/.local/share/notmuch/default
cat c_head3 c_body c_tail3 | test_C
restore_database
export NOTMUCH_CONFIG=${old_NOTMUCH_CONFIG}
notmuch_dir_sanitize < OUTPUT > OUTPUT.clean
test_expect_equal_file EXPECTED.common OUTPUT.clean
test_done test_done

View file

@ -6,8 +6,6 @@ test_description="library reopen API"
add_email_corpus add_email_corpus
cat <<EOF > c_head cat <<EOF > c_head
#include <string.h>
#include <stdlib.h>
#include <notmuch-test.h> #include <notmuch-test.h>
int main (int argc, char** argv) int main (int argc, char** argv)

View file

@ -6,10 +6,6 @@ test_description="message property API"
add_email_corpus add_email_corpus
cat <<EOF > c_head cat <<EOF > c_head
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <talloc.h>
#include <notmuch-test.h> #include <notmuch-test.h>
void print_properties (notmuch_message_t *message, const char *prefix, notmuch_bool_t exact) { void print_properties (notmuch_message_t *message, const char *prefix, notmuch_bool_t exact) {

View file

@ -9,9 +9,6 @@ if [ $NOTMUCH_HAVE_XAPIAN_DB_RETRY_LOCK -ne 1 ]; then
test_subtest_known_broken test_subtest_known_broken
fi fi
test_C ${MAIL_DIR} <<'EOF' test_C ${MAIL_DIR} <<'EOF'
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <notmuch-test.h> #include <notmuch-test.h>
void void

View file

@ -10,11 +10,8 @@ test_begin_subtest "catching DatabaseModifiedError in _notmuch_message_ensure_me
first_id=$(notmuch search --output=messages '*'| head -1 | sed s/^id://) first_id=$(notmuch search --output=messages '*'| head -1 | sed s/^id://)
test_C ${MAIL_DIR} <<EOF test_C ${MAIL_DIR} <<EOF
#include <unistd.h>
#include <stdlib.h>
#include <notmuch-test.h> #include <notmuch-test.h>
#include <talloc.h>
#include <assert.h>
int int
main (int argc, char **argv) main (int argc, char **argv)
{ {

View file

@ -58,13 +58,13 @@ test_expect_equal_file EXPECTED OUTPUT
test_begin_subtest "notmuch search --output=files with partially gzipped mail store" test_begin_subtest "notmuch search --output=files with partially gzipped mail store"
notmuch search --output=files '*' | notmuch_search_files_sanitize > OUTPUT notmuch search --output=files '*' | notmuch_search_files_sanitize > OUTPUT
cat <<EOF > EXPECTED cat <<EOF > EXPECTED
MAIL_DIR/msg-001.gz MAIL_DIR/msg-XXX.gz
MAIL_DIR/msg-002.gz MAIL_DIR/msg-XXX.gz
MAIL_DIR/msg-003.gz MAIL_DIR/msg-XXX.gz
MAIL_DIR/msg-004 MAIL_DIR/msg-XXX
MAIL_DIR/msg-005.gz MAIL_DIR/msg-XXX.gz
MAIL_DIR/msg-006 MAIL_DIR/msg-XXX
MAIL_DIR/msg-007.gz MAIL_DIR/msg-XXX.gz
EOF EOF
test_expect_equal_file EXPECTED OUTPUT test_expect_equal_file EXPECTED OUTPUT

Some files were not shown because too many files have changed in this diff Show more