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

[dgit distro=debian no-split --quilt=linear]
 -----BEGIN PGP SIGNATURE-----
 
 iQIzBAABCAAdFiEEkiyHYXwaY0SiY6fqA0U5G1WqFSEFAmGzWWMACgkQA0U5G1Wq
 FSG8xg/+Jx1z1pqC9CAQpw1Ccb9OuYsniwj7rvhfutbS82zkuFFq2UdjHa3rP31a
 zABqN9b9VDUtsEE5Wyf+Md7SZ202NQK3sZ/vh///CHis12CLaMZkvpi+twi9f5pj
 pMzcwXJ5voaHxErDvvjIp3FV9+RsfpZl5owXpav8j6Hl05TZ4xHfopF0O9rSJQhK
 +KuUjb2zGO7clP78cEZhEn+hk5DmYf0obRDK7hHgL/n8FVB+pmUsa5riauW2u/Jm
 ozhL+sC3DjVspQLZ1SgHHBocfuLCOlMcp2sqLHEN9pqF7XcW3m0aq9GFfYlUNh1m
 kc8HpTxHI7hcQmS0zsURgqV2fNR9okS8ye0zaCMw5oJ7h4J36i5kbRj0TPfRW7h3
 SKPuFGELPVqGj3NjUEhS34vgCcocmYmYqurAGKl1OnDouDT+ZpNMeg0EFrfhU/N3
 jcRTYcw6IarFJ85h8uFPBqIXd0H8KoPvUTa85eS4a/wqcrxPZTgtuqu35mlpe+1U
 77TyoQIT5AYa8c0N51a60jGK1ijY0/SBtMtb+hRxYuWLCU9wnWZEOBPakbqTVIdn
 +iBR4kYwQfhewqv2yLdCp/GsLqhQs2X79yBChWtcUc4WtM5B5QVlgU9TloyJJVHA
 2HB6ZkJW3MlzIhdJ/naemCuoW3D5ApnXJzGIWJk1nsSyR/Gq66k=
 =lO2D
 -----END PGP SIGNATURE-----

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

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

[dgit distro=debian no-split --quilt=linear]
This commit is contained in:
David Bremner 2022-01-10 10:54:03 -04:00
commit 1b58ea1e66
89 changed files with 3763 additions and 496 deletions

View file

@ -41,6 +41,15 @@ Talloc, and zlib which are each described below:
GMime is available from https://github.com/jstedfast/gmime
Sfsexp
------
sfsexp is the "small fast s-expression" library. Notmuch
optionally use it to provide a second query parser.
sfsexp is available from https://github.com/mjsottile/sfsexp.
In Debian Bookworm and later, install libsexp-dev.
Talloc
------
Talloc is a memory-pool allocator used by Notmuch.

80
NEWS
View file

@ -1,3 +1,73 @@
Notmuch 0.34.2 (2021-12-09)
===========================
Library
-------
Fix a bug that wrongly resolved conflict between the `database_path`
parameter to `notmuch_database_open_with_config` and configuration
item `database.path` in favour of the latter.
Python Bindings (notmuch2)
--------------------------
When building the documentation for the `notmuch2` python module,
import from the built module, not a system wide installed one.
The notmuch2.Database constructor now uses the library function
`notmuch_database_open_with_config` to support the same configuration
and database location options as the library does.
Fix some unprintable exception objects.
Notmuch 0.34.1 (2021-11-03)
===========================
Library
-------
Fix for deallocation and nulling of output parameter for
notmuch_database_{open_with,create_with,load}_config when errors
occur. This change fixes a potential use-after-free bug that has been
present since 0.32. This release also improves the documentation of
status returns for the same 3 functions.
Notmuch 0.34 (2021-10-20)
=========================
General
-------
An optional new s-expression based query parser is available if
notmuch is built with the `sfsexp` library. See
notmuch-sexp-queries(7) for syntax, and use `notmuch config get
built_with.sexpr_query` to check if notmuch is compiled with
s-expression query support.
CLI
---
Support multiple `Delivered-To` headers in notmuch-reply(1).
Emacs
-----
Functions are now allowed in `notmuch-search-result-format`.
Improvements to unthreaded view on large threads.
Tolerate bad/missing working directory for most commands.
Allow customization of tree drawing symbols in notmuch-tree mode.
Notmuch 0.33.2 (2021-09-30)
===========================
Tests
-----
Improve reliability of T355-smime by changing gpgsm initialization.
Notmuch 0.33.1 (2021-09-10)
===========================
@ -14,7 +84,7 @@ Notmuch 0.33 (2021-09-03)
Library
-------
Correct documentatation about transactions.
Correct documentation about transactions.
Add a configurable automatic commit of transactions. See
`database.autocommit` in notmuch-config(1).
@ -183,7 +253,7 @@ notmuch_database_remove_message or notmuch_message_delete in one
session has been fixed.
As always, the canonical source of API documentation is
`lib/notmuch.h`, or the doxygen formatted documentation in `notmuch(3)`
`lib/notmuch.h`, or the doxygen formatted documentation in `notmuch(3)`.
CLI
---
@ -249,12 +319,12 @@ Fix for exclude tags in notmuch2 bindings.
Build
-----
Portability update for T360-symbol-hiding
Portability update for T360-symbol-hiding.
Library
-------
Fix for memory error in notmuch_database_get_config_list
Fix for memory error in notmuch_database_get_config_list.
Notmuch 0.31.2 (2020-11-08)
===========================
@ -469,7 +539,7 @@ Command Line Interface
----------------------
`notmuch show` now supports --body=false and --include-html with
--format=text
--format=text.
Fix several performance problems with `notmuch reindex`.

View file

@ -53,6 +53,7 @@ ffibuilder.cdef(
NOTMUCH_STATUS_NO_CONFIG,
NOTMUCH_STATUS_NO_DATABASE,
NOTMUCH_STATUS_DATABASE_EXISTS,
NOTMUCH_STATUS_BAD_QUERY_SYNTAX,
NOTMUCH_STATUS_LAST_STATUS
} notmuch_status_t;
typedef enum {
@ -102,20 +103,18 @@ ffibuilder.cdef(
notmuch_status_to_string (notmuch_status_t status);
notmuch_status_t
notmuch_database_create_verbose (const char *path,
notmuch_database_t **database,
char **error_message);
notmuch_database_create_with_config (const char *database_path,
const char *config_path,
const char *profile,
notmuch_database_t **database,
char **error_message);
notmuch_status_t
notmuch_database_create (const char *path, notmuch_database_t **database);
notmuch_status_t
notmuch_database_open_verbose (const char *path,
notmuch_database_mode_t mode,
notmuch_database_t **database,
char **error_message);
notmuch_status_t
notmuch_database_open (const char *path,
notmuch_database_mode_t mode,
notmuch_database_t **database);
notmuch_database_open_with_config (const char *database_path,
notmuch_database_mode_t mode,
const char *config_path,
const char *profile,
notmuch_database_t **database,
char **error_message);
notmuch_status_t
notmuch_database_close (notmuch_database_t *database);
notmuch_status_t

View file

@ -31,6 +31,9 @@ class Mode(enum.Enum):
READ_ONLY = capi.lib.NOTMUCH_DATABASE_MODE_READ_ONLY
READ_WRITE = capi.lib.NOTMUCH_DATABASE_MODE_READ_WRITE
class ConfigFile(enum.Enum):
EMPTY = b''
SEARCH = capi.ffi.NULL
class QuerySortOrder(enum.Enum):
OLDEST_FIRST = capi.lib.NOTMUCH_SORT_OLDEST_FIRST
@ -71,6 +74,9 @@ class Database(base.NotmuchObject):
:cvar EXCLUDE: Which messages to exclude from queries, ``TRUE``,
``FLAG``, ``FALSE`` or ``ALL``. See the query documentation
for details.
:cvar CONFIG: Control loading of config file. Enumeration of
``EMPTY`` (don't load a config file), and ``SEARCH`` (search as
in :ref:`config_search`)
:cvar AddedMessage: A namedtuple ``(msg, dup)`` used by
:meth:`add` as return value.
:cvar STR_MODE_MAP: A map mapping strings to :attr:`MODE` items.
@ -81,9 +87,8 @@ class Database(base.NotmuchObject):
still open.
:param path: The directory of where the database is stored. If
``None`` the location will be read from the user's
configuration file, respecting the ``NOTMUCH_CONFIG``
environment variable if set.
``None`` the location will be searched according to
:ref:`database`
:type path: str, bytes, os.PathLike or pathlib.Path
:param mode: The mode to open the database in. One of
:attr:`MODE.READ_ONLY` OR :attr:`MODE.READ_WRITE`. For
@ -91,6 +96,8 @@ class Database(base.NotmuchObject):
:attr:`MODE.READ_ONLY` and ``rw`` for :attr:`MODE.READ_WRITE`.
:type mode: :attr:`MODE` or str.
:param config: Where to load the configuration from, if any.
:type config: :attr:`CONFIG.EMPTY`, :attr:`CONFIG.SEARCH`, str, bytes, os.PathLike, pathlib.Path
:raises KeyError: if an unknown mode string is used.
:raises OSError: or subclasses if the configuration file can not
be opened.
@ -102,6 +109,7 @@ class Database(base.NotmuchObject):
MODE = Mode
SORT = QuerySortOrder
EXCLUDE = QueryExclude
CONFIG = ConfigFile
AddedMessage = collections.namedtuple('AddedMessage', ['msg', 'dup'])
_db_p = base.MemoryPointer()
STR_MODE_MAP = {
@ -109,18 +117,40 @@ class Database(base.NotmuchObject):
'rw': MODE.READ_WRITE,
}
def __init__(self, path=None, mode=MODE.READ_ONLY):
@staticmethod
def _cfg_path_encode(path):
if isinstance(path,ConfigFile):
path = path.value
elif path is None:
path = capi.ffi.NULL
elif not hasattr(os, 'PathLike') and isinstance(path, pathlib.Path):
path = bytes(path)
else:
path = os.fsencode(path)
return path
@staticmethod
def _db_path_encode(path):
if path is None:
path = capi.ffi.NULL
elif not hasattr(os, 'PathLike') and isinstance(path, pathlib.Path):
path = bytes(path)
else:
path = os.fsencode(path)
return path
def __init__(self, path=None, mode=MODE.READ_ONLY, config=CONFIG.EMPTY):
if isinstance(mode, str):
mode = self.STR_MODE_MAP[mode]
self.mode = mode
if path is None:
path = self.default_path()
if not hasattr(os, 'PathLike') and isinstance(path, pathlib.Path):
path = bytes(path)
db_pp = capi.ffi.new('notmuch_database_t **')
cmsg = capi.ffi.new('char**')
ret = capi.lib.notmuch_database_open_verbose(os.fsencode(path),
mode.value, db_pp, cmsg)
ret = capi.lib.notmuch_database_open_with_config(self._db_path_encode(path),
mode.value,
self._cfg_path_encode(config),
capi.ffi.NULL,
db_pp, cmsg)
if cmsg[0]:
msg = capi.ffi.string(cmsg[0]).decode(errors='replace')
capi.lib.free(cmsg[0])
@ -132,18 +162,20 @@ class Database(base.NotmuchObject):
self.closed = False
@classmethod
def create(cls, path=None):
def create(cls, path=None, config=ConfigFile.EMPTY):
"""Create and open database in READ_WRITE mode.
This is creates a new notmuch database and returns an opened
instance in :attr:`MODE.READ_WRITE` mode.
:param path: The directory of where the database is stored. If
``None`` the location will be read from the user's
configuration file, respecting the ``NOTMUCH_CONFIG``
environment variable if set.
:param path: The directory of where the database is stored.
If ``None`` the location will be read searched by the
notmuch library (see notmuch(3)::notmuch_open_with_config).
:type path: str, bytes or os.PathLike
:param config: The pathname of the notmuch configuration file.
:type config: :attr:`CONFIG.EMPTY`, :attr:`CONFIG.SEARCH`, str, bytes, os.PathLike, pathlib.Path
:raises OSError: or subclasses if the configuration file can not
be opened.
:raises configparser.Error: or subclasses if the configuration
@ -154,14 +186,13 @@ class Database(base.NotmuchObject):
:returns: The newly created instance.
"""
if path is None:
path = cls.default_path()
if not hasattr(os, 'PathLike') and isinstance(path, pathlib.Path):
path = bytes(path)
db_pp = capi.ffi.new('notmuch_database_t **')
cmsg = capi.ffi.new('char**')
ret = capi.lib.notmuch_database_create_verbose(os.fsencode(path),
db_pp, cmsg)
ret = capi.lib.notmuch_database_create_with_config(cls._db_path_encode(path),
cls._cfg_path_encode(config),
capi.ffi.NULL,
db_pp, cmsg)
if cmsg[0]:
msg = capi.ffi.string(cmsg[0]).decode(errors='replace')
capi.lib.free(cmsg[0])
@ -176,7 +207,7 @@ class Database(base.NotmuchObject):
ret = capi.lib.notmuch_database_destroy(db_pp[0])
if ret != capi.lib.NOTMUCH_STATUS_SUCCESS:
raise errors.NotmuchError(ret)
return cls(path, cls.MODE.READ_WRITE)
return cls(path, cls.MODE.READ_WRITE, config=config)
@staticmethod
def default_path(cfg_path=None):
@ -187,8 +218,8 @@ class Database(base.NotmuchObject):
:param cfg_path: The pathname of the notmuch configuration file.
If not specified tries to use the pathname provided in the
:env:`NOTMUCH_CONFIG` environment variable and falls back
to :file:`~/.notmuch-config.
:envvar:`NOTMUCH_CONFIG` environment variable and falls back
to :file:`~/.notmuch-config`.
:type cfg_path: str, bytes, os.PathLike or pathlib.Path.
:returns: The path of the database, which does not necessarily
@ -198,8 +229,11 @@ class Database(base.NotmuchObject):
be opened.
:raises configparser.Error: or subclasses if the configuration
file can not be parsed.
:raises NotmuchError if the config file does not have the
:raises NotmuchError: if the config file does not have the
database.path setting.
.. deprecated:: 0.35
Use the ``config`` parameter to :meth:`__init__` or :meth:`__create__` instead.
"""
if not cfg_path:
cfg_path = _config_pathname()

View file

@ -56,6 +56,8 @@ class NotmuchError(Exception):
NoDatabaseError,
capi.lib.NOTMUCH_STATUS_DATABASE_EXISTS:
DatabaseExistsError,
capi.lib.NOTMUCH_STATUS_BAD_QUERY_SYNTAX:
QuerySyntaxError,
}
return types[status]
@ -81,7 +83,8 @@ class NotmuchError(Exception):
if self.message:
return self.message
elif self.status:
return capi.lib.notmuch_status_to_string(self.status)
char_str = capi.lib.notmuch_status_to_string(self.status)
return capi.ffi.string(char_str).decode(errors='replace')
else:
return 'Unknown error'
@ -103,6 +106,7 @@ class IllegalArgumentError(NotmuchError): pass
class NoConfigError(NotmuchError): pass
class NoDatabaseError(NotmuchError): pass
class DatabaseExistsError(NotmuchError): pass
class QuerySyntaxError(NotmuchError): pass
class ObjectDestroyedError(NotmuchError):
"""The object has already been destroyed and it's memory freed.

View file

@ -0,0 +1,8 @@
from notmuch2 import _capi as capi
from notmuch2 import _errors as errors
def test_status_no_message():
exc = errors.NotmuchError(capi.lib.NOTMUCH_STATUS_PATH_ERROR)
assert exc.status == capi.lib.NOTMUCH_STATUS_PATH_ERROR
assert exc.message is None
assert str(exc) == 'Path supplied is illegal for this function'

View file

@ -1 +1 @@
0.33.1
0.34.2

View file

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

172
configure vendored
View file

@ -496,16 +496,16 @@ int main () {
}
EOF
if ! TEMP_GPG=$(mktemp -d "${TMPDIR:-/tmp}/notmuch.XXXXXX"); then
printf 'No.\nCould not make tempdir for testing session-key support.\n'
errors=$((errors + 1))
printf 'No.\nCould not make tempdir for testing session-key support.\n'
errors=$((errors + 1))
elif ${CC} ${CFLAGS} ${gmime_cflags} _check_session_keys.c ${gmime_ldflags} -o _check_session_keys \
&& GNUPGHOME=${TEMP_GPG} gpg --batch --quiet --import < "$srcdir"/test/gnupg-secret-key.asc \
&& SESSION_KEY=$(GNUPGHOME=${TEMP_GPG} ./_check_session_keys) \
&& [ $SESSION_KEY = 9:0BACD64099D1468AB07C796F0C0AC4851948A658A15B34E803865E9FC635F2F5 ]
&& GNUPGHOME=${TEMP_GPG} gpg --batch --quiet --import < "$srcdir"/test/gnupg-secret-key.asc \
&& SESSION_KEY=$(GNUPGHOME=${TEMP_GPG} ./_check_session_keys) \
&& [ $SESSION_KEY = 9:0BACD64099D1468AB07C796F0C0AC4851948A658A15B34E803865E9FC635F2F5 ]
then
printf "OK.\n"
printf "OK.\n"
else
cat <<EOF
cat <<EOF
No.
*** Error: Could not extract session keys from encrypted message.
@ -515,15 +515,15 @@ version of GPGME.
Please try to rebuild your version of GMime against a more recent
version of GPGME (at least GPGME 1.8.0).
EOF
if command -v gpgme-config >/dev/null; then
printf 'Your current GPGME development version is: %s\n' "$(gpgme-config --version)"
else
printf 'You do not have the GPGME development libraries installed.\n'
fi
errors=$((errors + 1))
if command -v gpgme-config >/dev/null; then
printf 'Your current GPGME development version is: %s\n' "$(gpgme-config --version)"
else
printf 'You do not have the GPGME development libraries installed.\n'
fi
errors=$((errors + 1))
fi
if [ -n "$TEMP_GPG" -a -d "$TEMP_GPG" ]; then
rm -rf "$TEMP_GPG"
rm -rf "$TEMP_GPG"
fi
# see https://github.com/jstedfast/gmime/pull/90
@ -570,36 +570,36 @@ int main () {
}
EOF
if ! TEMP_GPG=$(mktemp -d "${TMPDIR:-/tmp}/notmuch.XXXXXX"); then
printf 'No.\nCould not make tempdir for testing X.509 certificate validity support.\n'
errors=$((errors + 1))
printf 'No.\nCould not make tempdir for testing X.509 certificate validity support.\n'
errors=$((errors + 1))
elif ${CC} ${CFLAGS} ${gmime_cflags} _check_x509_validity.c ${gmime_ldflags} -o _check_x509_validity \
&& echo disable-crl-checks > "$TEMP_GPG/gpgsm.conf" \
&& echo "4D:E0:FF:63:C0:E9:EC:01:29:11:C8:7A:EE:DA:3A:9A:7F:6E:C1:0D S" >> "$TEMP_GPG/trustlist.txt" \
&& GNUPGHOME=${TEMP_GPG} gpgsm --batch --quiet --import < "$srcdir"/test/smime/ca.crt
&& echo disable-crl-checks > "$TEMP_GPG/gpgsm.conf" \
&& echo "4D:E0:FF:63:C0:E9:EC:01:29:11:C8:7A:EE:DA:3A:9A:7F:6E:C1:0D S" >> "$TEMP_GPG/trustlist.txt" \
&& GNUPGHOME=${TEMP_GPG} gpgsm --batch --quiet --import < "$srcdir"/test/smime/ca.crt
then
if GNUPGHOME=${TEMP_GPG} ./_check_x509_validity; then
gmime_x509_cert_validity=1
printf "Yes.\n"
else
gmime_x509_cert_validity=0
printf "No.\n"
if pkg-config --exists "gmime-3.0 >= 3.2.7"; then
cat <<EOF
if GNUPGHOME=${TEMP_GPG} ./_check_x509_validity; then
gmime_x509_cert_validity=1
printf "Yes.\n"
else
gmime_x509_cert_validity=0
printf "No.\n"
if pkg-config --exists "gmime-3.0 >= 3.2.7"; then
cat <<EOF
*** Error: GMime fails to calculate X.509 certificate validity, and
is later than 3.2.7, which should have fixed this issue.
Please follow up on https://github.com/jstedfast/gmime/pull/90 with
more details.
EOF
errors=$((errors + 1))
fi
fi
errors=$((errors + 1))
fi
fi
else
printf 'No.\nFailed to set up gpgsm for testing X.509 certificate validity support.\n'
errors=$((errors + 1))
printf 'No.\nFailed to set up gpgsm for testing X.509 certificate validity support.\n'
errors=$((errors + 1))
fi
if [ -n "$TEMP_GPG" -a -d "$TEMP_GPG" ]; then
rm -rf "$TEMP_GPG"
rm -rf "$TEMP_GPG"
fi
# see https://dev.gnupg.org/T3464
@ -647,31 +647,31 @@ int main () {
}
EOF
if ! TEMP_GPG=$(mktemp -d "${TMPDIR:-/tmp}/notmuch.XXXXXX"); then
printf 'No.\nCould not make tempdir for testing signature verification when decrypting with session keys.\n'
errors=$((errors + 1))
printf 'No.\nCould not make tempdir for testing signature verification when decrypting with session keys.\n'
errors=$((errors + 1))
elif ${CC} ${CFLAGS} ${gmime_cflags} _verify_sig_with_session_key.c ${gmime_ldflags} -o _verify_sig_with_session_key \
&& GNUPGHOME=${TEMP_GPG} gpg --batch --quiet --import < "$srcdir"/test/gnupg-secret-key.asc \
&& rm -f ${TEMP_GPG}/private-keys-v1.d/*.key
&& GNUPGHOME=${TEMP_GPG} gpg --batch --quiet --import < "$srcdir"/test/gnupg-secret-key.asc \
&& rm -f ${TEMP_GPG}/private-keys-v1.d/*.key
then
if GNUPGHOME=${TEMP_GPG} ./_verify_sig_with_session_key; then
gmime_verify_with_session_key=1
printf "Yes.\n"
else
gmime_verify_with_session_key=0
printf "No.\n"
cat <<EOF
if GNUPGHOME=${TEMP_GPG} ./_verify_sig_with_session_key; then
gmime_verify_with_session_key=1
printf "Yes.\n"
else
gmime_verify_with_session_key=0
printf "No.\n"
cat <<EOF
*** Error: GMime fails to verify signatures when decrypting with a session key.
This is most likely due to a buggy version of GPGME, which should be fixed in 1.13.2 or later.
See https://dev.gnupg.org/T3464 for more details.
EOF
fi
fi
else
printf 'No.\nFailed to set up gpg for testing signature verification while decrypting with a session key.\n'
errors=$((errors + 1))
printf 'No.\nFailed to set up gpg for testing signature verification while decrypting with a session key.\n'
errors=$((errors + 1))
fi
if [ -n "$TEMP_GPG" -a -d "$TEMP_GPG" ]; then
rm -rf "$TEMP_GPG"
rm -rf "$TEMP_GPG"
fi
else
have_gmime=0
@ -768,24 +768,40 @@ have_python3=0
if [ $have_python -eq 1 ]; then
printf "Checking for python3 (>= 3.5)..."
if "$python" -c 'import sys, sysconfig; assert sys.version_info >= (3,5)'; >/dev/null 2>&1; then
printf "Yes.\n"
have_python3=1
printf "Yes.\n"
have_python3=1
else
printf "No (will not install CFFI-based python bindings).\n"
printf "No (will not install CFFI-based python bindings).\n"
fi
fi
have_python3_dev=0
if [ $have_python3 -eq 1 ]; then
printf "Checking for python3 version ..."
python3_version=$("$python" -c 'import sysconfig; print(sysconfig.get_python_version());')
printf "(%s)\n" $python3_version
printf "Checking for python $python3_version development files..."
if pkg-config --exists "python-$python3_version"; then
have_python3_dev=1
printf "Yes.\n"
else
have_python3_dev=0
printf "No (will not install CFFI-based python bindings).\n"
fi
fi
have_python3_cffi=0
have_python3_pytest=0
if [ $have_python3 -eq 1 ]; then
if [ $have_python3_dev -eq 1 ]; then
printf "Checking for python3 cffi and setuptools... "
if "$python" -c 'import cffi,setuptools; cffi.FFI().verify()' >/dev/null 2>&1; then
printf "Yes.\n"
have_python3_cffi=1
WITH_PYTHON_DOCS=1
printf "Yes.\n"
have_python3_cffi=1
WITH_PYTHON_DOCS=1
else
WITH_PYTHON_DOCS=0
printf "No (will not install CFFI-based python bindings).\n"
WITH_PYTHON_DOCS=0
printf "No (will not install CFFI-based python bindings).\n"
fi
rm -rf __pycache__ # cffi.FFI().verify() uses this space
@ -793,10 +809,10 @@ if [ $have_python3 -eq 1 ]; then
conf=$(mktemp)
printf "[pytest]\nminversion=3.0\n" > $conf
if "$python" -m pytest -c $conf --version >/dev/null 2>&1; then
printf "Yes.\n"
have_python3_pytest=1
printf "Yes.\n"
have_python3_pytest=1
else
printf "No (will not test CFFI-based python bindings).\n"
printf "No (will not test CFFI-based python bindings).\n"
fi
rm -f $conf
fi
@ -820,6 +836,19 @@ else
WITH_BASH=0
fi
printf "Checking for sfsexp... "
if pkg-config --exists sfsexp; then
printf "Yes.\n"
have_sfsexp=1
sfsexp_cflags=$(pkg-config --cflags sfsexp)
sfsexp_ldflags=$(pkg-config --libs sfsexp)
else
printf "No (will not enable s-expression queries).\n"
have_sfsexp=0
sfsexp_cflags=
sfsexp_ldflags=
fi
if [ -z "${EMACSLISPDIR-}" ]; then
EMACSLISPDIR="\$(prefix)/share/emacs/site-lisp"
fi
@ -831,10 +860,10 @@ fi
if [ $WITH_EMACS = "1" ]; then
printf "Checking if emacs (>= 25) is available... "
if emacs --quick --batch --eval '(if (< emacs-major-version 25) (kill-emacs 1))' > /dev/null 2>&1; then
printf "Yes.\n"
printf "Yes.\n"
else
printf "No (disabling emacs related parts of build)\n"
WITH_EMACS=0
printf "No (disabling emacs related parts of build)\n"
WITH_EMACS=0
fi
fi
@ -1443,6 +1472,13 @@ HAVE_VALGRIND = ${have_valgrind}
# And if so, flags needed at compile time for valgrind macros
VALGRIND_CFLAGS = ${valgrind_cflags}
# Whether the sfsexp library is available
HAVE_SFSEXP = ${have_sfsexp}
# And if so, flags needed at compile/link time for sfsexp
SFSEXP_CFLAGS = ${sfsexp_cflags}
SFSEXP_LDFLAGS = ${sfsexp_ldflags}
# Support for emacs
WITH_EMACS = ${WITH_EMACS}
@ -1459,6 +1495,7 @@ WITH_ZSH = ${WITH_ZSH}
COMMON_CONFIGURE_CFLAGS = \\
\$(GMIME_CFLAGS) \$(TALLOC_CFLAGS) \$(ZLIB_CFLAGS) \\
-DHAVE_VALGRIND=\$(HAVE_VALGRIND) \$(VALGRIND_CFLAGS) \\
-DHAVE_SFSEXP=\$(HAVE_SFSEXP) \$(SFSEXP_CFLAGS) \\
-DHAVE_GETLINE=\$(HAVE_GETLINE) \\
-DWITH_EMACS=\$(WITH_EMACS) \\
-DHAVE_CANONICALIZE_FILE_NAME=\$(HAVE_CANONICALIZE_FILE_NAME) \\
@ -1475,7 +1512,7 @@ CONFIGURE_CFLAGS = \$(COMMON_CONFIGURE_CFLAGS)
CONFIGURE_CXXFLAGS = \$(COMMON_CONFIGURE_CFLAGS) \$(XAPIAN_CXXFLAGS)
CONFIGURE_LDFLAGS = \$(GMIME_LDFLAGS) \$(TALLOC_LDFLAGS) \$(ZLIB_LDFLAGS) \$(XAPIAN_LDFLAGS)
CONFIGURE_LDFLAGS = \$(GMIME_LDFLAGS) \$(TALLOC_LDFLAGS) \$(ZLIB_LDFLAGS) \$(XAPIAN_LDFLAGS) \$(SFSEXP_LDFLAGS)
EOF
# construct the sh.config
@ -1524,6 +1561,9 @@ NOTMUCH_HAVE_PYTHON3_CFFI=${have_python3_cffi}
# Is the python pytest package available?
NOTMUCH_HAVE_PYTHON3_PYTEST=${have_python3_pytest}
# Is the sfsexp library available?
NOTMUCH_HAVE_SFSEXP=${have_sfsexp}
# Platform we are run on
PLATFORM=${platform}
EOF
@ -1531,10 +1571,10 @@ EOF
{
echo "# Generated by configure, run from doc/conf.py"
if [ $WITH_EMACS = "1" ]; then
echo "tags.add('WITH_EMACS')"
echo "tags.add('WITH_EMACS')"
fi
if [ $WITH_PYTHON_DOCS = "1" ]; then
echo "tags.add('WITH_PYTHON')"
echo "tags.add('WITH_PYTHON')"
fi
printf "rsti_dir = '%s'\n" "$(cd emacs && pwd -P)"
} > sphinx.config

41
debian/changelog vendored
View file

@ -1,3 +1,44 @@
notmuch (0.34.2-1) unstable; urgency=medium
* New upstream bugfix with release, with fixes database location in
library and notmuch2 python module.
* Build only against the default version of python, to avoid including
multiple .abi3.so files in python3-notmuch2
-- David Bremner <bremner@debian.org> Fri, 10 Dec 2021 09:35:43 -0400
notmuch (0.34.1-1) unstable; urgency=medium
* New upstream bugfix release. Fixes a memory deallocation error in
libnotmuch.
-- David Bremner <bremner@debian.org> Wed, 03 Nov 2021 10:20:33 -0300
notmuch (0.34-1) unstable; urgency=medium
* New upstream release
* Adds s-expression based query parser (man notmuch-sexp-queries).
* Bug fix: "notmuch breaks on directory removal", thanks to Joerg
Jaspert (Closes: #922536).
* Respect notmuch-show-text/html-blocked-images for renderer w3m
(Closes: #934082).
* Bug fix: "add an option to change the database path", thanks to
Michael Gold (Closes: #887041) (actually fixed in 0.32)
-- David Bremner <bremner@debian.org> Wed, 20 Oct 2021 11:15:23 -0300
notmuch (0.34~rc0-1) experimental; urgency=medium
* New upstream release candidate
-- David Bremner <bremner@debian.org> Fri, 15 Oct 2021 08:50:57 -0300
notmuch (0.33.2-1) unstable; urgency=medium
* Upstream fix for flaky/hanging tests in T355-smime
-- David Bremner <bremner@debian.org> Thu, 30 Sep 2021 08:27:10 -0300
notmuch (0.33.1-1~bpo11+1) bullseye-backports; urgency=medium
* Rebuild for bullseye-backports.

5
debian/control vendored
View file

@ -23,12 +23,13 @@ Build-Depends:
gnupg <!nocheck>,
gpgsm <!nocheck>,
libgmime-3.0-dev (>= 3.0.3~),
libpython3-all-dev,
libpython3-dev,
libsexp-dev,
libtalloc-dev,
libxapian-dev,
libz-dev,
pkg-config,
python3-all (>= 3.1.2-7~),
python3,
python3-cffi,
python3-pytest,
python3-pytest-cov,

View file

@ -114,6 +114,7 @@ libnotmuch.so.5 libnotmuch5 #MINVER#
notmuch_query_count_threads@Base 0.10~rc1
notmuch_query_count_threads_st@Base 0.21~rc1
notmuch_query_create@Base 0.3
notmuch_query_create_with_syntax@Base 0.34~rc0
notmuch_query_destroy@Base 0.3
notmuch_query_get_database@Base 0.21~rc1
notmuch_query_get_query_string@Base 0.4

View file

@ -17,3 +17,4 @@ usr/share/man/man1/notmuch.1.gz
usr/share/man/man5/notmuch-hooks.5.gz
usr/share/man/man7/notmuch-properties.7.gz
usr/share/man/man7/notmuch-search-terms.7.gz
usr/share/man/man7/notmuch-sexp-queries.7.gz

31
devel/check-notmuch-commit Executable file
View file

@ -0,0 +1,31 @@
#!/bin/sh
# Usage suggestion:
# git rebase -i --exec devel/check-notmuch-commit origin/master
set -e
quick=0
case "$1" in
-q|-Q|--quick)
quick=1
;;
esac
if [ $quick = 0 ]; then
make test
fi
unset uconf
for file in $(git diff --name-only --diff-filter=AM HEAD^); do
case $file in
*.c|*.h|*.cc|*.hh)
uncrustify --replace -c "${uconf=$(dirname "$0")/uncrustify.cfg}" "$file"
;;
*.el)
emacs -Q --batch "$file" --eval '(indent-region (point-min) (point-max) nil)' -f save-buffer
;;
esac
done
git diff --quiet

View file

@ -4,7 +4,7 @@ dir := doc
# You can set these variables from the command line.
SPHINXOPTS := -q
SPHINXBUILD = sphinx-build
SPHINXBUILD = env LD_LIBRARY_PATH=${NOTMUCH_BUILDDIR}/lib sphinx-build
DOCBUILDDIR := $(dir)/_build
# Internal variables.
@ -35,7 +35,7 @@ endif
INFO_INFO_FILES := $(INFO_TEXI_FILES:.texi=.info)
.PHONY: sphinx-html sphinx-texinfo sphinx-info
.PHONY: sphinx-html sphinx-texinfo sphinx-info doc-prereqs
.PHONY: install-man build-man apidocs install-apidocs
@ -46,15 +46,19 @@ ifeq ($(WITH_EMACS),1)
$(DOCBUILDDIR)/.roff.stamp $(DOCBUILDDIR)/.html.stamp $(DOCBUILDDIR)/.texi.stamp : docstring.stamp
endif
ifeq ($(HAVE_PYTHON3_CFFI),1)
doc-prereqs: python-cffi-bindings
endif
sphinx-html: $(DOCBUILDDIR)/.html.stamp
$(DOCBUILDDIR)/.html.stamp: $(ALL_RST_FILES)
$(DOCBUILDDIR)/.html.stamp: $(ALL_RST_FILES) doc-prereqs
$(SPHINXBUILD) -b html -d $(DOCBUILDDIR)/html_doctrees $(ALLSPHINXOPTS) $(DOCBUILDDIR)/html
touch $@
sphinx-texinfo: $(DOCBUILDDIR)/.texi.stamp
$(DOCBUILDDIR)/.texi.stamp: $(ALL_RST_FILES)
$(DOCBUILDDIR)/.texi.stamp: $(ALL_RST_FILES) doc-prereqs
$(SPHINXBUILD) -b texinfo -d $(DOCBUILDDIR)/texinfo_doctrees $(ALLSPHINXOPTS) $(DOCBUILDDIR)/texinfo
touch $@

View file

@ -25,7 +25,7 @@ for pathdir in ['.', '..']:
version=infile.read().replace('\n','')
# for autodoc
sys.path.insert(0, os.path.join(location, '..', 'bindings', 'python-cffi', 'notmuch2'))
sys.path.insert(0, os.path.join(location, '..', 'bindings', 'python-cffi', 'build', 'stage'))
# read generated config
for pathdir in ['.', '..']:
@ -159,6 +159,10 @@ man_pages = [
u'syntax for notmuch queries',
[notmuch_authors], 7),
('man7/notmuch-sexp-queries', 'notmuch-sexp-queries',
u's-expression syntax for notmuch queries',
[notmuch_authors], 7),
('man1/notmuch-show', 'notmuch-show',
u'show messages matching the given search terms',
[notmuch_authors], 1),

View file

@ -24,6 +24,7 @@ Contents:
man1/notmuch-restore
man1/notmuch-search
man7/notmuch-search-terms
man7/notmuch-sexp-queries
man1/notmuch-show
man1/notmuch-tag
python-bindings

View file

@ -251,9 +251,16 @@ paths are presumed relative to `$HOME` for items in section
:any:`notmuch-search-terms(7)` for more information about named
queries.
**squery.<name>**
Expansion for named query called <name>, using s-expression syntax. See
:any:`notmuch-sexp-queries(7)` for more information about s-expression
queries.
FILES
=====
.. _config_search:
CONFIGURATION
-------------

View file

@ -0,0 +1,315 @@
.. _notmuch-sexp-queries(7):
====================
notmuch-sexp-queries
====================
SYNOPSIS
========
**notmuch** *subcommand* ``--query=sexp`` [option ...] ``--`` '(and (to santa) (date december))'
DESCRIPTION
===========
Notmuch supports an alternative query syntax based on `S-expressions
<https://en.wikipedia.org/wiki/S-expression>`_ . It can be selected
with the command line ``--query=sexp`` or with the appropriate option
to the library function :c:func:`notmuch_query_create_with_syntax`.
Support for this syntax is currently optional, you can test if your
build of notmuch supports it with
::
$ notmuch config get built_with.sexpr_query
S-EXPRESSIONS
-------------
An *s-expression* is either an atom, or list of whitespace delimited
s-expressions inside parentheses. Atoms are either
*basic value*
A basic value is an unquoted string containing no whitespace, double quotes, or
parentheses.
*quoted string*
Double quotes (") delimit strings possibly containing whitespace
or parentheses. These can contain double quote characters by
escaping with backslash. E.g. ``"this is a quote \""``.
S-EXPRESSION QUERIES
--------------------
An s-expression query is either an atom, the empty list, or a
*compound query* consisting of a prefix atom (first element) defining
a *field*, *logical operation*, or *modifier*, and 0 or more
subqueries.
``*``
"*" matches any non-empty string in the current field.
``()``
The empty list matches all messages
*term*
Match all messages containing *term*, possibly after stemming or
phrase splitting. For discussion of stemming in notmuch see
:any:`notmuch-search-terms(7)`. Stemming only applies to unquoted
terms (basic values) in s-expression queries. For information on
phrase splitting see :any:`fields`.
``(`` *field* |q1| |q2| ... |qn| ``)``
Restrict the queries |q1| to |qn| to *field*, and combine with *and*
(for most fields) or *or*. See :any:`fields` for more information.
``(`` *operator* |q1| |q2| ... |qn| ``)``
Combine queries |q1| to |qn|. Currently supported operators are
``and``, ``or``, and ``not``. ``(not`` |q1| ... |qn| ``)`` is equivalent
to ``(and (not`` |q1| ``) ... (not`` |qn| ``))``.
``(`` *modifier* |q1| |q2| ... |qn| ``)``
Combine queries |q1| to |qn|, and reinterpret the result (e.g. as a regular expression).
See :any:`modifiers` for more information.
``(macro (`` |p1| ... |pn| ``) body)``
Define saved query with parameter substitution. The syntax is
recognized only in saved s-expression queries (see ``squery.*`` in
:any:`notmuch-config(1)`). Parameter names in ``body`` must be
prefixed with ``,`` to be expanded (see :any:`macro_examples`).
Macros may refer to other macros, but only to their own
parameters [#macro-details]_.
.. _fields:
FIELDS
``````
*Fields* [#aka-pref]_
correspond to attributes of mail messages. Some are inherent (and
immutable) like ``subject``, while others ``tag`` and ``property`` are
settable by the user. Each concrete field in
:any:`the table below <field-table>`
is discussed further under "Search prefixes" in
:any:`notmuch-search-terms(7)`. The row *user* refers to user defined
fields, described in :any:`notmuch-config(1)`.
Most fields are either *phrase fields* [#aka-prob]_ (which match
sequences of words), or *term fields* [#aka-bool]_ (which match exact
strings). *Phrase splitting* breaks the term (basic value or quoted
string) into words, ignore punctuation. Phrase splitting is applied to
terms in phrase (probabilistic) fields. Both phrase splitting and
stemming apply only in phrase fields.
Each term or phrase field has an associated combining operator
(``and`` or ``or``) used to combine the queries from each element of
the tail of the list. This is generally ``or`` for those fields where
a message has one such attribute, and ``and`` otherwise.
Term or phrase fields can contain arbitrarily complex queries made up
from terms, operators, and modifiers, but not other fields.
.. _field-table:
.. table:: Fields with supported modifiers
+------------+-----------+-----------+-----------+-----------+----------+
| field | combine | type | expand | wildcard | regex |
+============+===========+===========+===========+===========+==========+
| *none* | and | | no | yes | no |
+------------+-----------+-----------+-----------+-----------+----------+
| *user* | and | phrase | no | yes | no |
+------------+-----------+-----------+-----------+-----------+----------+
| attachment | and | phrase | yes | yes | no |
+------------+-----------+-----------+-----------+-----------+----------+
| body | and | phrase | no | no | no |
+------------+-----------+-----------+-----------+-----------+----------+
| date | | range | no | no | no |
+------------+-----------+-----------+-----------+-----------+----------+
| folder | or | phrase | yes | yes | yes |
+------------+-----------+-----------+-----------+-----------+----------+
| from | and | phrase | yes | yes | yes |
+------------+-----------+-----------+-----------+-----------+----------+
| id | or | term | no | yes | yes |
+------------+-----------+-----------+-----------+-----------+----------+
| is | and | term | yes | yes | yes |
+------------+-----------+-----------+-----------+-----------+----------+
| lastmod | | range | no | no | no |
+------------+-----------+-----------+-----------+-----------+----------+
| mid | or | term | no | yes | yes |
+------------+-----------+-----------+-----------+-----------+----------+
| mimetype | or | phrase | yes | yes | no |
+------------+-----------+-----------+-----------+-----------+----------+
| path | or | term | no | yes | yes |
+------------+-----------+-----------+-----------+-----------+----------+
| property | and | term | yes | yes | yes |
+------------+-----------+-----------+-----------+-----------+----------+
| subject | and | phrase | yes | yes | yes |
+------------+-----------+-----------+-----------+-----------+----------+
| tag | and | term | yes | yes | yes |
+------------+-----------+-----------+-----------+-----------+----------+
| thread | or | term | yes | yes | yes |
+------------+-----------+-----------+-----------+-----------+----------+
| to | and | phrase | yes | yes | no |
+------------+-----------+-----------+-----------+-----------+----------+
.. _modifiers:
MODIFIERS
`````````
*Modifiers* refer to any prefixes (first elements of compound queries)
that are neither operators nor fields.
``(infix`` *atom* ``)``
Interpret *atom* as an infix notmuch query (see
:any:`notmuch-search-terms(7)`). Not supported inside fields.
``(matching`` |q1| |q2| ... |qn| ``)`` ``(of`` |q1| |q2| ... |qn| ``)``
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
phrase fields. Most commonly used in the ``thread`` field.
``(query`` *atom* ``)``
Expand to the saved query named by *atom*. See
:any:`notmuch-config(1)` for more. Note that the saved query must
be in infix syntax (:any:`notmuch-search-terms(7)`). Not supported
inside fields.
``(regex`` *atom* ``)`` ``(rx`` *atom* ``)``
Interpret *atom* as a POSIX.2 regular expression (see
:manpage:`regex(7)`). This applies in term fields and a subset [#not-phrase]_ of
phrase fields (see :any:`field-table`).
``(starts-with`` *subword* ``)``
Matches any term starting with *subword*. This applies in either
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
atom ``*`` is a synonym for ``(starts-with "")``.
EXAMPLES
========
``Wizard``
Match all messages containing the word "wizard", ignoring case.
``added``
Match all messages containing "added", but also those containing "add", "additional",
"Additional", "adds", etc... via stemming.
``(and Bob Marley)``
Match messages containing words "Bob" and "Marley", or their stems
The words need not be adjacent.
``(not Bob Marley)``
Match messages containing neither "Bob" nor "Marley", nor their stems,
``"quick fox"`` ``quick-fox`` ``quick@fox``
Match the *phrase* "quick" followed by "fox" in phrase fields (or
outside a field). Match the literal string in a term field.
``(folder (of (id 1234@invalid)))``
Match any message in the same folder as the one with Message-Id "1234@invalid"
``(id 1234@invalid blah@test)``
Matches Message-Id "1234@invalid" *or* Message-Id "blah@test"
``(and (infix "date:2009-11-18..2009-11-18") (tag unread))``
Match messages in the given date range with tag unread.
``(starts-with prelim)``
Match any words starting with "prelim".
``(subject quick "brown fox")``
Match messages whose subject contains "quick" (anywhere, stemmed) and
the phrase "brown fox".
``(subject (starts-with prelim))``
Matches any word starting with "prelim", inside a message subject.
``(subject (starts-wih quick) "brown fox")``
Match messages whose subject contains "quick brown fox", but also
"brown fox quicksand".
``(thread (of (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)))``
Match any (messages in) a thread containing a message from
"bob@example.com" and a (possibly distinct) message to "bob at
example.com")
``(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",
"mallory@example.org", and also "bob@example.com.au" since it
contains the adjacent triple "bob", "example", "com".
``(not (to *))``
Match messages with an empty or invalid 'To' and 'Cc' field.
``(List *)``
Match messages with a non-empty List-Id header, assuming
configuration ``index.header.List=List-Id``
.. _macro_examples:
MACRO EXAMPLES
--------------
A macro that takes two parameters and applies different fields to them.
::
$ notmuch config set squery.TagSubject '(macro (tagname subj) (and (tag ,tagname) (subject ,subj)))'
$ notmuch search --query=sexp '(TagSubject inbox maildir)'
Nested macros are allowed.
::
$ notmuch config set squery.Inner '(macro (x) (subject ,x))'
$ notmuch config set squery.Outer '(macro (x y) (and (tag ,x) (Inner ,y)))'
$ notmuch search --query=sexp '(Outer inbox maildir)'
Parameters can be re-used to reduce boilerplate. Any field, including
user defined fields is permitted within a macro.
::
$ notmuch config set squery.About '(macro (name) (or (subject ,name) (List ,name)))'
$ notmuch search --query=sexp '(About notmuch)'
NOTES
=====
.. [#macro-details] Technically macros impliment lazy evaluation and
lexical scope. There is one top level scope
containing all macro definitions, but all
parameter definitions are local to a given macro.
.. [#aka-pref] a.k.a. prefixes
.. [#aka-prob] a.k.a. probabilistic prefixes
.. [#aka-bool] a.k.a. boolean prefixes
.. [#not-phrase] Due to the implemention of phrase fields in Xapian,
regex queries could only match individual words.
.. [#not-body] Due the the way ``body`` is implemented in notmuch,
this modifier is not supported in the ``body`` field.
.. [#not-path] Due to the way recursive ``path`` queries are implemented
in notmuch, this modifier is not supported in the
``path`` field.
.. |q1| replace:: :math:`q_1`
.. |q2| replace:: :math:`q_2`
.. |qn| replace:: :math:`q_n`
.. |p1| replace:: :math:`p_1`
.. |p2| replace:: :math:`p_2`
.. |pn| replace:: :math:`p_n`

View file

@ -217,7 +217,7 @@ requiring external commands."
;; harvest if necessary.
(notmuch-address-harvest-trigger)))
(t
(process-lines notmuch-address-command original))))
(notmuch--process-lines notmuch-address-command original))))
(defun notmuch-address-expand-name ()
(cond

View file

@ -164,7 +164,7 @@ mode."
(goto-char (point-max))
(insert (format "-- Key %s in message %s:\n"
fingerprint id))
(call-process notmuch-crypto-gpg-program nil t t
(notmuch--call-process notmuch-crypto-gpg-program nil t t
"--batch" "--no-tty" "--list-keys" fingerprint))
(recenter -1))))
@ -224,7 +224,7 @@ corresponding key when the status button is pressed."
(with-current-buffer buffer
(goto-char (point-max))
(insert (format "--- Retrieving key %s:\n" keyid)))
(let ((p (make-process
(let ((p (notmuch--make-process
:name "notmuch GPG key retrieval"
:connection-type 'pipe
:buffer buffer
@ -240,9 +240,9 @@ corresponding key when the status button is pressed."
(with-current-buffer buffer
(goto-char (point-max))
(insert (format "--- Retrieving key %s:\n" keyid))
(call-process notmuch-crypto-gpg-program nil t t "--recv-keys" keyid)
(notmuch--call-process notmuch-crypto-gpg-program nil t t "--recv-keys" keyid)
(insert "\n")
(call-process notmuch-crypto-gpg-program nil t t "--list-keys" keyid))
(notmuch--call-process notmuch-crypto-gpg-program nil t t "--list-keys" keyid))
(recenter -1))
(notmuch-show-refresh-view)))))

View file

@ -42,7 +42,7 @@
:group 'notmuch)
(defcustom notmuch-draft-tags '("+draft")
"List of tags changes to apply to a draft message when it is saved in the database.
"List of tag changes to apply when saving a draft message in the database.
Tags starting with \"+\" (or not starting with either \"+\" or
\"-\") in the list will be added, and tags starting with \"-\"
@ -239,7 +239,7 @@ applied to newly inserted messages)."
(defun notmuch-draft-resume (id)
"Resume editing of message with id ID."
;; Used by command `notmuch-show-resume-message'.
(let* ((tags (process-lines notmuch-command "search" "--output=tags"
(let* ((tags (notmuch--process-lines notmuch-command "search" "--output=tags"
"--exclude=false" id))
(draft (equal tags (notmuch-update-tags tags notmuch-draft-tags))))
(when (or draft
@ -249,7 +249,7 @@ applied to newly inserted messages)."
(setq buffer-read-only nil)
(erase-buffer)
(let ((coding-system-for-read 'no-conversion))
(call-process notmuch-command nil t nil "show" "--format=raw" id))
(notmuch--call-process notmuch-command nil t nil "show" "--format=raw" id))
(mime-to-mml)
(goto-char (point-min))
(when (re-search-forward "^$" nil t)

View file

@ -144,9 +144,11 @@ a plist. Supported properties are
Possible values are `oldest-first', `newest-first'
or nil. Nil means use the default sort order.
:search-type Specify whether to run the search in search-mode,
tree mode or unthreaded mode. Set to 'tree to specify tree
mode, 'unthreaded to specify unthreaded mode, and set to nil
(or anything except tree and unthreaded) to specify search mode.
tree mode or unthreaded mode. Set to `tree' to
specify tree mode, 'unthreaded to specify
unthreaded mode, and set to nil (or anything
except tree and unthreaded) to specify search
mode.
Other accepted forms are a cons cell of the form (NAME . QUERY)
or a list of the form (NAME QUERY COUNT-QUERY)."
@ -494,7 +496,7 @@ diagonal."
(widget-get widget :notmuch-search-oldest-first)))))
(defun notmuch-saved-search-count (search)
(car (process-lines notmuch-command "count" search)))
(car (notmuch--process-lines notmuch-command "count" search)))
(defun notmuch-hello-tags-per-line (widest)
"Determine how many tags to show per line and how wide they
@ -567,7 +569,7 @@ options will be handled as specified for
(or (plist-get options :filter-count)
(plist-get options :filter))))
"\n")))
(unless (= (call-process-region (point-min) (point-max) notmuch-command
(unless (= (notmuch--call-process-region (point-min) (point-max) notmuch-command
t t nil "count" "--batch") 0)
(notmuch-logged-error
"notmuch count --batch failed"
@ -746,7 +748,7 @@ Complete list of currently available key bindings:
(list (cons tag
(concat "tag:"
(notmuch-escape-boolean-term tag))))))
(process-lines notmuch-command "search" "--output=tags" "*")))
(notmuch--process-lines notmuch-command "search" "--output=tags" "*")))
(defun notmuch-hello-insert-header ()
"Insert the default notmuch-hello header."
@ -784,7 +786,7 @@ Complete list of currently available key bindings:
:help-echo "Refresh"
(notmuch-hello-nice-number
(string-to-number
(car (process-lines notmuch-command "count")))))
(car (notmuch--process-lines notmuch-command "count")))))
(widget-insert " messages.\n")))
(defun notmuch-hello-insert-saved-searches ()
@ -869,16 +871,16 @@ Supports the following entries in OPTIONS as a plist:
(start (point)))
(if is-hidden
(widget-create 'push-button
:notify `(lambda (widget &rest _ignore)
(setq notmuch-hello-hidden-sections
(delete ,title notmuch-hello-hidden-sections))
(notmuch-hello-update))
:notify (lambda (&rest _ignore)
(setq notmuch-hello-hidden-sections
(delete title notmuch-hello-hidden-sections))
(notmuch-hello-update))
"show")
(widget-create 'push-button
:notify `(lambda (widget &rest _ignore)
(add-to-list 'notmuch-hello-hidden-sections
,title)
(notmuch-hello-update))
:notify (lambda (&rest _ignore)
(add-to-list 'notmuch-hello-hidden-sections
title)
(notmuch-hello-update))
"hide"))
(widget-insert "\n")
(unless is-hidden

View file

@ -25,6 +25,10 @@
(require 'notmuch-lib)
(require 'notmuch-hello)
(declare-function notmuch-search "notmuch")
(declare-function notmuch-tree "notmuch-tree")
(declare-function notmuch-unthreaded "notmuch-tree")
;;;###autoload
(defun notmuch-jump-search ()
"Jump to a saved search by shortcut key.
@ -50,11 +54,11 @@ fast way to jump to a saved search from anywhere in Notmuch."
(push (list key name
(cond
((eq (plist-get saved-search :search-type) 'tree)
`(lambda () (notmuch-tree ',query)))
(lambda () (notmuch-tree query)))
((eq (plist-get saved-search :search-type) 'unthreaded)
`(lambda () (notmuch-unthreaded ',query)))
(lambda () (notmuch-unthreaded query)))
(t
`(lambda () (notmuch-search ',query ',oldest-first)))))
(lambda () (notmuch-search query oldest-first)))))
action-map)))))
(setq action-map (nreverse action-map))
(if action-map
@ -168,9 +172,10 @@ buffer."
(pcase-dolist (`(,key ,_name ,fn) action-map)
(when (= (length key) 1)
(define-key map key
`(lambda () (interactive)
(setq notmuch-jump--action ',fn)
(exit-minibuffer)))))
(lambda ()
(interactive)
(setq notmuch-jump--action fn)
(exit-minibuffer)))))
;; By doing this in two passes (and checking if we already have a
;; binding) we avoid problems if the user specifies a binding which
;; is a prefix of another binding.
@ -191,12 +196,13 @@ buffer."
action-submap)
(setq action-submap (nreverse action-submap))
(define-key map keystr
`(lambda () (interactive)
(setq notmuch-jump--action
',(apply-partially #'notmuch-jump
action-submap
new-prompt))
(exit-minibuffer)))))))
(lambda ()
(interactive)
(setq notmuch-jump--action
(apply-partially #'notmuch-jump
action-submap
new-prompt))
(exit-minibuffer)))))))
map))
(provide 'notmuch-jump)

View file

@ -195,7 +195,7 @@ will be signaled.
Otherwise the output will be returned."
(with-temp-buffer
(let ((status (apply #'call-process notmuch-command nil t nil args))
(let ((status (apply #'notmuch--call-process notmuch-command nil t nil args))
(output (buffer-string)))
(notmuch-check-exit-status status (cons notmuch-command args) output)
output)))
@ -206,7 +206,7 @@ Otherwise the output will be returned."
(defun notmuch-cli-sane-p ()
"Return t if the cli seems to be configured sanely."
(unless notmuch--cli-sane-p
(let ((status (call-process notmuch-command nil nil nil
(let ((status (notmuch--call-process notmuch-command nil nil nil
"config" "get" "user.primary_email")))
(setq notmuch--cli-sane-p (= status 0))))
notmuch--cli-sane-p)
@ -286,7 +286,7 @@ depending on the value of `notmuch-poll-script'."
(message "Polling mail...")
(if (stringp notmuch-poll-script)
(unless (string-empty-p notmuch-poll-script)
(unless (equal (call-process notmuch-poll-script nil nil) 0)
(unless (equal (notmuch--call-process notmuch-poll-script nil nil) 0)
(error "Notmuch: poll script `%s' failed!" notmuch-poll-script)))
(notmuch-call-notmuch-process "new"))
(message "Polling mail...done"))
@ -639,7 +639,7 @@ the given type."
;; charset is US-ASCII. RFC6657
;; complicates this somewhat.
'us-ascii)))))
(apply #'call-process
(apply #'notmuch--call-process
notmuch-command nil '(t nil) nil args)
(buffer-string))))))
(when (and cache data)
@ -860,6 +860,32 @@ You may need to restart Emacs or upgrade your notmuch package."))
;; `notmuch-logged-error' does not return.
))))
(defmacro notmuch--apply-with-env (func &rest args)
`(let ((default-directory "~"))
(apply ,func ,@args)))
(defun notmuch--process-lines (program &rest args)
"Wrap process-lines, binding DEFAULT-DIRECTORY to a safe
default"
(notmuch--apply-with-env #'process-lines program args))
(defun notmuch--make-process (&rest args)
"Wrap make-process, binding DEFAULT-DIRECTORY to a safe
default"
(notmuch--apply-with-env #'make-process args))
(defun notmuch--call-process-region (start end program
&optional delete buffer display
&rest args)
"Wrap call-process-region, binding DEFAULT-DIRECTORY to a safe
default"
(notmuch--apply-with-env
#'call-process-region start end program delete buffer display args))
(defun notmuch--call-process (program &optional infile destination display &rest args)
"Wrap call-process, binding DEFAULT-DIRECTORY to a safe default"
(notmuch--apply-with-env #'call-process program infile destination display args))
(defun notmuch-call-notmuch--helper (destination args)
"Helper for synchronous notmuch invocation commands.
@ -874,9 +900,9 @@ for `call-process'. ARGS is as described for
(otherwise
(error "Unknown keyword argument: %s" (car args)))))
(if (null stdin-string)
(apply #'call-process notmuch-command nil destination nil args)
(apply #'notmuch--call-process notmuch-command nil destination nil args)
(insert stdin-string)
(apply #'call-process-region (point-min) (point-max)
(apply #'notmuch--call-process-region (point-min) (point-max)
notmuch-command t destination nil args))))
(defun notmuch-call-notmuch-process (&rest args)
@ -933,7 +959,7 @@ status."
(let* ((command (or (executable-find notmuch-command)
(error "Command not found: %s" notmuch-command)))
(err-buffer (generate-new-buffer " *notmuch-stderr*"))
(proc (make-process
(proc (notmuch--make-process
:name name
:buffer buffer
:command (cons command args)

View file

@ -41,16 +41,17 @@ Three types of values are permitted:
- a string: the value of `notmuch-fcc-dirs' is the Fcc header to
be used.
- a list: the folder is chosen based on the From address of the
current message using a list of regular expressions and
corresponding folders:
- an alist: the folder is chosen based on the From address of
the current message according to an alist mapping regular
expressions to folders or nil:
((\"Sebastian@SSpaeth.de\" . \"privat\")
(\"spaetz@sspaeth.de\" . \"OUTBOX.OSS\")
(\".*\" . \"defaultinbox\"))
If none of the regular expressions match the From address, no
Fcc header will be added.
If none of the regular expressions match the From address, or
if the cdr of the matching entry is nil, then no Fcc header
will be added.
If `notmuch-maildir-use-notmuch-insert' is set (the default) then
the header should be of the form \"folder +tag1 -tag2\" where
@ -74,7 +75,8 @@ directory if it does not exist yet when sending a mail."
(const :tag "No FCC header" nil)
(string :tag "A single folder")
(repeat :tag "A folder based on the From header"
(cons regexp (string :tag "Folder"))))
(cons regexp (choice (const :tag "No FCC header" nil)
(string :tag "Folder")))))
:require 'notmuch-fcc-initialization
:group 'notmuch-send)
@ -105,13 +107,14 @@ Otherwise set it according to `notmuch-fcc-dirs'."
;; Old style - no longer works.
(error "Invalid `notmuch-fcc-dirs' setting (old style)"))
((listp notmuch-fcc-dirs)
(or (seq-some (let ((from (message-field-value "From")))
(pcase-lambda (`(,regexp . ,folder))
(and (string-match-p regexp from)
folder)))
notmuch-fcc-dirs)
(progn (message "No Fcc header added.")
nil)))
(if-let ((match (seq-some (let ((from (message-field-value "From")))
(pcase-lambda (`(,regexp . ,folder))
(and (string-match-p regexp from)
(cons t folder))))
notmuch-fcc-dirs)))
(cdr match)
(message "No Fcc header added.")
nil))
(t
(error "Invalid `notmuch-fcc-dirs' setting (neither string nor list)")))))
(when subdir

View file

@ -474,7 +474,7 @@ the From: address."
(with-current-buffer temp-buffer
(erase-buffer)
(let ((coding-system-for-read 'no-conversion))
(call-process notmuch-command nil t nil
(notmuch--call-process notmuch-command nil t nil
"show" "--format=raw" id))
;; Because we process the messages in reverse order,
;; always generate a forwarded subject, then use the

View file

@ -48,7 +48,7 @@
"Pass the contents of the current buffer to 'muttprint'.
Optional OUTPUT allows passing a list of flags to muttprint."
(apply #'call-process-region (point-min) (point-max)
(apply #'notmuch--call-process-region (point-min) (point-max)
;; Reads from stdin.
"muttprint"
nil nil nil

View file

@ -279,7 +279,7 @@ position of the message in the thread."
(let ((buf (generate-new-buffer (concat "*notmuch-msg-" id "*"))))
(with-current-buffer buf
(let ((coding-system-for-read 'no-conversion))
(call-process notmuch-command nil t nil "show" "--format=raw" id))
(notmuch--call-process notmuch-command nil t nil "show" "--format=raw" id))
,@body)
(kill-buffer buf)))))
@ -719,21 +719,23 @@ will return nil if the CID is unknown or cannot be retrieved."
t)
(defun notmuch-show-insert-part-message/rfc822 (msg part _content-type _nth depth _button)
(let* ((message (car (plist-get part :content)))
(body (car (plist-get message :body)))
(start (point)))
;; Override `notmuch-message-headers' to force `From' to be
;; displayed.
(let ((notmuch-message-headers '("From" "Subject" "To" "Cc" "Date")))
(notmuch-show-insert-headers (plist-get message :headers)))
;; Blank line after headers to be compatible with the normal
;; message display.
(insert "\n")
;; Show the body
(notmuch-show-insert-bodypart msg body depth)
(when notmuch-show-indent-multipart
(indent-rigidly start (point) 1)))
t)
(let ((message (car (plist-get part :content))))
(and
message
(let ((body (car (plist-get message :body)))
(start (point)))
;; Override `notmuch-message-headers' to force `From' to be
;; displayed.
(let ((notmuch-message-headers '("From" "Subject" "To" "Cc" "Date")))
(notmuch-show-insert-headers (plist-get message :headers)))
;; Blank line after headers to be compatible with the normal
;; message display.
(insert "\n")
;; Show the body
(notmuch-show-insert-bodypart msg body depth)
(when notmuch-show-indent-multipart
(indent-rigidly start (point) 1))
t))))
(defun notmuch-show-insert-part-text/plain (msg part _content-type _nth depth button)
;; For backward compatibility we want to apply the text/plain hook
@ -2032,7 +2034,7 @@ to show, nil otherwise."
(pop-to-buffer-same-window buf)
(erase-buffer)
(let ((coding-system-for-read 'no-conversion))
(call-process notmuch-command nil t nil "show" "--format=raw" id))
(notmuch--call-process notmuch-command nil t nil "show" "--format=raw" id))
(goto-char (point-min))
(set-buffer-modified-p nil)
(setq buffer-read-only t)
@ -2078,19 +2080,19 @@ message."
(let ((cwd default-directory)
(buf (get-buffer-create (concat "*notmuch-pipe*"))))
(with-current-buffer buf
(setq buffer-read-only nil)
(erase-buffer)
;; Use the originating buffer's working directory instead of
;; that of the pipe buffer.
(cd cwd)
(let ((exit-code (call-process-shell-command shell-command nil buf)))
(goto-char (point-max))
(set-buffer-modified-p nil)
(setq buffer-read-only t)
(unless (zerop exit-code)
(pop-to-buffer buf)
(message (format "Command '%s' exited abnormally with code %d"
shell-command exit-code))))))))
(setq buffer-read-only t)
(let ((inhibit-read-only t))
(erase-buffer)
;; Use the originating buffer's working directory instead of
;; that of the pipe buffer.
(cd cwd)
(let ((exit-code (call-process-shell-command shell-command nil buf)))
(goto-char (point-max))
(set-buffer-modified-p nil)
(unless (zerop exit-code)
(pop-to-buffer buf)
(message (format "Command '%s' exited abnormally with code %d"
shell-command exit-code)))))))))
(defun notmuch-show-tag-message (&rest tag-changes)
"Change tags for the current message.

View file

@ -397,7 +397,7 @@ Return all tags if no search terms are given."
(split-string
(with-output-to-string
(with-current-buffer standard-output
(apply 'call-process notmuch-command nil t
(apply 'notmuch--call-process notmuch-command nil t
nil "search" "--output=tags" "--exclude=false" search-terms)))
"\n+" t))
@ -553,7 +553,7 @@ and vice versa."
name)
(mapconcat #'identity tag-change " "))))
(push (list key name-string
`(lambda () (,tag-function ',tag-change)))
(lambda () (funcall tag-function tag-change)))
action-map)))
(push (list notmuch-tag-jump-reverse-key
(if reverse

View file

@ -74,24 +74,55 @@
notmuch-unthreaded-show-out
notmuch-tree-show-out))
(defcustom notmuch-tree-thread-symbols
'((prefix . " ")
(top . "")
(top-tee . "")
(vertical . "")
(vertical-tee . "")
(bottom . "")
(arrow . ""))
"Strings used to draw trees in notmuch tree results.
Symbol keys denote where the corresponding string value is used:
`prefix' is used at the top of the tree, followed by `top' if it
has no children or `top-tee' if it does; `vertical' is a bar
connecting with a response down the list skipping the current
one, while `vertical-tee' marks the current message as a reply to
the previous one; `bottom' is used at the bottom of threads.
Finally, the `arrrow' string in the list is used as a pointer to
every message.
Common customizations include setting `prefix' to \"-\", to see
equal-length prefixes, and `arrow' to an empty string or to a
different kind of arrow point."
:type '(alist :key-type symbol :value-type string)
:group 'notmuch-tree)
(defcustom notmuch-tree-result-format
`(("date" . "%12s ")
("authors" . "%-20s")
((("tree" . "%s")("subject" . "%s")) ." %-54s ")
((("tree" . "%s")
("subject" . "%s"))
. " %-54s ")
("tags" . "(%s)"))
"Result formatting for tree view. Supported fields are: date,
authors, subject, tree, tags. Tree means the thread tree
box graphics. The field may also be a list in which case
the formatting rules are applied recursively and then the
output of all the fields in the list is inserted
according to format-string.
"Result formatting for tree view.
Note the author string should not contain
whitespace (put it in the neighbouring fields instead).
For example:
(setq notmuch-tree-result-format \(\(\"authors\" . \"%-40s\"\)
\(\"subject\" . \"%s\"\)\)\)"
:type '(alist :key-type (string) :value-type (string))
Supported fields are: date, authors, subject, tree, tags.
Tree means the thread tree box graphics. The field may
also be a list in which case the formatting rules are
applied recursively and then the output of all the fields
in the list is inserted according to format-string.
Note that the author string should not contain whitespace
\(put it in the neighbouring fields instead). For example:
(setq notmuch-tree-result-format
'((\"authors\" . \"%-40s\")
(\"subject\" . \"%s\")))"
:type '(alist :key-type (choice string
(alist :key-type string
:value-type string))
:value-type string)
:group 'notmuch-tree)
(defcustom notmuch-unthreaded-result-format
@ -99,19 +130,24 @@ For example:
("authors" . "%-20s")
((("subject" . "%s")) ." %-54s ")
("tags" . "(%s)"))
"Result formatting for unthreaded tree view. Supported fields are: date,
authors, subject, tree, tags. Tree means the thread tree
box graphics. The field may also be a list in which case
the formatting rules are applied recursively and then the
output of all the fields in the list is inserted
according to format-string.
"Result formatting for unthreaded tree view.
Note the author string should not contain
whitespace (put it in the neighbouring fields instead).
For example:
(setq notmuch-tree-result-format \(\(\"authors\" . \"%-40s\"\)
\(\"subject\" . \"%s\"\)\)\)"
:type '(alist :key-type (string) :value-type (string))
Supported fields are: date, authors, subject, tree, tags.
Tree means the thread tree box graphics. The field may
also be a list in which case the formatting rules are
applied recursively and then the output of all the fields
in the list is inserted according to format-string.
Note that the author string should not contain whitespace
\(put it in the neighbouring fields instead). For example:
(setq notmuch-unthreaded-result-format
'((\"authors\" . \"%-40s\")
(\"subject\" . \"%s\")))"
:type '(alist :key-type (choice string
(alist :key-type string
:value-type string))
:value-type string)
:group 'notmuch-tree)
(defun notmuch-tree-result-format ()
@ -873,6 +909,9 @@ unchanged ADDRESS if parsing fails."
((listp field)
(format format-string (notmuch-tree-format-field-list field msg)))
((functionp field)
(funcall field format-string msg))
((string-equal field "date")
(let ((face (if match
'notmuch-tree-match-date-face
@ -968,20 +1007,20 @@ message together with all its descendents."
(replies (cadr tree)))
(cond
((and (< 0 depth) (not last))
(push "" tree-status))
(push (alist-get 'vertical-tee notmuch-tree-thread-symbols) tree-status))
((and (< 0 depth) last)
(push "" tree-status))
(push (alist-get 'bottom notmuch-tree-thread-symbols) tree-status))
((and (eq 0 depth) first last)
;; Choice between these two variants is a matter of taste.
;; (push "─" tree-status))
(push " " tree-status))
(push (alist-get 'prefix notmuch-tree-thread-symbols) tree-status))
((and (eq 0 depth) first (not last))
(push "" tree-status))
(push (alist-get 'top-tee notmuch-tree-thread-symbols) tree-status))
((and (eq 0 depth) (not first) last)
(push "" tree-status))
(push (alist-get 'bottom notmuch-tree-thread-symbols) tree-status))
((and (eq 0 depth) (not first) (not last))
(push "" tree-status)))
(push (concat (if replies "" "") "") tree-status)
(push (alist-get 'vertical-tee notmuch-tree-thread-symbols) tree-status)))
(push (concat (alist-get (if replies 'top-tee 'top) notmuch-tree-thread-symbols)
(alist-get 'arrow notmuch-tree-thread-symbols))
tree-status)
(setq msg (plist-put msg :first (and first (eq 0 depth))))
(setq msg (plist-put msg :tree-status tree-status))
(setq msg (plist-put msg :orig-tags (plist-get msg :tags)))
@ -990,7 +1029,7 @@ message together with all its descendents."
(pop tree-status)
(if last
(push " " tree-status)
(push "" tree-status))
(push (alist-get 'vertical notmuch-tree-thread-symbols) tree-status))
(notmuch-tree-insert-thread replies (1+ depth) tree-status)))
(defun notmuch-tree-insert-thread (thread depth tree-status)
@ -1098,7 +1137,7 @@ the same as for the function notmuch-tree."
(concat " and (" query-context ")"))))
(sort-arg (if oldest-first "--sort=oldest-first" "--sort=newest-first"))
(message-arg (if unthreaded "--unthreaded" "--entire-thread")))
(when (equal (car (process-lines notmuch-command "count" search-args)) "0")
(when (equal (car (notmuch--process-lines notmuch-command "count" search-args)) "0")
(setq search-args basic-query))
(notmuch-tag-clear-cache)
(let ((proc (notmuch-start-notmuch

View file

@ -88,18 +88,21 @@
("authors" . "%-20s ")
("subject" . "%s ")
("tags" . "(%s)"))
"Search result formatting. Supported fields are:
date, count, authors, subject, tags
"Search result formatting.
Supported fields are: date, count, authors, subject, tags.
For example:
(setq notmuch-search-result-format \(\(\"authors\" . \"%-40s\"\)
\(\"subject\" . \"%s\"\)\)\)
(setq notmuch-search-result-format
'((\"authors\" . \"%-40s\")
(\"subject\" . \"%s\")))
Line breaks are permitted in format strings (though this is
currently experimental). Note that a line break at the end of an
\"authors\" field will get elided if the authors list is long;
place it instead at the beginning of the following field. To
enter a line break when setting this variable with setq, use \\n.
To enter a line break in customize, press \\[quoted-insert] C-j."
:type '(alist :key-type (string) :value-type (string))
:type '(alist :key-type string :value-type string)
:group 'notmuch-search)
;; The name of this variable `notmuch-init-file' is consistent with the
@ -830,26 +833,28 @@ non-authors is found, assume that all of the authors match."
(insert padding))))
(defun notmuch-search-insert-field (field format-string result)
(cond
((string-equal field "date")
(insert (propertize (format format-string (plist-get result :date_relative))
'face 'notmuch-search-date)))
((string-equal field "count")
(insert (propertize (format format-string
(format "[%s/%s]" (plist-get result :matched)
(plist-get result :total)))
'face 'notmuch-search-count)))
((string-equal field "subject")
(insert (propertize (format format-string
(notmuch-sanitize (plist-get result :subject)))
'face 'notmuch-search-subject)))
((string-equal field "authors")
(notmuch-search-insert-authors
format-string (notmuch-sanitize (plist-get result :authors))))
((string-equal field "tags")
(let ((tags (plist-get result :tags))
(orig-tags (plist-get result :orig-tags)))
(insert (format format-string (notmuch-tag-format-tags tags orig-tags)))))))
(pcase field
((pred functionp)
(insert (funcall field format-string result)))
("date"
(insert (propertize (format format-string (plist-get result :date_relative))
'face 'notmuch-search-date)))
("count"
(insert (propertize (format format-string
(format "[%s/%s]" (plist-get result :matched)
(plist-get result :total)))
'face 'notmuch-search-count)))
("subject"
(insert (propertize (format format-string
(notmuch-sanitize (plist-get result :subject)))
'face 'notmuch-search-subject)))
("authors"
(notmuch-search-insert-authors format-string
(notmuch-sanitize (plist-get result :authors))))
("tags"
(let ((tags (plist-get result :tags))
(orig-tags (plist-get result :orig-tags)))
(insert (format format-string (notmuch-tag-format-tags tags orig-tags)))))))
(defun notmuch-search-show-result (result pos)
"Insert RESULT at POS."
@ -935,7 +940,7 @@ See `notmuch-tag' for information on the format of TAG-CHANGES."
PROMPT is the string to prompt with."
(let* ((all-tags
(mapcar (lambda (tag) (notmuch-escape-boolean-term tag))
(process-lines notmuch-command "search" "--output=tags" "*")))
(notmuch--process-lines notmuch-command "search" "--output=tags" "*")))
(completions
(append (list "folder:" "path:" "thread:" "id:" "date:" "from:" "to:"
"subject:" "attachment:")

View file

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

View file

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

View file

@ -259,7 +259,15 @@ _notmuch_config_load_from_database (notmuch_database_t *notmuch)
for (; notmuch_config_list_valid (list); notmuch_config_list_move_to_next (list)) {
const char *key = notmuch_config_list_key (list);
char *normalized_val = _expand_path (list, key, notmuch_config_list_value (list));
char *normalized_val = NULL;
/* If we opened from a given path, do not overwrite it */
if (strcmp (key, "database.path") == 0 &&
(notmuch->params & NOTMUCH_PARAM_DATABASE) &&
notmuch->xapian_db)
continue;
normalized_val = _expand_path (list, key, notmuch_config_list_value (list));
_notmuch_string_map_append (notmuch->config, key, normalized_val);
talloc_free (normalized_val);
}
@ -432,6 +440,13 @@ _notmuch_config_load_from_file (notmuch_database_t *notmuch,
status = NOTMUCH_STATUS_FILE_ERROR;
goto DONE;
}
/* If we opened from a given path, do not overwrite it */
if (strcmp (absolute_key, "database.path") == 0 &&
(notmuch->params & NOTMUCH_PARAM_DATABASE) &&
notmuch->xapian_db)
continue;
normalized_val = _expand_path (notmuch, absolute_key, val);
_notmuch_string_map_set (notmuch->config, absolute_key, normalized_val);
g_free (val);

View file

@ -40,6 +40,10 @@
#include <xapian.h>
#if HAVE_SFSEXP
#include <sexp.h>
#endif
/* Bit masks for _notmuch_database::features. Features are named,
* independent aspects of the database schema.
*
@ -186,6 +190,39 @@ operator& (notmuch_field_flag_t a, notmuch_field_flag_t b)
Xapian::QueryParser::FLAG_WILDCARD | \
Xapian::QueryParser::FLAG_PURE_NOT)
/*
* Which parameters were explicit when the database was opened */
typedef enum {
NOTMUCH_PARAM_NONE = 0,
NOTMUCH_PARAM_DATABASE = 1 << 0,
NOTMUCH_PARAM_CONFIG = 1 << 1,
NOTMUCH_PARAM_PROFILE = 1 << 2,
} notmuch_open_param_t;
/*
* define bitwise operators to hide casts */
inline notmuch_open_param_t
operator| (notmuch_open_param_t a, notmuch_open_param_t b)
{
return static_cast<notmuch_open_param_t>(
static_cast<unsigned>(a) | static_cast<unsigned>(b));
}
inline notmuch_open_param_t&
operator|= (notmuch_open_param_t &a, notmuch_open_param_t b)
{
a = a | b;
return a;
}
inline notmuch_open_param_t
operator& (notmuch_open_param_t a, notmuch_open_param_t b)
{
return static_cast<notmuch_open_param_t>(
static_cast<unsigned>(a) & static_cast<unsigned>(b));
}
struct _notmuch_database {
bool exception_reported;
@ -232,6 +269,7 @@ struct _notmuch_database {
*/
unsigned long view;
Xapian::QueryParser *query_parser;
Xapian::Stem *stemmer;
Xapian::TermGenerator *term_gen;
Xapian::RangeProcessor *value_range_processor;
Xapian::RangeProcessor *date_range_processor;
@ -244,6 +282,9 @@ struct _notmuch_database {
/* Cached and possibly overridden configuration */
notmuch_string_map_t *config;
/* Track what parameters were specified when opening */
notmuch_open_param_t params;
};
/* Prior to database version 3, features were implied by the database
@ -300,4 +341,38 @@ _notmuch_database_setup_standard_query_fields (notmuch_database_t *notmuch);
notmuch_status_t
_notmuch_database_setup_user_query_fields (notmuch_database_t *notmuch);
#if __cplusplus
/* query.cc */
notmuch_status_t
_notmuch_query_string_to_xapian_query (notmuch_database_t *notmuch,
std::string query_string,
Xapian::Query &output,
std::string &msg);
/* parse-sexp.cc */
notmuch_status_t
_notmuch_sexp_string_to_xapian_query (notmuch_database_t *notmuch, const char *querystr,
Xapian::Query &output);
notmuch_status_t
_notmuch_query_expand (notmuch_database_t *notmuch, const char *field, Xapian::Query subquery,
Xapian::Query &output, std::string &msg);
/* regexp-fields.cc */
notmuch_status_t
_notmuch_regexp_to_query (notmuch_database_t *notmuch, Xapian::valueno slot, std::string field,
std::string regexp_str,
Xapian::Query &output, std::string &msg);
/* thread-fp.cc */
notmuch_status_t
_notmuch_query_name_to_query (notmuch_database_t *notmuch, const std::string name,
Xapian::Query &output);
#if HAVE_SFSEXP
/* parse-sexp.cc */
notmuch_status_t
_notmuch_sexp_string_to_xapian_query (notmuch_database_t *notmuch, const char *querystr,
Xapian::Query &output);
#endif
#endif
#endif

View file

@ -309,6 +309,8 @@ notmuch_status_to_string (notmuch_status_t status)
return "No database found";
case NOTMUCH_STATUS_DATABASE_EXISTS:
return "Database exists, not recreated";
case NOTMUCH_STATUS_BAD_QUERY_SYNTAX:
return "Syntax error in query";
default:
case NOTMUCH_STATUS_LAST_STATUS:
return "Unknown error status value";

View file

@ -291,11 +291,16 @@ _notmuch_message_file_get_header (notmuch_message_file_t *message,
if (value)
return value;
if (strcasecmp (header, "received") == 0) {
if (strcasecmp (header, "received") == 0 ||
strcasecmp (header, "delivered-to") == 0) {
/*
* The Received: header is special. We concatenate all
* instances of the header as we use this when analyzing the
* path the mail has taken from sender to recipient.
* The Received: header is special. We concatenate all instances of the
* header as we use this when analyzing the path the mail has taken
* from sender to recipient.
*
* Similarly, multiple instances of Delivered-To may be present. We
* concatenate them so the one with highest priority may be picked (eg.
* primary_email before other_email).
*/
decoded = _notmuch_message_file_get_combined_header (message, header);
} else {

View file

@ -58,7 +58,7 @@ NOTMUCH_BEGIN_DECLS
* version in Makefile.local.
*/
#define LIBNOTMUCH_MAJOR_VERSION 5
#define LIBNOTMUCH_MINOR_VERSION 4
#define LIBNOTMUCH_MINOR_VERSION 5
#define LIBNOTMUCH_MICRO_VERSION 0
@ -220,6 +220,10 @@ typedef enum _notmuch_status {
* Database exists, so not (re)-created
*/
NOTMUCH_STATUS_DATABASE_EXISTS,
/**
* Syntax error in query
*/
NOTMUCH_STATUS_BAD_QUERY_SYNTAX,
/**
* Not an actual status value. Just a way to find out how many
* valid status values there are.
@ -429,6 +433,8 @@ notmuch_database_open_verbose (const char *path,
* @retval NOTMUCH_STATUS_NULL_POINTER: The given \a database
* argument is NULL.
*
* @retval NOTMUCH_STATUS_NO_CONFIG: No config file was found. Fatal.
*
* @retval NOTMUCH_STATUS_OUT_OF_MEMORY: Out of memory.
*
* @retval NOTMUCH_STATUS_FILE_ERROR: An error occurred trying to open the
@ -454,6 +460,9 @@ notmuch_database_open_with_config (const char *database_path,
*
* For description of arguments, @see notmuch_database_open_with_config
*
* For errors other then NO_DATABASE and NO_CONFIG, *database is set to
* NULL.
*
* @retval NOTMUCH_STATUS_SUCCESS: Successfully loaded configuration.
*
* @retval NOTMUCH_STATUS_NO_CONFIG: No config file was loaded. Not fatal.
@ -485,6 +494,9 @@ notmuch_database_load_config (const char *database_path,
*
* For description of arguments, @see notmuch_database_open_with_config
*
* In case of any failure, this function returns an error status and
* sets *database to NULL.
*
* @retval NOTMUCH_STATUS_SUCCESS: Successfully created the database.
*
* @retval NOTMUCH_STATUS_DATABASE_EXISTS: Database already exists, not created
@ -961,6 +973,16 @@ notmuch_query_t *
notmuch_query_create (notmuch_database_t *database,
const char *query_string);
typedef enum {
NOTMUCH_QUERY_SYNTAX_XAPIAN,
NOTMUCH_QUERY_SYNTAX_SEXP
} notmuch_query_syntax_t;
notmuch_status_t
notmuch_query_create_with_syntax (notmuch_database_t *database,
const char *query_string,
notmuch_query_syntax_t syntax,
notmuch_query_t **output);
/**
* Sort values for notmuch_query_set_sort.
*/

View file

@ -247,7 +247,7 @@ _choose_database_path (void *ctx,
}
static notmuch_database_t *
_alloc_notmuch ()
_alloc_notmuch (const char *database_path, const char *config_path, const char *profile)
{
notmuch_database_t *notmuch;
@ -263,6 +263,15 @@ _alloc_notmuch ()
notmuch->transaction_count = 0;
notmuch->transaction_threshold = 0;
notmuch->view = 1;
notmuch->params = NOTMUCH_PARAM_NONE;
if (database_path)
notmuch->params |= NOTMUCH_PARAM_DATABASE;
if (config_path)
notmuch->params |= NOTMUCH_PARAM_CONFIG;
if (profile)
notmuch->params |= NOTMUCH_PARAM_PROFILE;
return notmuch;
}
@ -396,8 +405,6 @@ _finish_open (notmuch_database_t *notmuch,
" has a newer database format version (%u) than supported by this\n"
" version of notmuch (%u).\n",
database_path, version, NOTMUCH_DATABASE_VERSION));
notmuch_database_destroy (notmuch);
notmuch = NULL;
status = NOTMUCH_STATUS_FILE_ERROR;
goto DONE;
}
@ -414,8 +421,6 @@ _finish_open (notmuch_database_t *notmuch,
" requires features (%s)\n"
" not supported by this version of notmuch.\n",
database_path, incompat_features));
notmuch_database_destroy (notmuch);
notmuch = NULL;
status = NOTMUCH_STATUS_FILE_ERROR;
goto DONE;
}
@ -432,7 +437,8 @@ _finish_open (notmuch_database_t *notmuch,
"lastmod:");
notmuch->query_parser->set_default_op (Xapian::Query::OP_AND);
notmuch->query_parser->set_database (*notmuch->xapian_db);
notmuch->query_parser->set_stemmer (Xapian::Stem ("english"));
notmuch->stemmer = new Xapian::Stem ("english");
notmuch->query_parser->set_stemmer (*notmuch->stemmer);
notmuch->query_parser->set_stemming_strategy (Xapian::QueryParser::STEM_SOME);
notmuch->query_parser->add_rangeprocessor (notmuch->value_range_processor);
notmuch->query_parser->add_rangeprocessor (notmuch->date_range_processor);
@ -488,8 +494,6 @@ _finish_open (notmuch_database_t *notmuch,
} catch (const Xapian::Error &error) {
IGNORE_RESULT (asprintf (&message, "A Xapian exception occurred opening database: %s\n",
error.get_msg ().c_str ()));
notmuch_database_destroy (notmuch);
notmuch = NULL;
status = NOTMUCH_STATUS_XAPIAN_EXCEPTION;
}
DONE:
@ -515,7 +519,7 @@ notmuch_database_open_with_config (const char *database_path,
_notmuch_init ();
notmuch = _alloc_notmuch ();
notmuch = _alloc_notmuch (database_path, config_path, profile);
if (! notmuch) {
status = NOTMUCH_STATUS_OUT_OF_MEMORY;
goto DONE;
@ -558,10 +562,13 @@ notmuch_database_open_with_config (const char *database_path,
free (message);
}
if (status && notmuch) {
notmuch_database_destroy (notmuch);
notmuch = NULL;
}
if (database)
*database = notmuch;
else
talloc_free (notmuch);
if (notmuch)
notmuch->open = true;
@ -612,7 +619,7 @@ notmuch_database_create_with_config (const char *database_path,
_notmuch_init ();
notmuch = _alloc_notmuch ();
notmuch = _alloc_notmuch (database_path, config_path, profile);
if (! notmuch) {
status = NOTMUCH_STATUS_OUT_OF_MEMORY;
goto DONE;
@ -716,10 +723,16 @@ notmuch_database_create_with_config (const char *database_path,
else
free (message);
}
if (status && notmuch) {
notmuch_database_destroy (notmuch);
notmuch = NULL;
}
if (database)
*database = notmuch;
else
talloc_free (notmuch);
if (notmuch)
notmuch->open = true;
return status;
}
@ -808,7 +821,7 @@ notmuch_database_load_config (const char *database_path,
_notmuch_init ();
notmuch = _alloc_notmuch ();
notmuch = _alloc_notmuch (database_path, config_path, profile);
if (! notmuch) {
status = NOTMUCH_STATUS_OUT_OF_MEMORY;
goto DONE;
@ -867,6 +880,13 @@ notmuch_database_load_config (const char *database_path,
if (status_string)
*status_string = message;
if (status &&
status != NOTMUCH_STATUS_NO_DATABASE
&& status != NOTMUCH_STATUS_NO_CONFIG) {
notmuch_database_destroy (notmuch);
notmuch = NULL;
}
if (database)
*database = notmuch;

587
lib/parse-sexp.cc Normal file
View file

@ -0,0 +1,587 @@
#include "database-private.h"
#if HAVE_SFSEXP
#include "sexp.h"
#include "unicode-util.h"
/* _sexp is used for file scope symbols to avoid clashing with
* definitions from sexp.h */
/* sexp_binding structs attach name to a sexp and a defining
* context. The latter allows lazy evaluation of parameters whose
* definition contains other parameters. Lazy evaluation is needed
* because a primary goal of macros is to change the parent field for
* a sexp.
*/
typedef struct sexp_binding {
const char *name;
const sexp_t *sx;
const struct sexp_binding *context;
const struct sexp_binding *next;
} _sexp_binding_t;
typedef enum {
SEXP_FLAG_NONE = 0,
SEXP_FLAG_FIELD = 1 << 0,
SEXP_FLAG_BOOLEAN = 1 << 1,
SEXP_FLAG_SINGLE = 1 << 2,
SEXP_FLAG_WILDCARD = 1 << 3,
SEXP_FLAG_REGEX = 1 << 4,
SEXP_FLAG_DO_REGEX = 1 << 5,
SEXP_FLAG_EXPAND = 1 << 6,
SEXP_FLAG_DO_EXPAND = 1 << 7,
SEXP_FLAG_ORPHAN = 1 << 8,
} _sexp_flag_t;
/*
* define bitwise operators to hide casts */
inline _sexp_flag_t
operator| (_sexp_flag_t a, _sexp_flag_t b)
{
return static_cast<_sexp_flag_t>(
static_cast<unsigned>(a) | static_cast<unsigned>(b));
}
inline _sexp_flag_t
operator& (_sexp_flag_t a, _sexp_flag_t b)
{
return static_cast<_sexp_flag_t>(
static_cast<unsigned>(a) & static_cast<unsigned>(b));
}
typedef struct {
const char *name;
Xapian::Query::op xapian_op;
Xapian::Query initial;
_sexp_flag_t flags;
} _sexp_prefix_t;
static _sexp_prefix_t prefixes[] =
{
{ "and", Xapian::Query::OP_AND, Xapian::Query::MatchAll,
SEXP_FLAG_NONE },
{ "attachment", Xapian::Query::OP_AND, Xapian::Query::MatchAll,
SEXP_FLAG_FIELD | SEXP_FLAG_WILDCARD | SEXP_FLAG_EXPAND },
{ "body", Xapian::Query::OP_AND, Xapian::Query::MatchAll,
SEXP_FLAG_FIELD },
{ "from", Xapian::Query::OP_AND, Xapian::Query::MatchAll,
SEXP_FLAG_FIELD | SEXP_FLAG_WILDCARD | SEXP_FLAG_REGEX | SEXP_FLAG_EXPAND },
{ "folder", Xapian::Query::OP_OR, Xapian::Query::MatchNothing,
SEXP_FLAG_FIELD | SEXP_FLAG_BOOLEAN | SEXP_FLAG_WILDCARD | SEXP_FLAG_REGEX | SEXP_FLAG_EXPAND },
{ "id", Xapian::Query::OP_OR, Xapian::Query::MatchNothing,
SEXP_FLAG_FIELD | SEXP_FLAG_BOOLEAN | SEXP_FLAG_WILDCARD | SEXP_FLAG_REGEX },
{ "infix", Xapian::Query::OP_INVALID, Xapian::Query::MatchAll,
SEXP_FLAG_SINGLE | SEXP_FLAG_ORPHAN },
{ "is", Xapian::Query::OP_AND, Xapian::Query::MatchAll,
SEXP_FLAG_FIELD | SEXP_FLAG_BOOLEAN | SEXP_FLAG_WILDCARD | SEXP_FLAG_REGEX | SEXP_FLAG_EXPAND },
{ "matching", Xapian::Query::OP_AND, Xapian::Query::MatchAll,
SEXP_FLAG_DO_EXPAND },
{ "mid", Xapian::Query::OP_OR, Xapian::Query::MatchNothing,
SEXP_FLAG_FIELD | SEXP_FLAG_BOOLEAN | SEXP_FLAG_WILDCARD | SEXP_FLAG_REGEX },
{ "mimetype", Xapian::Query::OP_AND, Xapian::Query::MatchAll,
SEXP_FLAG_FIELD | SEXP_FLAG_WILDCARD | SEXP_FLAG_EXPAND },
{ "not", Xapian::Query::OP_AND_NOT, Xapian::Query::MatchAll,
SEXP_FLAG_NONE },
{ "of", Xapian::Query::OP_AND, Xapian::Query::MatchAll,
SEXP_FLAG_DO_EXPAND },
{ "or", Xapian::Query::OP_OR, Xapian::Query::MatchNothing,
SEXP_FLAG_NONE },
{ "path", Xapian::Query::OP_OR, Xapian::Query::MatchNothing,
SEXP_FLAG_FIELD | SEXP_FLAG_BOOLEAN | SEXP_FLAG_WILDCARD | SEXP_FLAG_REGEX },
{ "property", Xapian::Query::OP_AND, Xapian::Query::MatchAll,
SEXP_FLAG_FIELD | SEXP_FLAG_BOOLEAN | SEXP_FLAG_WILDCARD | SEXP_FLAG_REGEX | SEXP_FLAG_EXPAND },
{ "query", Xapian::Query::OP_INVALID, Xapian::Query::MatchNothing,
SEXP_FLAG_SINGLE | SEXP_FLAG_ORPHAN },
{ "regex", Xapian::Query::OP_INVALID, Xapian::Query::MatchAll,
SEXP_FLAG_SINGLE | SEXP_FLAG_DO_REGEX },
{ "rx", Xapian::Query::OP_INVALID, Xapian::Query::MatchAll,
SEXP_FLAG_SINGLE | SEXP_FLAG_DO_REGEX },
{ "starts-with", Xapian::Query::OP_WILDCARD, Xapian::Query::MatchAll,
SEXP_FLAG_SINGLE },
{ "subject", Xapian::Query::OP_AND, Xapian::Query::MatchAll,
SEXP_FLAG_FIELD | SEXP_FLAG_WILDCARD | SEXP_FLAG_REGEX | SEXP_FLAG_EXPAND },
{ "tag", Xapian::Query::OP_AND, Xapian::Query::MatchAll,
SEXP_FLAG_FIELD | SEXP_FLAG_BOOLEAN | SEXP_FLAG_WILDCARD | SEXP_FLAG_REGEX | SEXP_FLAG_EXPAND },
{ "thread", Xapian::Query::OP_OR, Xapian::Query::MatchNothing,
SEXP_FLAG_FIELD | SEXP_FLAG_BOOLEAN | SEXP_FLAG_WILDCARD | SEXP_FLAG_REGEX | SEXP_FLAG_EXPAND },
{ "to", Xapian::Query::OP_AND, Xapian::Query::MatchAll,
SEXP_FLAG_FIELD | SEXP_FLAG_WILDCARD | SEXP_FLAG_EXPAND },
{ }
};
static notmuch_status_t _sexp_to_xapian_query (notmuch_database_t *notmuch,
const _sexp_prefix_t *parent,
const _sexp_binding_t *env,
const sexp_t *sx,
Xapian::Query &output);
static notmuch_status_t
_sexp_combine_query (notmuch_database_t *notmuch,
const _sexp_prefix_t *parent,
const _sexp_binding_t *env,
Xapian::Query::op operation,
Xapian::Query left,
const sexp_t *sx,
Xapian::Query &output)
{
Xapian::Query subquery;
notmuch_status_t status;
/* if we run out elements, return accumulator */
if (! sx) {
output = left;
return NOTMUCH_STATUS_SUCCESS;
}
status = _sexp_to_xapian_query (notmuch, parent, env, sx, subquery);
if (status)
return status;
return _sexp_combine_query (notmuch,
parent,
env,
operation,
Xapian::Query (operation, left, subquery),
sx->next, output);
}
static notmuch_status_t
_sexp_parse_phrase (std::string term_prefix, const char *phrase, Xapian::Query &output)
{
Xapian::Utf8Iterator p (phrase);
Xapian::Utf8Iterator end;
std::vector<std::string> terms;
while (p != end) {
Xapian::Utf8Iterator start;
while (p != end && ! Xapian::Unicode::is_wordchar (*p))
p++;
if (p == end)
break;
start = p;
while (p != end && Xapian::Unicode::is_wordchar (*p))
p++;
if (p != start) {
std::string word (start, p);
word = Xapian::Unicode::tolower (word);
terms.push_back (term_prefix + word);
}
}
output = Xapian::Query (Xapian::Query::OP_PHRASE, terms.begin (), terms.end ());
return NOTMUCH_STATUS_SUCCESS;
}
static notmuch_status_t
_sexp_parse_wildcard (notmuch_database_t *notmuch,
const _sexp_prefix_t *parent,
unused(const _sexp_binding_t *env),
std::string match,
Xapian::Query &output)
{
std::string term_prefix = parent ? _notmuch_database_prefix (notmuch, parent->name) : "";
if (parent && ! (parent->flags & SEXP_FLAG_WILDCARD)) {
_notmuch_database_log (notmuch, "'%s' does not support wildcard queries\n", parent->name);
return NOTMUCH_STATUS_BAD_QUERY_SYNTAX;
}
output = Xapian::Query (Xapian::Query::OP_WILDCARD,
term_prefix + Xapian::Unicode::tolower (match));
return NOTMUCH_STATUS_SUCCESS;
}
static notmuch_status_t
_sexp_parse_one_term (notmuch_database_t *notmuch, std::string term_prefix, const sexp_t *sx,
Xapian::Query &output)
{
Xapian::Stem stem = *(notmuch->stemmer);
if (sx->aty == SEXP_BASIC && unicode_word_utf8 (sx->val)) {
std::string term = Xapian::Unicode::tolower (sx->val);
output = Xapian::Query ("Z" + term_prefix + stem (term));
return NOTMUCH_STATUS_SUCCESS;
} else {
return _sexp_parse_phrase (term_prefix, sx->val, output);
}
}
notmuch_status_t
_sexp_parse_regex (notmuch_database_t *notmuch,
const _sexp_prefix_t *prefix, const _sexp_prefix_t *parent,
unused(const _sexp_binding_t *env),
std::string val, Xapian::Query &output)
{
if (! parent) {
_notmuch_database_log (notmuch, "illegal '%s' outside field\n",
prefix->name);
return NOTMUCH_STATUS_BAD_QUERY_SYNTAX;
}
if (! (parent->flags & SEXP_FLAG_REGEX)) {
_notmuch_database_log (notmuch, "'%s' not supported in field '%s'\n",
prefix->name, parent->name);
return NOTMUCH_STATUS_BAD_QUERY_SYNTAX;
}
std::string msg; /* ignored */
return _notmuch_regexp_to_query (notmuch, Xapian::BAD_VALUENO, parent->name,
val, output, msg);
}
static notmuch_status_t
_sexp_expand_query (notmuch_database_t *notmuch,
const _sexp_prefix_t *prefix, const _sexp_prefix_t *parent,
unused(const _sexp_binding_t *env), const sexp_t *sx, Xapian::Query &output)
{
Xapian::Query subquery;
notmuch_status_t status;
std::string msg;
if (! (parent->flags & SEXP_FLAG_EXPAND)) {
_notmuch_database_log (notmuch, "'%s' unsupported inside '%s'\n", prefix->name, parent->name);
return NOTMUCH_STATUS_BAD_QUERY_SYNTAX;
}
status = _sexp_combine_query (notmuch, NULL, NULL, prefix->xapian_op, prefix->initial, sx,
subquery);
if (status)
return status;
status = _notmuch_query_expand (notmuch, parent->name, subquery, output, msg);
if (status) {
_notmuch_database_log (notmuch, "error expanding query %s\n", msg.c_str ());
}
return status;
}
static notmuch_status_t
_sexp_parse_infix (notmuch_database_t *notmuch, const sexp_t *sx, Xapian::Query &output)
{
try {
output = notmuch->query_parser->parse_query (sx->val, NOTMUCH_QUERY_PARSER_FLAGS);
} catch (const Xapian::QueryParserError &error) {
_notmuch_database_log (notmuch, "Syntax error in infix query: %s\n", sx->val);
return NOTMUCH_STATUS_BAD_QUERY_SYNTAX;
} catch (const Xapian::Error &error) {
if (! notmuch->exception_reported) {
_notmuch_database_log (notmuch,
"A Xapian exception occurred parsing query: %s\n",
error.get_msg ().c_str ());
_notmuch_database_log_append (notmuch,
"Query string was: %s\n",
sx->val);
notmuch->exception_reported = true;
return NOTMUCH_STATUS_XAPIAN_EXCEPTION;
}
}
return NOTMUCH_STATUS_SUCCESS;
}
static notmuch_status_t
_sexp_parse_header (notmuch_database_t *notmuch, const _sexp_prefix_t *parent,
const _sexp_binding_t *env, const sexp_t *sx, Xapian::Query &output)
{
_sexp_prefix_t user_prefix;
user_prefix.name = sx->list->val;
user_prefix.flags = SEXP_FLAG_FIELD | SEXP_FLAG_WILDCARD;
if (parent) {
_notmuch_database_log (notmuch, "nested field: '%s' inside '%s'\n",
sx->list->val, parent->name);
return NOTMUCH_STATUS_BAD_QUERY_SYNTAX;
}
parent = &user_prefix;
return _sexp_combine_query (notmuch, parent, env, Xapian::Query::OP_AND, Xapian::Query::MatchAll,
sx->list->next, output);
}
static _sexp_binding_t *
_sexp_bind (void *ctx, const _sexp_binding_t *env, const char *name, const sexp_t *sx, const
_sexp_binding_t *context)
{
_sexp_binding_t *binding = talloc (ctx, _sexp_binding_t);
binding->name = talloc_strdup (ctx, name);
binding->sx = sx;
binding->context = context;
binding->next = env;
return binding;
}
static notmuch_status_t
maybe_apply_macro (notmuch_database_t *notmuch, const _sexp_prefix_t *parent,
const _sexp_binding_t *env, const sexp_t *sx, const sexp_t *args,
Xapian::Query &output)
{
const sexp_t *params, *param, *arg, *body;
void *local = talloc_new (notmuch);
_sexp_binding_t *new_env = NULL;
notmuch_status_t status = NOTMUCH_STATUS_SUCCESS;
if (sx->list->ty != SEXP_VALUE || strcmp (sx->list->val, "macro") != 0) {
status = NOTMUCH_STATUS_IGNORED;
goto DONE;
}
params = sx->list->next;
if (! params || (params->ty != SEXP_LIST)) {
_notmuch_database_log (notmuch, "missing (possibly empty) list of arguments to macro\n");
return NOTMUCH_STATUS_BAD_QUERY_SYNTAX;
}
body = params->next;
if (! body) {
_notmuch_database_log (notmuch, "missing body of macro\n");
status = NOTMUCH_STATUS_BAD_QUERY_SYNTAX;
goto DONE;
}
for (param = params->list, arg = args;
param && arg;
param = param->next, arg = arg->next) {
if (param->ty != SEXP_VALUE || param->aty != SEXP_BASIC) {
_notmuch_database_log (notmuch, "macro parameters must be unquoted atoms\n");
status = NOTMUCH_STATUS_BAD_QUERY_SYNTAX;
goto DONE;
}
new_env = _sexp_bind (local, new_env, param->val, arg, env);
}
if (param && ! arg) {
_notmuch_database_log (notmuch, "too few arguments to macro\n");
status = NOTMUCH_STATUS_BAD_QUERY_SYNTAX;
goto DONE;
}
if (! param && arg) {
_notmuch_database_log (notmuch, "too many arguments to macro\n");
status = NOTMUCH_STATUS_BAD_QUERY_SYNTAX;
goto DONE;
}
status = _sexp_to_xapian_query (notmuch, parent, new_env, body, output);
DONE:
if (local)
talloc_free (local);
return status;
}
static notmuch_status_t
maybe_saved_squery (notmuch_database_t *notmuch, const _sexp_prefix_t *parent,
const _sexp_binding_t *env, const sexp_t *sx, Xapian::Query &output)
{
char *key;
char *expansion = NULL;
notmuch_status_t status;
sexp_t *saved_sexp;
void *local = talloc_new (notmuch);
char *buf;
key = talloc_asprintf (local, "squery.%s", sx->list->val);
if (! key) {
status = NOTMUCH_STATUS_OUT_OF_MEMORY;
goto DONE;
}
status = notmuch_database_get_config (notmuch, key, &expansion);
if (status)
goto DONE;
if (EMPTY_STRING (expansion)) {
status = NOTMUCH_STATUS_IGNORED;
goto DONE;
}
buf = talloc_strdup (local, expansion);
/* XXX TODO: free this memory */
saved_sexp = parse_sexp (buf, strlen (expansion));
if (! saved_sexp) {
_notmuch_database_log (notmuch, "invalid saved s-expression query: '%s'\n", expansion);
status = NOTMUCH_STATUS_BAD_QUERY_SYNTAX;
goto DONE;
}
status = maybe_apply_macro (notmuch, parent, env, saved_sexp, sx->list->next, output);
if (status == NOTMUCH_STATUS_IGNORED)
status = _sexp_to_xapian_query (notmuch, parent, env, saved_sexp, output);
DONE:
if (local)
talloc_free (local);
return status;
}
static notmuch_status_t
_sexp_expand_param (notmuch_database_t *notmuch, const _sexp_prefix_t *parent,
const _sexp_binding_t *env, const char *name,
Xapian::Query &output)
{
for (; env; env = env->next) {
if (strcmp (name, env->name) == 0) {
return _sexp_to_xapian_query (notmuch, parent, env->context, env->sx,
output);
}
}
_notmuch_database_log (notmuch, "undefined parameter %s\n", name);
return NOTMUCH_STATUS_BAD_QUERY_SYNTAX;
}
/* Here we expect the s-expression to be a proper list, with first
* element defining and operation, or as a special case the empty
* list */
static notmuch_status_t
_sexp_to_xapian_query (notmuch_database_t *notmuch, const _sexp_prefix_t *parent,
const _sexp_binding_t *env, const sexp_t *sx, Xapian::Query &output)
{
notmuch_status_t status;
if (sx->ty == SEXP_VALUE && sx->aty == SEXP_BASIC && sx->val[0] == ',') {
return _sexp_expand_param (notmuch, parent, env, sx->val + 1, output);
}
if (sx->ty == SEXP_VALUE) {
std::string term_prefix = parent ? _notmuch_database_prefix (notmuch, parent->name) : "";
if (sx->aty == SEXP_BASIC && strcmp (sx->val, "*") == 0) {
return _sexp_parse_wildcard (notmuch, parent, env, "", output);
}
if (parent && (parent->flags & SEXP_FLAG_BOOLEAN)) {
output = Xapian::Query (term_prefix + sx->val);
return NOTMUCH_STATUS_SUCCESS;
}
if (parent) {
return _sexp_parse_one_term (notmuch, term_prefix, sx, output);
} else {
Xapian::Query accumulator;
for (_sexp_prefix_t *prefix = prefixes; prefix->name; prefix++) {
if (prefix->flags & SEXP_FLAG_FIELD) {
Xapian::Query subquery;
term_prefix = _notmuch_database_prefix (notmuch, prefix->name);
status = _sexp_parse_one_term (notmuch, term_prefix, sx, subquery);
if (status)
return status;
accumulator = Xapian::Query (Xapian::Query::OP_OR, accumulator, subquery);
}
}
output = accumulator;
return NOTMUCH_STATUS_SUCCESS;
}
}
/* Empty list */
if (! sx->list) {
output = Xapian::Query::MatchAll;
return NOTMUCH_STATUS_SUCCESS;
}
if (sx->list->ty == SEXP_LIST) {
_notmuch_database_log (notmuch, "unexpected list in field/operation position\n",
sx->list->val);
return NOTMUCH_STATUS_BAD_QUERY_SYNTAX;
}
status = maybe_saved_squery (notmuch, parent, env, sx, output);
if (status != NOTMUCH_STATUS_IGNORED)
return status;
/* Check for user defined field */
if (_notmuch_string_map_get (notmuch->user_prefix, sx->list->val)) {
return _sexp_parse_header (notmuch, parent, env, sx, output);
}
if (strcmp (sx->list->val, "macro") == 0) {
_notmuch_database_log (notmuch, "macro definition not permitted here\n");
return NOTMUCH_STATUS_BAD_QUERY_SYNTAX;
}
for (_sexp_prefix_t *prefix = prefixes; prefix && prefix->name; prefix++) {
if (strcmp (prefix->name, sx->list->val) == 0) {
if (prefix->flags & SEXP_FLAG_FIELD) {
if (parent) {
_notmuch_database_log (notmuch, "nested field: '%s' inside '%s'\n",
prefix->name, parent->name);
return NOTMUCH_STATUS_BAD_QUERY_SYNTAX;
}
parent = prefix;
}
if (parent && (prefix->flags & SEXP_FLAG_ORPHAN)) {
_notmuch_database_log (notmuch, "'%s' not supported inside '%s'\n",
prefix->name, parent->name);
return NOTMUCH_STATUS_BAD_QUERY_SYNTAX;
}
if ((prefix->flags & SEXP_FLAG_SINGLE) &&
(! sx->list->next || sx->list->next->next || sx->list->next->ty != SEXP_VALUE)) {
_notmuch_database_log (notmuch, "'%s' expects single atom as argument\n",
prefix->name);
return NOTMUCH_STATUS_BAD_QUERY_SYNTAX;
}
if (strcmp (prefix->name, "infix") == 0) {
return _sexp_parse_infix (notmuch, sx->list->next, output);
}
if (strcmp (prefix->name, "query") == 0) {
return _notmuch_query_name_to_query (notmuch, sx->list->next->val, output);
}
if (prefix->xapian_op == Xapian::Query::OP_WILDCARD)
return _sexp_parse_wildcard (notmuch, parent, env, sx->list->next->val, output);
if (prefix->flags & SEXP_FLAG_DO_REGEX) {
return _sexp_parse_regex (notmuch, prefix, parent, env, sx->list->next->val, output);
}
if (prefix->flags & SEXP_FLAG_DO_EXPAND) {
return _sexp_expand_query (notmuch, prefix, parent, env, sx->list->next, output);
}
return _sexp_combine_query (notmuch, parent, env, prefix->xapian_op, prefix->initial,
sx->list->next, output);
}
}
_notmuch_database_log (notmuch, "unknown prefix '%s'\n", sx->list->val);
return NOTMUCH_STATUS_BAD_QUERY_SYNTAX;
}
notmuch_status_t
_notmuch_sexp_string_to_xapian_query (notmuch_database_t *notmuch, const char *querystr,
Xapian::Query &output)
{
const sexp_t *sx = NULL;
char *buf = talloc_strdup (notmuch, querystr);
sx = parse_sexp (buf, strlen (querystr));
if (! sx) {
_notmuch_database_log (notmuch, "invalid s-expression: '%s'\n", querystr);
return NOTMUCH_STATUS_BAD_QUERY_SYNTAX;
}
return _sexp_to_xapian_query (notmuch, NULL, NULL, sx, output);
}
#endif

View file

@ -24,17 +24,33 @@
#include "query-fp.h"
#include <iostream>
Xapian::Query
QueryFieldProcessor::operator() (const std::string & name)
notmuch_status_t
_notmuch_query_name_to_query (notmuch_database_t *notmuch, const std::string name,
Xapian::Query &output)
{
std::string key = "query." + name;
char *expansion;
notmuch_status_t status;
status = notmuch_database_get_config (notmuch, key.c_str (), &expansion);
if (status)
return status;
output = notmuch->query_parser->parse_query (expansion, NOTMUCH_QUERY_PARSER_FLAGS);
return NOTMUCH_STATUS_SUCCESS;
}
Xapian::Query
QueryFieldProcessor::operator() (const std::string & name)
{
notmuch_status_t status;
Xapian::Query output;
status = _notmuch_query_name_to_query (notmuch, name, output);
if (status) {
throw Xapian::QueryParserError ("error looking up key" + name);
}
return parser.parse_query (expansion, NOTMUCH_QUERY_PARSER_FLAGS);
return output;
}

View file

@ -30,6 +30,7 @@ struct _notmuch_query {
notmuch_string_list_t *exclude_terms;
notmuch_exclude_t omit_excluded;
bool parsed;
notmuch_query_syntax_t syntax;
Xapian::Query xapian_query;
std::set<std::string> terms;
};
@ -84,9 +85,9 @@ _notmuch_query_destructor (notmuch_query_t *query)
return 0;
}
notmuch_query_t *
notmuch_query_create (notmuch_database_t *notmuch,
const char *query_string)
static notmuch_query_t *
_notmuch_query_constructor (notmuch_database_t *notmuch,
const char *query_string)
{
notmuch_query_t *query;
@ -105,7 +106,10 @@ notmuch_query_create (notmuch_database_t *notmuch,
query->notmuch = notmuch;
query->query_string = talloc_strdup (query, query_string);
if (query_string)
query->query_string = talloc_strdup (query, query_string);
else
query->query_string = NULL;
query->sort = NOTMUCH_SORT_NEWEST_FIRST;
@ -116,42 +120,142 @@ notmuch_query_create (notmuch_database_t *notmuch,
return query;
}
notmuch_query_t *
notmuch_query_create (notmuch_database_t *notmuch,
const char *query_string)
{
notmuch_query_t *query;
notmuch_status_t status;
status = notmuch_query_create_with_syntax (notmuch, query_string,
NOTMUCH_QUERY_SYNTAX_XAPIAN,
&query);
if (status)
return NULL;
return query;
}
notmuch_status_t
notmuch_query_create_with_syntax (notmuch_database_t *notmuch,
const char *query_string,
notmuch_query_syntax_t syntax,
notmuch_query_t **output)
{
notmuch_query_t *query;
if (! output)
return NOTMUCH_STATUS_NULL_POINTER;
query = _notmuch_query_constructor (notmuch, query_string);
if (! query)
return NOTMUCH_STATUS_OUT_OF_MEMORY;
if (syntax == NOTMUCH_QUERY_SYNTAX_SEXP && ! HAVE_SFSEXP) {
_notmuch_database_log (notmuch, "sexp query parser not available");
return NOTMUCH_STATUS_ILLEGAL_ARGUMENT;
}
query->syntax = syntax;
*output = query;
return NOTMUCH_STATUS_SUCCESS;
}
static void
_notmuch_query_cache_terms (notmuch_query_t *query)
{
/* Xapian doesn't support skip_to on terms from a query since
* they are unordered, so cache a copy of all terms in
* something searchable.
*/
for (Xapian::TermIterator t = query->xapian_query.get_terms_begin ();
t != query->xapian_query.get_terms_end (); ++t)
query->terms.insert (*t);
}
notmuch_status_t
_notmuch_query_string_to_xapian_query (notmuch_database_t *notmuch,
std::string query_string,
Xapian::Query &output,
std::string &msg)
{
try {
if (query_string == "" || query_string == "*") {
output = Xapian::Query::MatchAll;
} else {
output =
notmuch->query_parser->
parse_query (query_string, NOTMUCH_QUERY_PARSER_FLAGS);
}
} catch (const Xapian::Error &error) {
if (! notmuch->exception_reported) {
_notmuch_database_log (notmuch,
"A Xapian exception occurred parsing query: %s\n",
error.get_msg ().c_str ());
_notmuch_database_log_append (notmuch,
"Query string was: %s\n",
query_string.c_str ());
notmuch->exception_reported = true;
}
msg = error.get_msg ();
return NOTMUCH_STATUS_XAPIAN_EXCEPTION;
}
return NOTMUCH_STATUS_SUCCESS;
}
static notmuch_status_t
_notmuch_query_ensure_parsed_xapian (notmuch_query_t *query)
{
notmuch_status_t status;
std::string msg; /* ignored */
status = _notmuch_query_string_to_xapian_query (query->notmuch, query->query_string,
query->xapian_query, msg);
if (status)
return status;
query->parsed = true;
_notmuch_query_cache_terms (query);
return NOTMUCH_STATUS_SUCCESS;
}
static notmuch_status_t
_notmuch_query_ensure_parsed_sexpr (notmuch_query_t *query)
{
notmuch_status_t status;
if (query->parsed)
return NOTMUCH_STATUS_SUCCESS;
status = _notmuch_sexp_string_to_xapian_query (query->notmuch, query->query_string,
query->xapian_query);
if (status)
return status;
_notmuch_query_cache_terms (query);
return NOTMUCH_STATUS_SUCCESS;
}
static notmuch_status_t
_notmuch_query_ensure_parsed (notmuch_query_t *query)
{
if (query->parsed)
return NOTMUCH_STATUS_SUCCESS;
try {
query->xapian_query =
query->notmuch->query_parser->
parse_query (query->query_string, NOTMUCH_QUERY_PARSER_FLAGS);
#if HAVE_SFSEXP
if (query->syntax == NOTMUCH_QUERY_SYNTAX_SEXP)
return _notmuch_query_ensure_parsed_sexpr (query);
#endif
/* Xapian doesn't support skip_to on terms from a query since
* they are unordered, so cache a copy of all terms in
* something searchable.
*/
for (Xapian::TermIterator t = query->xapian_query.get_terms_begin ();
t != query->xapian_query.get_terms_end (); ++t)
query->terms.insert (*t);
query->parsed = true;
} catch (const Xapian::Error &error) {
if (! query->notmuch->exception_reported) {
_notmuch_database_log (query->notmuch,
"A Xapian exception occurred parsing query: %s\n",
error.get_msg ().c_str ());
_notmuch_database_log_append (query->notmuch,
"Query string was: %s\n",
query->query_string);
query->notmuch->exception_reported = true;
}
return NOTMUCH_STATUS_XAPIAN_EXCEPTION;
}
return NOTMUCH_STATUS_SUCCESS;
return _notmuch_query_ensure_parsed_xapian (query);
}
const char *
@ -249,7 +353,6 @@ _notmuch_query_search_documents (notmuch_query_t *query,
notmuch_messages_t **out)
{
notmuch_database_t *notmuch = query->notmuch;
const char *query_string = query->query_string;
notmuch_mset_messages_t *messages;
notmuch_status_t status;
@ -279,13 +382,9 @@ _notmuch_query_search_documents (notmuch_query_t *query,
Xapian::MSet mset;
Xapian::MSetIterator iterator;
if (strcmp (query_string, "") == 0 ||
strcmp (query_string, "*") == 0) {
final_query = mail_query;
} else {
final_query = Xapian::Query (Xapian::Query::OP_AND,
mail_query, query->xapian_query);
}
final_query = Xapian::Query (Xapian::Query::OP_AND,
mail_query, query->xapian_query);
messages->base.excluded_doc_ids = NULL;
if ((query->omit_excluded != NOTMUCH_EXCLUDE_FALSE) && (query->exclude_terms)) {
@ -606,7 +705,6 @@ notmuch_status_t
_notmuch_query_count_documents (notmuch_query_t *query, const char *type, unsigned *count_out)
{
notmuch_database_t *notmuch = query->notmuch;
const char *query_string = query->query_string;
Xapian::doccount count = 0;
notmuch_status_t status;
@ -622,13 +720,8 @@ _notmuch_query_count_documents (notmuch_query_t *query, const char *type, unsign
Xapian::Query final_query, exclude_query;
Xapian::MSet mset;
if (strcmp (query_string, "") == 0 ||
strcmp (query_string, "*") == 0) {
final_query = mail_query;
} else {
final_query = Xapian::Query (Xapian::Query::OP_AND,
mail_query, query->xapian_query);
}
final_query = Xapian::Query (Xapian::Query::OP_AND,
mail_query, query->xapian_query);
exclude_query = _notmuch_exclude_tags (query);
@ -728,3 +821,51 @@ notmuch_query_get_database (const notmuch_query_t *query)
{
return query->notmuch;
}
notmuch_status_t
_notmuch_query_expand (notmuch_database_t *notmuch, const char *field, Xapian::Query subquery,
Xapian::Query &output, std::string &msg)
{
std::set<std::string> terms;
const std::string term_prefix = _find_prefix (field);
if (_debug_query ()) {
fprintf (stderr, "Expanding subquery:\n%s\n",
subquery.get_description ().c_str ());
}
try {
Xapian::Enquire enquire (*notmuch->xapian_db);
Xapian::MSet mset;
enquire.set_weighting_scheme (Xapian::BoolWeight ());
enquire.set_query (subquery);
mset = enquire.get_mset (0, notmuch->xapian_db->get_doccount ());
for (Xapian::MSetIterator iterator = mset.begin (); iterator != mset.end (); iterator++) {
Xapian::docid doc_id = *iterator;
Xapian::Document doc = notmuch->xapian_db->get_document (doc_id);
Xapian::TermIterator i = doc.termlist_begin ();
for (i.skip_to (term_prefix);
i != doc.termlist_end () && ((*i).rfind (term_prefix, 0) == 0); i++) {
terms.insert (*i);
}
}
output = Xapian::Query (Xapian::Query::OP_OR, terms.begin (), terms.end ());
if (_debug_query ()) {
fprintf (stderr, "Expanded query:\n%s\n",
subquery.get_description ().c_str ());
}
} catch (const Xapian::Error &error) {
_notmuch_database_log (notmuch,
"A Xapian exception occurred expanding query: %s\n",
error.get_msg ().c_str ());
msg = error.get_msg ();
return NOTMUCH_STATUS_XAPIAN_EXCEPTION;
}
return NOTMUCH_STATUS_SUCCESS;
}

View file

@ -26,27 +26,32 @@
#include "notmuch-private.h"
#include "database-private.h"
static void
compile_regex (regex_t &regexp, const char *str)
notmuch_status_t
compile_regex (regex_t &regexp, const char *str, std::string &msg)
{
int err = regcomp (&regexp, str, REG_EXTENDED | REG_NOSUB);
if (err != 0) {
size_t len = regerror (err, &regexp, NULL, 0);
char *buffer = new char[len];
std::string msg = "Regexp error: ";
msg = "Regexp error: ";
(void) regerror (err, &regexp, buffer, len);
msg.append (buffer, len);
delete[] buffer;
throw Xapian::QueryParserError (msg);
return NOTMUCH_STATUS_ILLEGAL_ARGUMENT;
}
return NOTMUCH_STATUS_SUCCESS;
}
RegexpPostingSource::RegexpPostingSource (Xapian::valueno slot, const std::string &regexp)
: slot_ (slot)
{
compile_regex (regexp_, regexp.c_str ());
std::string msg;
notmuch_status_t status = compile_regex (regexp_, regexp.c_str (), msg);
if (status)
throw Xapian::QueryParserError (msg);
}
RegexpPostingSource::~RegexpPostingSource ()
@ -141,18 +146,54 @@ _find_slot (std::string prefix)
return Xapian::BAD_VALUENO;
}
RegexpFieldProcessor::RegexpFieldProcessor (std::string prefix,
RegexpFieldProcessor::RegexpFieldProcessor (std::string field_,
notmuch_field_flag_t options_,
Xapian::QueryParser &parser_,
notmuch_database_t *notmuch_)
: slot (_find_slot (prefix)),
term_prefix (_find_prefix (prefix.c_str ())),
: slot (_find_slot (field_)),
field (field_),
term_prefix (_find_prefix (field_.c_str ())),
options (options_),
parser (parser_),
notmuch (notmuch_)
{
};
notmuch_status_t
_notmuch_regexp_to_query (notmuch_database_t *notmuch, Xapian::valueno slot, std::string field,
std::string regexp_str,
Xapian::Query &output, std::string &msg)
{
regex_t regexp;
notmuch_status_t status;
status = compile_regex (regexp, regexp_str.c_str (), msg);
if (status) {
_notmuch_database_log_append (notmuch, "error compiling regex %s", msg.c_str ());
return status;
}
if (slot == Xapian::BAD_VALUENO)
slot = _find_slot (field);
if (slot == Xapian::BAD_VALUENO) {
std::string term_prefix = _find_prefix (field.c_str ());
std::vector<std::string> terms;
for (Xapian::TermIterator it = notmuch->xapian_db->allterms_begin (term_prefix);
it != notmuch->xapian_db->allterms_end (); ++it) {
if (regexec (&regexp, (*it).c_str () + term_prefix.size (),
0, NULL, 0) == 0)
terms.push_back (*it);
}
output = Xapian::Query (Xapian::Query::OP_OR, terms.begin (), terms.end ());
} else {
RegexpPostingSource *postings = new RegexpPostingSource (slot, regexp_str);
output = Xapian::Query (postings->release ());
}
return NOTMUCH_STATUS_SUCCESS;
}
Xapian::Query
RegexpFieldProcessor::operator() (const std::string & str)
{
@ -168,23 +209,15 @@ RegexpFieldProcessor::operator() (const std::string & str)
if (str.at (0) == '/') {
if (str.length () > 1 && str.at (str.size () - 1) == '/') {
Xapian::Query query;
std::string regexp_str = str.substr (1, str.size () - 2);
if (slot != Xapian::BAD_VALUENO) {
RegexpPostingSource *postings = new RegexpPostingSource (slot, regexp_str);
return Xapian::Query (postings->release ());
} else {
std::vector<std::string> terms;
regex_t regexp;
std::string msg;
notmuch_status_t status;
compile_regex (regexp, regexp_str.c_str ());
for (Xapian::TermIterator it = notmuch->xapian_db->allterms_begin (term_prefix);
it != notmuch->xapian_db->allterms_end (); ++it) {
if (regexec (&regexp, (*it).c_str () + term_prefix.size (),
0, NULL, 0) == 0)
terms.push_back (*it);
}
return Xapian::Query (Xapian::Query::OP_OR, terms.begin (), terms.end ());
}
status = _notmuch_regexp_to_query (notmuch, slot, field, regexp_str, query, msg);
if (status)
throw Xapian::QueryParserError (msg);
return query;
} else {
throw Xapian::QueryParserError ("unmatched regex delimiter in '" + str + "'");
}

View file

@ -30,6 +30,11 @@
#include "database-private.h"
#include "notmuch-private.h"
notmuch_status_t
_notmuch_regex_to_query (notmuch_database_t *notmuch, Xapian::valueno slot, std::string field,
std::string regexp_str,
Xapian::Query &output, std::string &msg);
/* A posting source that returns documents where a value matches a
* regexp.
*/
@ -64,6 +69,7 @@ public:
class RegexpFieldProcessor : public Xapian::FieldProcessor {
protected:
Xapian::valueno slot;
std::string field;
std::string term_prefix;
notmuch_field_flag_t options;
Xapian::QueryParser &parser;

View file

@ -34,28 +34,20 @@ ThreadFieldProcessor::operator() (const std::string & str)
if (str.size () <= 1 || str.at (str.size () - 1) != '}') {
throw Xapian::QueryParserError ("missing } in '" + str + "'");
} else {
Xapian::Query subquery;
Xapian::Query query;
std::string msg;
std::string subquery_str = str.substr (1, str.size () - 2);
notmuch_query_t *subquery = notmuch_query_create (notmuch, subquery_str.c_str ());
notmuch_messages_t *messages;
std::set<std::string> terms;
if (! subquery)
throw Xapian::QueryParserError ("failed to create subquery for '" + subquery_str +
"'");
status = notmuch_query_search_messages (subquery, &messages);
status = _notmuch_query_string_to_xapian_query (notmuch, subquery_str, subquery, msg);
if (status)
throw Xapian::QueryParserError ("failed to search messages for '" + subquery_str +
"'");
throw Xapian::QueryParserError (msg);
for (; notmuch_messages_valid (messages); notmuch_messages_move_to_next (messages)) {
std::string term = thread_prefix;
notmuch_message_t *message;
message = notmuch_messages_get (messages);
term += _notmuch_message_get_thread_id_only (message);
terms.insert (term);
}
return Xapian::Query (Xapian::Query::OP_OR, terms.begin (), terms.end ());
status = _notmuch_query_expand (notmuch, "thread", subquery, query, msg);
if (status)
throw Xapian::QueryParserError (msg);
return query;
}
} else {
/* literal thread id */

View file

@ -485,11 +485,11 @@ print_status_gzbytes (const char *loc,
#include "command-line-arguments.h"
extern const char *notmuch_requested_db_uuid;
extern const notmuch_opt_desc_t notmuch_shared_options [];
void notmuch_exit_if_unmatched_db_uuid (notmuch_database_t *notmuch);
void notmuch_process_shared_options (const char *subcommand_name);
notmuch_query_syntax_t shared_option_query_syntax ();
void notmuch_process_shared_options (notmuch_database_t *notmuch, const char *subcommand_name);
int notmuch_minimal_options (const char *subcommand_name,
int argc, char **argv);

View file

@ -45,12 +45,7 @@ notmuch_compact_command (notmuch_database_t *notmuch, int argc, char *argv[])
if (opt_index < 0)
return EXIT_FAILURE;
if (notmuch_requested_db_uuid) {
fprintf (stderr, "Error: --uuid not implemented for compact\n");
return EXIT_FAILURE;
}
notmuch_process_shared_options (argv[0]);
notmuch_process_shared_options (NULL, argv[0]);
if (! quiet)
printf ("Compacting database...\n");

View file

@ -517,6 +517,7 @@ static const struct config_key
{ "index.decrypt", false, NULL },
{ "index.header.", true, validate_field_name },
{ "query.", true, NULL },
{ "squery.", true, validate_field_name },
};
static const config_key_info_t *
@ -679,6 +680,9 @@ _notmuch_config_list_built_with ()
printf ("%sretry_lock=%s\n",
BUILT_WITH_PREFIX,
notmuch_built_with ("retry_lock") ? "true" : "false");
printf ("%ssexpr_query=%s\n",
BUILT_WITH_PREFIX,
notmuch_built_with ("sexpr_query") ? "true" : "false");
}
static int
@ -708,10 +712,6 @@ notmuch_config_command (notmuch_database_t *notmuch, int argc, char *argv[])
if (opt_index < 0)
return EXIT_FAILURE;
if (notmuch_requested_db_uuid)
fprintf (stderr, "Warning: ignoring --uuid=%s\n",
notmuch_requested_db_uuid);
/* skip at least subcommand argument */
argc -= opt_index;
argv += opt_index;

View file

@ -74,10 +74,12 @@ print_count (notmuch_database_t *notmuch, const char *query_str,
int ret = 0;
notmuch_status_t status;
query = notmuch_query_create (notmuch, query_str);
if (query == NULL) {
fprintf (stderr, "Out of memory\n");
return -1;
status = notmuch_query_create_with_syntax (notmuch, query_str,
shared_option_query_syntax (),
&query);
if (print_status_database ("notmuch count", notmuch, status)) {
ret = -1;
goto DONE;
}
for (notmuch_config_values_start (exclude_tags);
@ -182,7 +184,7 @@ notmuch_count_command (notmuch_database_t *notmuch, int argc, char *argv[])
if (opt_index < 0)
return EXIT_FAILURE;
notmuch_process_shared_options (argv[0]);
notmuch_process_shared_options (notmuch, argv[0]);
if (input_file_name) {
batch = true;
@ -201,8 +203,6 @@ notmuch_count_command (notmuch_database_t *notmuch, int argc, char *argv[])
return EXIT_FAILURE;
}
notmuch_exit_if_unmatched_db_uuid (notmuch);
query_str = query_string_from_args (notmuch, argc - opt_index, argv + opt_index);
if (query_str == NULL) {
fprintf (stderr, "Out of memory.\n");

View file

@ -232,11 +232,12 @@ database_dump_file (notmuch_database_t *notmuch, gzFile output,
if (! query_str)
query_str = "";
query = notmuch_query_create (notmuch, query_str);
if (query == NULL) {
fprintf (stderr, "Out of memory\n");
status = notmuch_query_create_with_syntax (notmuch, query_str,
shared_option_query_syntax (),
&query);
if (print_status_database ("notmuch dump", notmuch, status))
return EXIT_FAILURE;
}
/* Don't ask xapian to sort by Message-ID. Xapian optimizes returning the
* first results quickly at the expense of total time.
*/
@ -366,8 +367,6 @@ notmuch_dump_command (notmuch_database_t *notmuch, int argc, char *argv[])
const char *query_str = NULL;
int ret;
notmuch_exit_if_unmatched_db_uuid (notmuch);
const char *output_file_name = NULL;
int opt_index;
@ -394,7 +393,7 @@ notmuch_dump_command (notmuch_database_t *notmuch, int argc, char *argv[])
if (opt_index < 0)
return EXIT_FAILURE;
notmuch_process_shared_options (argv[0]);
notmuch_process_shared_options (notmuch, argv[0]);
if (include == 0)
include = DUMP_INCLUDE_CONFIG | DUMP_INCLUDE_TAGS | DUMP_INCLUDE_PROPERTIES;

View file

@ -478,7 +478,7 @@ notmuch_insert_command (notmuch_database_t *notmuch, int argc, char *argv[])
if (opt_index < 0)
return EXIT_FAILURE;
notmuch_process_shared_options (argv[0]);
notmuch_process_shared_options (notmuch, argv[0]);
mail_root = notmuch_config_get (notmuch, NOTMUCH_CONFIG_MAIL_ROOT);
@ -550,8 +550,6 @@ notmuch_insert_command (notmuch_database_t *notmuch, int argc, char *argv[])
return EXIT_FAILURE;
}
notmuch_exit_if_unmatched_db_uuid (notmuch);
status = notmuch_process_shared_indexing_options (notmuch);
if (status != NOTMUCH_STATUS_SUCCESS) {
fprintf (stderr, "Error: Failed to process index options. (%s)\n",

View file

@ -1142,7 +1142,7 @@ notmuch_new_command (notmuch_database_t *notmuch, int argc, char *argv[])
if (opt_index < 0)
return EXIT_FAILURE;
notmuch_process_shared_options (argv[0]);
notmuch_process_shared_options (notmuch, argv[0]);
/* quiet trumps verbose */
if (quiet)
@ -1197,8 +1197,6 @@ notmuch_new_command (notmuch_database_t *notmuch, int argc, char *argv[])
return EXIT_FAILURE;
}
notmuch_exit_if_unmatched_db_uuid (notmuch);
if (notmuch_database_get_revision (notmuch, NULL) == 0) {
int count = 0;
count_files (mail_root, &count, &add_files_state);

View file

@ -49,11 +49,11 @@ reindex_query (notmuch_database_t *notmuch, const char *query_string,
notmuch_status_t ret = NOTMUCH_STATUS_SUCCESS;
query = notmuch_query_create (notmuch, query_string);
if (query == NULL) {
fprintf (stderr, "Out of memory.\n");
status = notmuch_query_create_with_syntax (notmuch, query_string,
shared_option_query_syntax (),
&query);
if (print_status_database ("notmuch reindex", notmuch, status))
return 1;
}
/* reindexing is not interested in any special sort order */
notmuch_query_set_sort (query, NOTMUCH_SORT_UNSORTED);
@ -108,9 +108,7 @@ notmuch_reindex_command (notmuch_database_t *notmuch, int argc, char *argv[])
if (opt_index < 0)
return EXIT_FAILURE;
notmuch_process_shared_options (argv[0]);
notmuch_exit_if_unmatched_db_uuid (notmuch);
notmuch_process_shared_options (notmuch, argv[0]);
status = notmuch_process_shared_indexing_options (notmuch);
if (status != NOTMUCH_STATUS_SUCCESS) {

View file

@ -464,8 +464,8 @@ guess_from_in_received_by (notmuch_database_t *notmuch, const char *received)
* (last Received: header added) and try to extract from them
* indications to which email address this message was delivered.
*
* The Received: header is special in our get_header function and is
* always concatenated.
* The Received: header is among special ones in our get_header function
* and is always concatenated.
*
* Return the address that was found, if any, and NULL otherwise.
*/
@ -499,6 +499,9 @@ guess_from_in_received_headers (notmuch_message_t *message)
* headers: Envelope-To, X-Original-To, and Delivered-To (searched in
* that order).
*
* The Delivered-To: header is among special ones in our get_header
* function and is always concatenated.
*
* Return the address that was found, if any, and NULL otherwise.
*/
static const char *
@ -716,6 +719,7 @@ notmuch_reply_command (notmuch_database_t *notmuch, int argc, char *argv[])
};
int format = FORMAT_DEFAULT;
int reply_all = true;
notmuch_status_t status;
notmuch_opt_desc_t options[] = {
{ .opt_keyword = &format, .name = "format", .keywords =
@ -743,7 +747,7 @@ notmuch_reply_command (notmuch_database_t *notmuch, int argc, char *argv[])
if (opt_index < 0)
return EXIT_FAILURE;
notmuch_process_shared_options (argv[0]);
notmuch_process_shared_options (notmuch, argv[0]);
notmuch_exit_if_unsupported_format ();
@ -758,13 +762,11 @@ notmuch_reply_command (notmuch_database_t *notmuch, int argc, char *argv[])
return EXIT_FAILURE;
}
notmuch_exit_if_unmatched_db_uuid (notmuch);
query = notmuch_query_create (notmuch, query_string);
if (query == NULL) {
fprintf (stderr, "Out of memory\n");
status = notmuch_query_create_with_syntax (notmuch, query_string,
shared_option_query_syntax (),
&query);
if (print_status_database ("notmuch reply", notmuch, status))
return EXIT_FAILURE;
}
if (do_reply (notmuch, query, &params, format, reply_all) != 0)
return EXIT_FAILURE;

View file

@ -272,8 +272,7 @@ notmuch_restore_command (notmuch_database_t *notmuch, int argc, char *argv[])
goto DONE;
}
notmuch_process_shared_options (argv[0]);
notmuch_exit_if_unmatched_db_uuid (notmuch);
notmuch_process_shared_options (notmuch, argv[0]);
if (include == 0) {
include = DUMP_INCLUDE_CONFIG | DUMP_INCLUDE_PROPERTIES | DUMP_INCLUDE_TAGS;

View file

@ -56,6 +56,7 @@ typedef struct {
int format_sel;
sprinter_t *format;
int exclude;
int query_syntax;
notmuch_query_t *query;
int sort;
int output;
@ -709,8 +710,6 @@ _notmuch_search_prepare (search_context_t *ctx, int argc, char *argv[])
notmuch_exit_if_unsupported_format ();
notmuch_exit_if_unmatched_db_uuid (ctx->notmuch);
query_str = query_string_from_args (ctx->notmuch, argc, argv);
if (query_str == NULL) {
fprintf (stderr, "Out of memory.\n");
@ -721,11 +720,11 @@ _notmuch_search_prepare (search_context_t *ctx, int argc, char *argv[])
return EXIT_FAILURE;
}
ctx->query = notmuch_query_create (ctx->notmuch, query_str);
if (ctx->query == NULL) {
fprintf (stderr, "Out of memory\n");
if (print_status_database ("notmuch search", ctx->notmuch,
notmuch_query_create_with_syntax (ctx->notmuch, query_str,
shared_option_query_syntax (),
&ctx->query)))
return EXIT_FAILURE;
}
notmuch_query_set_sort (ctx->query, ctx->sort);
@ -771,6 +770,7 @@ static search_context_t search_context = {
.format_sel = NOTMUCH_FORMAT_TEXT,
.exclude = NOTMUCH_EXCLUDE_TRUE,
.sort = NOTMUCH_SORT_NEWEST_FIRST,
.query_syntax = NOTMUCH_QUERY_SYNTAX_XAPIAN,
.output = 0,
.offset = 0,
.limit = -1, /* unlimited */
@ -827,7 +827,7 @@ notmuch_search_command (notmuch_database_t *notmuch, int argc, char *argv[])
if (opt_index < 0)
return EXIT_FAILURE;
notmuch_process_shared_options (argv[0]);
notmuch_process_shared_options (notmuch, argv[0]);
if (ctx->output != OUTPUT_FILES && ctx->output != OUTPUT_MESSAGES &&
ctx->dupe != -1) {
@ -893,7 +893,7 @@ notmuch_address_command (notmuch_database_t *notmuch, int argc, char *argv[])
if (opt_index < 0)
return EXIT_FAILURE;
notmuch_process_shared_options (argv[0]);
notmuch_process_shared_options (notmuch, argv[0]);
if (! (ctx->output & (OUTPUT_SENDER | OUTPUT_RECIPIENTS)))
ctx->output |= OUTPUT_SENDER;

View file

@ -147,10 +147,6 @@ notmuch_setup_command (notmuch_database_t *notmuch,
if (notmuch_minimal_options ("setup", argc, argv) < 0)
return EXIT_FAILURE;
if (notmuch_requested_db_uuid)
fprintf (stderr, "Warning: ignoring --uuid=%s\n",
notmuch_requested_db_uuid);
config = notmuch_conffile_open (notmuch,
notmuch_config_path (notmuch), true);
if (! config)

View file

@ -1287,7 +1287,7 @@ notmuch_show_command (notmuch_database_t *notmuch, int argc, char *argv[])
if (opt_index < 0)
return EXIT_FAILURE;
notmuch_process_shared_options (argv[0]);
notmuch_process_shared_options (notmuch, argv[0]);
/* explicit decryption implies verification */
if (params.crypto.decrypt == NOTMUCH_DECRYPT_NOSTASH ||
@ -1353,8 +1353,6 @@ notmuch_show_command (notmuch_database_t *notmuch, int argc, char *argv[])
}
}
notmuch_exit_if_unmatched_db_uuid (notmuch);
query_string = query_string_from_args (notmuch, argc - opt_index, argv + opt_index);
if (query_string == NULL) {
fprintf (stderr, "Out of memory\n");
@ -1366,11 +1364,11 @@ notmuch_show_command (notmuch_database_t *notmuch, int argc, char *argv[])
return EXIT_FAILURE;
}
query = notmuch_query_create (notmuch, query_string);
if (query == NULL) {
fprintf (stderr, "Out of memory\n");
status = notmuch_query_create_with_syntax (notmuch, query_string,
shared_option_query_syntax (),
&query);
if (print_status_database ("notmuch show", notmuch, status))
return EXIT_FAILURE;
}
notmuch_query_set_sort (query, sort);

View file

@ -39,8 +39,8 @@ handle_sigint (unused (int sig))
static char *
_optimize_tag_query (void *ctx, const char *orig_query_string,
const tag_op_list_t *list)
_optimize_tag_query_infix (void *ctx, const char *orig_query_string,
const tag_op_list_t *list)
{
/* This is subtler than it looks. Xapian ignores the '-' operator
* at the beginning both queries and parenthesized groups and,
@ -88,6 +88,33 @@ _optimize_tag_query (void *ctx, const char *orig_query_string,
return query_string;
}
static char *
_optimize_tag_query (void *ctx, const char *orig_query_string,
notmuch_query_syntax_t stx,
const tag_op_list_t *list)
{
char *query_string;
if (stx == NOTMUCH_QUERY_SYNTAX_XAPIAN)
return _optimize_tag_query_infix (ctx, orig_query_string, list);
/* Don't optimize if there are no tag changes. */
if (tag_op_list_size (list) == 0)
return talloc_strdup (ctx, orig_query_string);
query_string = talloc_asprintf (ctx, "(and %s", orig_query_string);
for (size_t i = 0; i < tag_op_list_size (list) && query_string; i++) {
query_string = talloc_asprintf_append_buffer (
query_string, tag_op_list_isremove (list, i) ? " (tag \"%s\")" : " (not (tag \"%s\"))",
tag_op_list_tag (list, i));
}
if (query_string)
query_string = talloc_strdup_append_buffer (query_string, ")");
return query_string;
}
/* Tag messages matching 'query_string' according to 'tag_ops'
*/
static int
@ -104,7 +131,9 @@ tag_query (void *ctx, notmuch_database_t *notmuch, const char *query_string,
if (! (flags & TAG_FLAG_REMOVE_ALL)) {
/* Optimize the query so it excludes messages that already
* have the specified set of tags. */
query_string = _optimize_tag_query (ctx, query_string, tag_ops);
query_string = _optimize_tag_query (ctx, query_string,
shared_option_query_syntax (),
tag_ops);
if (query_string == NULL) {
fprintf (stderr, "Out of memory.\n");
return 1;
@ -112,11 +141,11 @@ tag_query (void *ctx, notmuch_database_t *notmuch, const char *query_string,
flags |= TAG_FLAG_PRE_OPTIMIZED;
}
query = notmuch_query_create (notmuch, query_string);
if (query == NULL) {
fprintf (stderr, "Out of memory.\n");
status = notmuch_query_create_with_syntax (notmuch, query_string,
shared_option_query_syntax (),
&query);
if (print_status_database ("notmuch tag", notmuch, status))
return 1;
}
/* tagging is not interested in any special sort order */
notmuch_query_set_sort (query, NOTMUCH_SORT_UNSORTED);
@ -220,7 +249,7 @@ notmuch_tag_command (notmuch_database_t *notmuch, int argc, char *argv[])
if (opt_index < 0)
return EXIT_FAILURE;
notmuch_process_shared_options (argv[0]);
notmuch_process_shared_options (notmuch, argv[0]);
if (input_file_name) {
batch = true;
@ -261,8 +290,6 @@ notmuch_tag_command (notmuch_database_t *notmuch, int argc, char *argv[])
}
}
notmuch_exit_if_unmatched_db_uuid (notmuch);
if (print_status_database (
"notmuch restore",
notmuch,

View file

@ -49,22 +49,38 @@ notmuch_command (notmuch_database_t *notmuch, int argc, char *argv[]);
static int
_help_for (const char *topic);
static void
notmuch_exit_if_unmatched_db_uuid (notmuch_database_t *notmuch);
static bool print_version = false, print_help = false;
const char *notmuch_requested_db_uuid = NULL;
static const char *notmuch_requested_db_uuid = NULL;
static int query_syntax = NOTMUCH_QUERY_SYNTAX_XAPIAN;
const notmuch_opt_desc_t notmuch_shared_options [] = {
{ .opt_bool = &print_version, .name = "version" },
{ .opt_bool = &print_help, .name = "help" },
{ .opt_string = &notmuch_requested_db_uuid, .name = "uuid" },
{ .opt_keyword = &query_syntax, .name = "query", .keywords =
(notmuch_keyword_t []){ { "infix", NOTMUCH_QUERY_SYNTAX_XAPIAN },
{ "sexp", NOTMUCH_QUERY_SYNTAX_SEXP },
{ 0, 0 } } },
{ }
};
notmuch_query_syntax_t
shared_option_query_syntax ()
{
return query_syntax;
}
/* any subcommand wanting to support these options should call
* inherit notmuch_shared_options and call
* notmuch_process_shared_options (subcommand_name);
* notmuch_process_shared_options (notmuch, subcommand_name);
* with notmuch = an open database, or NULL.
*/
void
notmuch_process_shared_options (const char *subcommand_name)
notmuch_process_shared_options (notmuch_database_t *notmuch, const char *subcommand_name)
{
if (print_version) {
printf ("notmuch " STRINGIFY (NOTMUCH_VERSION) "\n");
@ -75,6 +91,14 @@ notmuch_process_shared_options (const char *subcommand_name)
int ret = _help_for (subcommand_name);
exit (ret);
}
if (notmuch) {
notmuch_exit_if_unmatched_db_uuid (notmuch);
} else {
if (notmuch_requested_db_uuid)
fprintf (stderr, "Warning: ignoring --uuid=%s\n",
notmuch_requested_db_uuid);
}
}
/* This is suitable for subcommands that do not actually open the
@ -97,7 +121,7 @@ notmuch_minimal_options (const char *subcommand_name,
return -1;
/* We can't use argv here as it is sometimes NULL */
notmuch_process_shared_options (subcommand_name);
notmuch_process_shared_options (NULL, subcommand_name);
return opt_index;
}
@ -280,7 +304,7 @@ be supported in the future.\n", notmuch_format_version);
}
}
void
static void
notmuch_exit_if_unmatched_db_uuid (notmuch_database_t *notmuch)
{
const char *uuid = NULL;
@ -480,7 +504,7 @@ main (int argc, char *argv[])
if (opt_index < argc)
command_name = argv[opt_index];
notmuch_process_shared_options (command_name);
notmuch_process_shared_options (NULL, command_name);
command = find_command (command_name);
/* if command->function is NULL, try external command */

View file

@ -51,6 +51,7 @@ cat <<EOF > EXPECTED
built_with.compact=something
built_with.field_processor=something
built_with.retry_lock=something
built_with.sexpr_query=something
database.autocommit=8000
database.mail_root=MAIL_DIR
database.path=MAIL_DIR

View file

@ -266,7 +266,7 @@ EOF
test_expect_equal "${output}+${output2}" "${value}+"
test_begin_subtest "Config list ($config)"
notmuch config list | notmuch_dir_sanitize | \
notmuch config list | notmuch_config_sanitize | \
sed -e "s/^database.backup_dir=.*$/database.backup_dir/" \
-e "s/^database.hook_dir=.*$/database.hook_dir/" \
-e "s/^database.path=.*$/database.path/" \
@ -274,9 +274,10 @@ EOF
-e "s,^database.mail_root=CWD/home/env_points_here,database.mail_root=MAIL_DIR," \
> OUTPUT
cat <<EOF > EXPECTED
built_with.compact=true
built_with.field_processor=true
built_with.retry_lock=true
built_with.compact=something
built_with.field_processor=something
built_with.retry_lock=something
built_with.sexpr_query=something
database.autocommit=8000
database.backup_dir
database.hook_dir
@ -305,7 +306,23 @@ EOF
output2=$(notmuch --config='' config get ${key})
notmuch config set ${key}
test_expect_equal "${output}+${output2}" "${value}+"
;;
;&
split)
test_begin_subtest "'to' header does not crash (python-cffi) ($config)"
echo 'notmuch@notmuchmail.org' > EXPECTED
test_python <<EOF
from notmuch2 import Database
db=Database(config=Database.CONFIG.SEARCH)
m=db.find('20091117232137.GA7669@griffis1.net')
to=m.header('To')
print(to)
EOF
test_expect_equal_file EXPECTED OUTPUT
;& # fall through
esac
case $config in
split|XDG*)
esac
restore_config
rm -rf home/.local

View file

@ -154,4 +154,28 @@ print("4: {} messages".format(query.count_messages()))
EOF
test_expect_equal_file EXPECTED OUTPUT
if [ $NOTMUCH_HAVE_SFSEXP -eq 1 ]; then
test_begin_subtest "and of exact terms (query=sexp)"
output=$(notmuch count --query=sexp '(and "wonderful" "wizard")')
test_expect_equal "$output" 1
test_begin_subtest "or of exact terms (query=sexp)"
output=$(notmuch count --query=sexp '(or "php" "wizard")')
test_expect_equal "$output" 2
test_begin_subtest "starts-with, case-insensitive (query=sexp)"
output=$(notmuch count --query=sexp '(starts-with FreeB)')
test_expect_equal "$output" 5
test_begin_subtest "query that matches no messages (query=sexp)"
count=$(notmuch count --query=sexp '(and (from keithp) (to keithp))')
test_expect_equal 0 "$count"
test_begin_subtest "Compound subquery (query=sexp)"
output=$(notmuch count --query=sexp '(thread (of (from keithp) (subject Maildir)))')
test_expect_equal "$output" 7
fi
test_done

986
test/T081-sexpr-search.sh Executable file
View file

@ -0,0 +1,986 @@
#!/usr/bin/env bash
test_description='"notmuch search" in several variations'
. $(dirname "$0")/test-lib.sh || exit 1
if [ $NOTMUCH_HAVE_SFSEXP -ne 1 ]; then
printf "Skipping due to missing sfsexp library\n"
test_done
fi
add_email_corpus
for query in '()' '(not)' '(and)' '(or ())' '(or (not))' '(or (and))' \
'(or (and) (or) (not (and)))'; do
test_begin_subtest "all messages: $query"
notmuch search '*' > EXPECTED
notmuch search --query=sexp "$query" > OUTPUT
test_expect_equal_file EXPECTED OUTPUT
done
for query in '(or)' '(not ())' '(not (not))' '(not (and))' \
'(not (or (and) (or) (not (and))))'; do
test_begin_subtest "no messages: $query"
notmuch search --query=sexp "$query" > OUTPUT
test_expect_equal_file /dev/null OUTPUT
done
test_begin_subtest "and of exact terms"
notmuch search --query=sexp '(and "wonderful" "wizard")' | notmuch_search_sanitize > OUTPUT
cat <<EOF > EXPECTED
thread:XXX 2009-11-18 [1/3] Carl Worth| Jan Janak; [notmuch] What a great idea! (inbox unread)
EOF
test_expect_equal_file EXPECTED OUTPUT
test_begin_subtest "or of exact terms"
notmuch search --query=sexp '(or "php" "wizard")' | notmuch_search_sanitize > OUTPUT
cat <<EOF > EXPECTED
thread:XXX 2010-12-29 [1/1] François Boulogne; [aur-general] Guidelines: cp, mkdir vs install (inbox unread)
thread:XXX 2009-11-18 [1/3] Carl Worth| Jan Janak; [notmuch] What a great idea! (inbox unread)
EOF
test_expect_equal_file EXPECTED OUTPUT
test_begin_subtest "single term in body"
notmuch search --query=sexp 'wizard' | notmuch_search_sanitize>OUTPUT
cat <<EOF > EXPECTED
thread:XXX 2009-11-18 [1/3] Carl Worth| Jan Janak; [notmuch] What a great idea! (inbox unread)
EOF
test_expect_equal_file EXPECTED OUTPUT
test_begin_subtest "single term in body (case insensitive)"
notmuch search --query=sexp 'Wizard' | notmuch_search_sanitize>OUTPUT
cat <<EOF > EXPECTED
thread:XXX 2009-11-18 [1/3] Carl Worth| Jan Janak; [notmuch] What a great idea! (inbox unread)
EOF
test_expect_equal_file EXPECTED OUTPUT
test_begin_subtest "single term in body, stemmed version"
notmuch search arriv > EXPECTED
notmuch search --query=sexp arriv > OUTPUT
test_expect_equal_file EXPECTED OUTPUT
test_begin_subtest "single term in body, unstemmed version"
notmuch search --query=sexp '"arriv"' > OUTPUT
test_expect_equal_file /dev/null OUTPUT
test_begin_subtest "Search by 'subject'"
add_message [subject]=subjectsearchtest '[date]="Sat, 01 Jan 2000 12:00:00 -0000"'
output=$(notmuch search --query=sexp '(subject subjectsearchtest)' | notmuch_search_sanitize)
test_expect_equal "$output" "thread:XXX 2000-01-01 [1/1] Notmuch Test Suite; subjectsearchtest (inbox unread)"
test_begin_subtest "Search by 'subject' (case insensitive)"
notmuch search tag:inbox and subject:maildir | notmuch_search_sanitize > EXPECTED
notmuch search --query=sexp '(subject "Maildir")' | notmuch_search_sanitize > OUTPUT
test_expect_equal_file EXPECTED OUTPUT
test_begin_subtest "Search by 'subject' (utf-8):"
add_message [subject]=utf8-sübjéct '[date]="Sat, 01 Jan 2000 12:00:00 -0000"'
output=$(notmuch search --query=sexp '(subject utf8 sübjéct)' | notmuch_search_sanitize)
test_expect_equal "$output" "thread:XXX 2000-01-01 [1/1] Notmuch Test Suite; utf8-sübjéct (inbox unread)"
test_begin_subtest "Search by 'subject' (utf-8, and):"
output=$(notmuch search --query=sexp '(subject (and utf8 sübjéct))' | notmuch_search_sanitize)
test_expect_equal "$output" "thread:XXX 2000-01-01 [1/1] Notmuch Test Suite; utf8-sübjéct (inbox unread)"
test_begin_subtest "Search by 'subject' (utf-8, and outside):"
output=$(notmuch search --query=sexp '(and (subject utf8) (subject sübjéct))' | notmuch_search_sanitize)
test_expect_equal "$output" "thread:XXX 2000-01-01 [1/1] Notmuch Test Suite; utf8-sübjéct (inbox unread)"
test_begin_subtest "Search by 'subject' (utf-8, or):"
notmuch search --query=sexp '(subject (or utf8 subjectsearchtest))' | notmuch_search_sanitize > OUTPUT
cat <<EOF > EXPECTED
thread:XXX 2000-01-01 [1/1] Notmuch Test Suite; subjectsearchtest (inbox unread)
thread:XXX 2000-01-01 [1/1] Notmuch Test Suite; utf8-sübjéct (inbox unread)
EOF
test_expect_equal_file EXPECTED OUTPUT
test_begin_subtest "Search by 'subject' (utf-8, or outside):"
notmuch search --query=sexp '(or (subject utf8) (subject subjectsearchtest))' | notmuch_search_sanitize > OUTPUT
cat <<EOF > EXPECTED
thread:XXX 2000-01-01 [1/1] Notmuch Test Suite; subjectsearchtest (inbox unread)
thread:XXX 2000-01-01 [1/1] Notmuch Test Suite; utf8-sübjéct (inbox unread)
EOF
test_expect_equal_file EXPECTED OUTPUT
test_begin_subtest "Search by 'attachment'"
notmuch search attachment:notmuch-help.patch > EXPECTED
notmuch search --query=sexp '(attachment notmuch-help.patch)' > OUTPUT
test_expect_equal_file EXPECTED OUTPUT
test_begin_subtest "Search by 'body'"
add_message '[subject]="body search"' '[date]="Sat, 01 Jan 2000 12:00:00 -0000"' [body]=bodysearchtest
output=$(notmuch search --query=sexp '(body bodysearchtest)' | notmuch_search_sanitize)
test_expect_equal "$output" "thread:XXX 2000-01-01 [1/1] Notmuch Test Suite; body search (inbox unread)"
test_begin_subtest "Search by 'body' (phrase)"
add_message '[subject]="body search (phrase)"' '[date]="Sat, 01 Jan 2000 12:00:00 -0000"' '[body]="body search (phrase)"'
add_message '[subject]="negative result"' '[date]="Sat, 01 Jan 2000 12:00:00 -0000"' '[body]="This phrase should not match the body search"'
output=$(notmuch search --query=sexp '(body "body search phrase")' | notmuch_search_sanitize)
test_expect_equal "$output" "thread:XXX 2000-01-01 [1/1] Notmuch Test Suite; body search (phrase) (inbox unread)"
test_begin_subtest "Search by 'body' (utf-8):"
add_message '[subject]="utf8-message-body-subject"' '[date]="Sat, 01 Jan 2000 12:00:00 -0000"' '[body]="message body utf8: bödý"'
output=$(notmuch search --query=sexp '(body bödý)' | notmuch_search_sanitize)
test_expect_equal "$output" "thread:XXX 2000-01-01 [1/1] Notmuch Test Suite; utf8-message-body-subject (inbox unread)"
add_message "[body]=thebody-1" "[subject]=kryptonite-1"
add_message "[body]=nothing-to-see-here-1" "[subject]=thebody-1"
test_begin_subtest 'search without body: prefix'
notmuch search thebody > EXPECTED
notmuch search --query=sexp '(and thebody)' > OUTPUT
test_expect_equal_file EXPECTED OUTPUT
test_begin_subtest 'negated body: prefix'
notmuch search thebody and not body:thebody > EXPECTED
notmuch search --query=sexp '(and (not (body thebody)) thebody)' > OUTPUT
test_expect_equal_file EXPECTED OUTPUT
test_begin_subtest 'search unprefixed for prefixed term'
notmuch search kryptonite > EXPECTED
notmuch search --query=sexp '(and kryptonite)' > OUTPUT
test_expect_equal_file EXPECTED OUTPUT
test_begin_subtest 'search with body: prefix for term only in subject'
notmuch search body:kryptonite > EXPECTED
notmuch search --query=sexp '(body kryptonite)' > OUTPUT
test_expect_equal_file EXPECTED OUTPUT
test_begin_subtest "Search by 'from'"
add_message '[subject]="search by from"' '[date]="Sat, 01 Jan 2000 12:00:00 -0000"' [from]=searchbyfrom
output=$(notmuch search --query=sexp '(from searchbyfrom)' | notmuch_search_sanitize)
test_expect_equal "$output" "thread:XXX 2000-01-01 [1/1] searchbyfrom; search by from (inbox unread)"
test_begin_subtest "Search by 'from' (address)"
add_message '[subject]="search by from (address)"' '[date]="Sat, 01 Jan 2000 12:00:00 -0000"' [from]=searchbyfrom@example.com
output=$(notmuch search --query=sexp '(from searchbyfrom@example.com)' | notmuch_search_sanitize)
test_expect_equal "$output" "thread:XXX 2000-01-01 [1/1] searchbyfrom@example.com; search by from (address) (inbox unread)"
test_begin_subtest "Search by 'from' (name)"
add_message '[subject]="search by from (name)"' '[date]="Sat, 01 Jan 2000 12:00:00 -0000"' '[from]="Search By From Name <test@example.com>"'
output=$(notmuch search --query=sexp '(from "Search By From Name")' | notmuch_search_sanitize)
test_expect_equal "$output" "thread:XXX 2000-01-01 [1/1] Search By From Name; search by from (name) (inbox unread)"
test_begin_subtest "Search by 'from' (name and address)"
output=$(notmuch search --query=sexp '(from "Search By From Name <test@example.com>")' | notmuch_search_sanitize)
test_expect_equal "$output" "thread:XXX 2000-01-01 [1/1] Search By From Name; search by from (name) (inbox unread)"
add_message '[dir]=bad' '[subject]="To the bone"'
add_message '[dir]=.' '[subject]="Top level"'
add_message '[dir]=bad/news' '[subject]="Bears"'
mkdir -p "${MAIL_DIR}/duplicate/bad/news"
cp "$gen_msg_filename" "${MAIL_DIR}/duplicate/bad/news"
add_message '[dir]=things' '[subject]="These are a few"'
add_message '[dir]=things/favorite' '[subject]="Raindrops, whiskers, kettles"'
add_message '[dir]=things/bad' '[subject]="Bites, stings, sad feelings"'
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': top level."
notmuch search folder:'""' > EXPECTED
notmuch search --query=sexp '(folder "")' > OUTPUT
test_expect_equal_file EXPECTED OUTPUT
test_begin_subtest "Search by 'id'"
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)
test_expect_equal "$output" "thread:XXX 2000-01-01 [1/1] Notmuch Test Suite; search by id (inbox unread)"
test_begin_subtest "Search by 'id' (or)"
add_message '[subject]="search by id"' '[date]="Sat, 01 Jan 2000 12:00:00 -0000"'
output=$(notmuch search --query=sexp "(id non-existent-mid ${gen_msg_id})" | notmuch_search_sanitize)
test_expect_equal "$output" "thread:XXX 2000-01-01 [1/1] Notmuch Test Suite; search by id (inbox unread)"
test_begin_subtest "Search by 'is' (multiple)"
notmuch tag -inbox tag:searchbytag
notmuch search is:inbox AND is:unread | notmuch_search_sanitize > EXPECTED
notmuch search --query=sexp '(is inbox unread)' | notmuch_search_sanitize > OUTPUT
notmuch tag +inbox tag:searchbytag
test_expect_equal_file EXPECTED OUTPUT
test_begin_subtest "Search by 'mid'"
add_message '[subject]="search by mid"' '[date]="Sat, 01 Jan 2000 12:00:00 -0000"'
output=$(notmuch search --query=sexp "(mid ${gen_msg_id})" | notmuch_search_sanitize)
test_expect_equal "$output" "thread:XXX 2000-01-01 [1/1] Notmuch Test Suite; search by mid (inbox unread)"
test_begin_subtest "Search by 'mid' (or)"
add_message '[subject]="search by mid"' '[date]="Sat, 01 Jan 2000 12:00:00 -0000"'
output=$(notmuch search --query=sexp "(mid non-existent-mid ${gen_msg_id})" | notmuch_search_sanitize)
test_expect_equal "$output" "thread:XXX 2000-01-01 [1/1] Notmuch Test Suite; search by mid (inbox unread)"
test_begin_subtest "Search by 'mimetype'"
notmuch search mimetype:text/html > EXPECTED
notmuch search --query=sexp '(mimetype text html)' > OUTPUT
test_expect_equal_file EXPECTED OUTPUT
QUERYSTR="date:2009-11-18..2009-11-18 and tag:unread"
QUERYSTR2="query:test and subject:Maildir"
notmuch config set --database query.test "$QUERYSTR"
notmuch config set query.test2 "$QUERYSTR2"
test_begin_subtest "ill-formed named query search"
notmuch search --query=sexp '(query)' > OUTPUT 2>&1
cat <<EOF > EXPECTED
notmuch search: Syntax error in query
'query' expects single atom as argument
EOF
test_expect_equal_file EXPECTED OUTPUT
test_begin_subtest "ill-formed named query search 2"
notmuch search --query=sexp '(to (query))' > OUTPUT 2>&1
cat <<EOF > EXPECTED
notmuch search: Syntax error in query
'query' not supported inside 'to'
EOF
test_expect_equal_file EXPECTED OUTPUT
test_begin_subtest "search named query"
notmuch search --query=sexp '(query test)' > OUTPUT
notmuch search $QUERYSTR > EXPECTED
test_expect_equal_file EXPECTED OUTPUT
test_begin_subtest "Search by 'subject' (utf-8, phrase-token):"
output=$(notmuch search --query=sexp '(subject utf8-sübjéct)' | notmuch_search_sanitize)
test_expect_equal "$output" "thread:XXX 2000-01-01 [1/1] Notmuch Test Suite; utf8-sübjéct (inbox unread)"
test_begin_subtest "search named query with other terms"
notmuch search --query=sexp '(and (query test) (subject Maildir))' > OUTPUT
notmuch search $QUERYSTR and subject:Maildir > EXPECTED
test_expect_equal_file EXPECTED OUTPUT
test_begin_subtest "search nested named query"
notmuch search --query=sexp '(query test2)' > OUTPUT
notmuch search $QUERYSTR2 > EXPECTED
test_expect_equal_file EXPECTED OUTPUT
test_begin_subtest "Search by 'subject' (utf-8, quoted string):"
output=$(notmuch search --query=sexp '(subject "utf8 sübjéct")' | notmuch_search_sanitize)
test_expect_equal "$output" "thread:XXX 2000-01-01 [1/1] Notmuch Test Suite; utf8-sübjéct (inbox unread)"
test_begin_subtest "Search by 'subject' (combine phrase, term):"
output=$(notmuch search --query=sexp '(subject Mac "compatibility issues")' | notmuch_search_sanitize)
test_expect_equal "$output" "thread:XXX 2009-11-18 [4/4] Jjgod Jiang, Alexander Botero-Lowry; [notmuch] Mac OS X/Darwin compatibility issues (inbox unread)"
test_begin_subtest "Search by 'subject' (combine phrase, term 2):"
notmuch search --query=sexp '(subject (or utf8 "compatibility issues"))' | notmuch_search_sanitize > OUTPUT
cat <<EOF > EXPECTED
thread:XXX 2009-11-18 [4/4] Jjgod Jiang, Alexander Botero-Lowry; [notmuch] Mac OS X/Darwin compatibility issues (inbox unread)
thread:XXX 2000-01-01 [1/1] Notmuch Test Suite; utf8-sübjéct (inbox unread)
thread:XXX 2000-01-01 [1/1] Notmuch Test Suite; utf8-message-body-subject (inbox unread)
EOF
test_expect_equal_file EXPECTED OUTPUT
test_begin_subtest "Search by 'subject' (combine phrase, term 3):"
notmuch search --query=sexp '(subject issues X/Darwin)' | notmuch_search_sanitize > OUTPUT
cat <<EOF > EXPECTED
thread:XXX 2009-11-18 [4/4] Jjgod Jiang, Alexander Botero-Lowry; [notmuch] Mac OS X/Darwin compatibility issues (inbox unread)
EOF
test_expect_equal_file EXPECTED OUTPUT
test_begin_subtest "Search by 'tag'"
add_message '[subject]="search by tag"' '[date]="Sat, 01 Jan 2000 12:00:00 -0000"'
notmuch tag +searchbytag id:${gen_msg_id}
output=$(notmuch search --query=sexp '(tag searchbytag)' | notmuch_search_sanitize)
test_expect_equal "$output" "thread:XXX 2000-01-01 [1/1] Notmuch Test Suite; search by tag (inbox searchbytag unread)"
test_begin_subtest "Search by 'tag' (multiple)"
notmuch tag -inbox tag:searchbytag
notmuch search tag:inbox AND tag:unread | notmuch_search_sanitize > EXPECTED
notmuch search --query=sexp '(tag inbox unread)' | notmuch_search_sanitize > OUTPUT
notmuch tag +inbox tag:searchbytag
test_expect_equal_file EXPECTED OUTPUT
test_begin_subtest "Search by 'tag' and 'subject'"
notmuch search tag:inbox and subject:maildir | notmuch_search_sanitize > EXPECTED
notmuch search --query=sexp '(and (tag inbox) (subject maildir))' | notmuch_search_sanitize > OUTPUT
test_expect_equal_file EXPECTED OUTPUT
test_begin_subtest "Search by 'thread'"
add_message '[subject]="search by thread"' '[date]="Sat, 01 Jan 2000 12:00:00 -0000"'
thread_id=$(notmuch search id:${gen_msg_id} | sed -e "s/thread:\([a-f0-9]*\).*/\1/")
output=$(notmuch search --query=sexp "(thread ${thread_id})" | notmuch_search_sanitize)
test_expect_equal "$output" "thread:XXX 2000-01-01 [1/1] Notmuch Test Suite; search by thread (inbox unread)"
test_begin_subtest "Search by 'to'"
add_message '[subject]="search by to"' '[date]="Sat, 01 Jan 2000 12:00:00 -0000"' [to]=searchbyto
output=$(notmuch search --query=sexp '(to searchbyto)' | notmuch_search_sanitize)
test_expect_equal "$output" "thread:XXX 2000-01-01 [1/1] Notmuch Test Suite; search by to (inbox unread)"
test_begin_subtest "Search by 'to' (address)"
add_message '[subject]="search by to (address)"' '[date]="Sat, 01 Jan 2000 12:00:00 -0000"' [to]=searchbyto@example.com
output=$(notmuch search --query=sexp '(to searchbyto@example.com)' | notmuch_search_sanitize)
test_expect_equal "$output" "thread:XXX 2000-01-01 [1/1] Notmuch Test Suite; search by to (address) (inbox unread)"
test_begin_subtest "Search by 'to' (name)"
add_message '[subject]="search by to (name)"' '[date]="Sat, 01 Jan 2000 12:00:00 -0000"' '[to]="Search By To Name <test@example.com>"'
output=$(notmuch search --query=sexp '(to "Search By To Name")' | notmuch_search_sanitize)
test_expect_equal "$output" "thread:XXX 2000-01-01 [1/1] Notmuch Test Suite; search by to (name) (inbox unread)"
test_begin_subtest "Search by 'to' (name and address)"
output=$(notmuch search --query=sexp '(to "Search By To Name <test@example.com>")' | notmuch_search_sanitize)
test_expect_equal "$output" "thread:XXX 2000-01-01 [1/1] Notmuch Test Suite; search by to (name) (inbox unread)"
test_begin_subtest "starts-with, no prefix"
output=$(notmuch search --query=sexp '(starts-with prelim)' | notmuch_search_sanitize)
test_expect_equal "$output" "thread:XXX 2009-11-17 [2/2] Alex Botero-Lowry, Carl Worth; [notmuch] preliminary FreeBSD support (attachment inbox unread)"
test_begin_subtest "starts-with, case-insensitive"
notmuch search --query=sexp '(starts-with FreeB)' | notmuch_search_sanitize > OUTPUT
cat <<EOF > EXPECTED
thread:XXX 2009-11-18 [3/4] Alexander Botero-Lowry, Jjgod Jiang; [notmuch] Mac OS X/Darwin compatibility issues (inbox unread)
thread:XXX 2009-11-17 [2/2] Alex Botero-Lowry, Carl Worth; [notmuch] preliminary FreeBSD support (attachment inbox unread)
EOF
test_expect_equal_file EXPECTED OUTPUT
test_begin_subtest "starts-with, no prefix, all messages"
notmuch search --query=sexp '(starts-with "")' | notmuch_search_sanitize > OUTPUT
notmuch search '*' | notmuch_search_sanitize > EXPECTED
test_expect_equal_file EXPECTED OUTPUT
test_begin_subtest "starts-with, attachment"
output=$(notmuch search --query=sexp '(attachment (starts-with not))' | notmuch_search_sanitize)
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"
notmuch search --output=files --query=sexp '(folder (starts-with bad))' | notmuch_dir_sanitize | sed 's/[0-9]*$/XXX/' > OUTPUT
cat <<EOF > EXPECTED
MAIL_DIR/bad/msg-XXX
MAIL_DIR/bad/news/msg-XXX
MAIL_DIR/duplicate/bad/news/msg-XXX
EOF
test_expect_equal_file EXPECTED OUTPUT
test_begin_subtest "starts-with, from"
notmuch search --query=sexp '(from (starts-with Mik))' | notmuch_search_sanitize > OUTPUT
cat <<EOF > EXPECTED
thread:XXX 2009-11-17 [1/1] Mikhail Gusarov; [notmuch] [PATCH] Handle rename of message file (inbox unread)
thread:XXX 2009-11-17 [2/7] Mikhail Gusarov| Lars Kellogg-Stedman, Keith Packard, Carl Worth; [notmuch] Working with Maildir storage? (inbox signed unread)
thread:XXX 2009-11-17 [2/5] Mikhail Gusarov| Carl Worth, Keith Packard; [notmuch] [PATCH 2/2] Include <stdint.h> to get uint32_t in C++ file with gcc 4.4 (inbox unread)
EOF
test_expect_equal_file EXPECTED OUTPUT
test_begin_subtest "starts-with, id"
notmuch search --query=sexp --output=messages '(id (starts-with 877))' > OUTPUT
cat <<EOF > EXPECTED
id:877h1wv7mg.fsf@inf-8657.int-evry.fr
id:877htoqdbo.fsf@yoom.home.cworth.org
EOF
test_expect_equal_file EXPECTED OUTPUT
test_begin_subtest "starts-with, is"
output=$(notmuch search --query=sexp '(is (starts-with searchby))' | notmuch_search_sanitize)
test_expect_equal "$output" 'thread:XXX 2000-01-01 [1/1] Notmuch Test Suite; search by tag (inbox searchbytag unread)'
test_begin_subtest "starts-with, mid"
notmuch search --query=sexp --output=messages '(mid (starts-with 877))' > OUTPUT
cat <<EOF > EXPECTED
id:877h1wv7mg.fsf@inf-8657.int-evry.fr
id:877htoqdbo.fsf@yoom.home.cworth.org
EOF
test_expect_equal_file EXPECTED OUTPUT
test_begin_subtest "starts-with, mimetype"
notmuch search --query=sexp '(mimetype (starts-with sig))' | notmuch_search_sanitize > OUTPUT
cat <<EOF > EXPECTED
thread:XXX 2009-11-18 [2/2] Lars Kellogg-Stedman; [notmuch] "notmuch help" outputs to stderr? (attachment inbox signed unread)
thread:XXX 2009-11-18 [4/7] Lars Kellogg-Stedman, Mikhail Gusarov| Keith Packard, Carl Worth; [notmuch] Working with Maildir storage? (inbox signed unread)
thread:XXX 2009-11-17 [1/3] Adrian Perez de Castro| Keith Packard, Carl Worth; [notmuch] Introducing myself (inbox signed unread)
EOF
test_expect_equal_file EXPECTED OUTPUT
add_message '[subject]="message with properties"'
notmuch restore <<EOF
#= ${gen_msg_id} foo=bar
EOF
test_begin_subtest "starts-with, property"
notmuch search --query=sexp '(property (starts-with foo=))' | notmuch_search_sanitize > OUTPUT
cat <<EOF > EXPECTED
thread:XXX 2001-01-05 [1/1] Notmuch Test Suite; message with properties (inbox unread)
EOF
test_expect_equal_file EXPECTED OUTPUT
test_begin_subtest "starts-with, subject"
notmuch search --query=sexp '(subject (starts-with FreeB))' | notmuch_search_sanitize > OUTPUT
cat <<EOF > EXPECTED
thread:XXX 2009-11-17 [2/2] Alex Botero-Lowry, Carl Worth; [notmuch] preliminary FreeBSD support (attachment inbox unread)
EOF
test_expect_equal_file EXPECTED OUTPUT
test_begin_subtest "starts-with, tag"
output=$(notmuch search --query=sexp '(tag (starts-with searchby))' | notmuch_search_sanitize)
test_expect_equal "$output" 'thread:XXX 2000-01-01 [1/1] Notmuch Test Suite; search by tag (inbox searchbytag unread)'
add_message '[subject]="no tags"'
notag_mid=${gen_msg_id}
notmuch tag -unread -inbox id:${notag_mid}
test_begin_subtest "negated starts-with, tag"
output=$(notmuch search --query=sexp '(tag (not (starts-with in)))' | notmuch_search_sanitize)
test_expect_equal "$output" 'thread:XXX 2001-01-05 [1/1] Notmuch Test Suite; no tags ()'
test_begin_subtest "negated starts-with, tag 2"
output=$(notmuch search --query=sexp '(not (tag (starts-with in)))' | notmuch_search_sanitize)
test_expect_equal "$output" 'thread:XXX 2001-01-05 [1/1] Notmuch Test Suite; no tags ()'
test_begin_subtest "negated starts-with, tag 3"
output=$(notmuch search --query=sexp '(not (tag (starts-with "")))' | notmuch_search_sanitize)
test_expect_equal "$output" 'thread:XXX 2001-01-05 [1/1] Notmuch Test Suite; no tags ()'
test_begin_subtest "starts-with, thread"
notmuch search --query=sexp '(thread (starts-with "00"))' > OUTPUT
notmuch search '*' > EXPECTED
test_expect_equal_file EXPECTED OUTPUT
test_begin_subtest "starts-with, to"
notmuch search --query=sexp '(to (starts-with "search"))' | notmuch_search_sanitize > OUTPUT
cat <<EOF > EXPECTED
thread:XXX 2000-01-01 [1/1] Notmuch Test Suite; search by to (inbox unread)
thread:XXX 2000-01-01 [1/1] Notmuch Test Suite; search by to (address) (inbox unread)
thread:XXX 2000-01-01 [1/1] Notmuch Test Suite; search by to (name) (inbox unread)
EOF
test_expect_equal_file EXPECTED OUTPUT
test_begin_subtest "wildcard search for 'is'"
notmuch search not id:${notag_mid} > EXPECTED
notmuch search --query=sexp '(is *)' > OUTPUT
test_expect_equal_file EXPECTED OUTPUT
test_begin_subtest "negated wildcard search for 'is'"
notmuch search id:${notag_mid} > EXPECTED
notmuch search --query=sexp '(not (is *))' > OUTPUT
test_expect_equal_file EXPECTED OUTPUT
test_begin_subtest "wildcard search for 'property'"
notmuch search property:foo=bar > EXPECTED
notmuch search --query=sexp '(property *)' > OUTPUT
test_expect_equal_file EXPECTED OUTPUT
test_begin_subtest "wildcard search for 'tag'"
notmuch search not id:${notag_mid} > EXPECTED
notmuch search --query=sexp '(tag *)' > OUTPUT
test_expect_equal_file EXPECTED OUTPUT
test_begin_subtest "negated wildcard search for 'tag'"
notmuch search id:${notag_mid} > EXPECTED
notmuch search --query=sexp '(not (tag *))' > OUTPUT
test_expect_equal_file EXPECTED OUTPUT
add_message '[subject]="message with tag \"*\""'
notmuch tag '+*' id:${gen_msg_id}
test_begin_subtest "search for 'tag' \"*\""
output=$(notmuch search --query=sexp --output=messages '(tag "*")')
test_expect_equal "$output" "id:$gen_msg_id"
test_begin_subtest "search for missing / empty to"
add_message [to]="undisclosed-recipients:"
notmuch search --query=sexp '(not (to *))' | notmuch_search_sanitize > OUTPUT
cat <<EOF > EXPECTED
thread:XXX 2001-01-05 [1/1] Notmuch Test Suite; search for missing / empty to (inbox unread)
EOF
test_expect_equal_file EXPECTED OUTPUT
test_begin_subtest "Unbalanced parens"
# A code 1 indicates the error was handled (a crash will return e.g. 139).
test_expect_code 1 "notmuch search --query=sexp '('"
test_begin_subtest "Unbalanced parens, error message"
notmuch search --query=sexp '(' >OUTPUT 2>&1
cat <<EOF > EXPECTED
notmuch search: Syntax error in query
invalid s-expression: '('
EOF
test_expect_equal_file EXPECTED OUTPUT
test_begin_subtest "unknown prefix"
notmuch search --query=sexp '(foo)' >OUTPUT 2>&1
cat <<EOF > EXPECTED
notmuch search: Syntax error in query
unknown prefix 'foo'
EOF
test_expect_equal_file EXPECTED OUTPUT
test_begin_subtest "list as prefix"
notmuch search --query=sexp '((foo))' >OUTPUT 2>&1
cat <<EOF > EXPECTED
notmuch search: Syntax error in query
unexpected list in field/operation position
EOF
test_expect_equal_file EXPECTED OUTPUT
test_begin_subtest "illegal nesting"
notmuch search --query=sexp '(subject (subject foo))' >OUTPUT 2>&1
cat <<EOF > EXPECTED
notmuch search: Syntax error in query
nested field: 'subject' inside 'subject'
EOF
test_expect_equal_file EXPECTED OUTPUT
test_begin_subtest "starts-with, no argument"
notmuch search --query=sexp '(starts-with)' >OUTPUT 2>&1
cat <<EOF > EXPECTED
notmuch search: Syntax error in query
'starts-with' expects single atom as argument
EOF
test_expect_equal_file EXPECTED OUTPUT
test_begin_subtest "starts-with, list argument"
notmuch search --query=sexp '(starts-with (stuff))' >OUTPUT 2>&1
cat <<EOF > EXPECTED
notmuch search: Syntax error in query
'starts-with' expects single atom as argument
EOF
test_expect_equal_file EXPECTED OUTPUT
test_begin_subtest "starts-with, too many arguments"
notmuch search --query=sexp '(starts-with a b c)' >OUTPUT 2>&1
cat <<EOF > EXPECTED
notmuch search: Syntax error in query
'starts-with' expects single atom as argument
EOF
test_expect_equal_file EXPECTED OUTPUT
test_begin_subtest "starts-with, illegal field"
notmuch search --query=sexp '(body (starts-with foo))' >OUTPUT 2>&1
cat <<EOF > EXPECTED
notmuch search: Syntax error in query
'body' does not support wildcard queries
EOF
test_expect_equal_file EXPECTED OUTPUT
test_begin_subtest "wildcard, illegal field"
notmuch search --query=sexp '(body *)' >OUTPUT 2>&1
cat <<EOF > EXPECTED
notmuch search: Syntax error in query
'body' does not support wildcard queries
EOF
test_expect_equal_file EXPECTED OUTPUT
test_begin_subtest "Search, exclude \"deleted\" messages from search"
notmuch config set search.exclude_tags deleted
generate_message '[subject]="Not deleted"'
not_deleted_id=$gen_msg_id
generate_message '[subject]="Deleted"'
notmuch new > /dev/null
notmuch tag +deleted id:$gen_msg_id
deleted_id=$gen_msg_id
output=$(notmuch search --query=sexp '(subject deleted)' | notmuch_search_sanitize)
test_expect_equal "$output" "thread:XXX 2001-01-05 [1/1] Notmuch Test Suite; Not deleted (inbox unread)"
test_begin_subtest "Search, exclude \"deleted\" messages from message search --exclude=false"
output=$(notmuch search --query=sexp --exclude=false --output=messages '(subject deleted)' | notmuch_search_sanitize)
test_expect_equal "$output" "id:$not_deleted_id
id:$deleted_id"
test_begin_subtest "Search, exclude \"deleted\" messages from search, overridden"
notmuch search --query=sexp '(and (subject deleted) (tag deleted))' | notmuch_search_sanitize > OUTPUT
cat <<EOF > EXPECTED
thread:XXX 2001-01-05 [1/1] Notmuch Test Suite; Deleted (deleted inbox unread)
EOF
test_expect_equal_file EXPECTED OUTPUT
test_begin_subtest "Search, exclude \"deleted\" messages from threads"
add_message '[subject]="Not deleted reply"' '[in-reply-to]="<$gen_msg_id>"'
output=$(notmuch search --query=sexp '(subject deleted)' | notmuch_search_sanitize)
test_expect_equal "$output" "thread:XXX 2001-01-05 [1/1] Notmuch Test Suite; Not deleted (inbox unread)
thread:XXX 2001-01-05 [1/2] Notmuch Test Suite; Not deleted reply (deleted inbox unread)"
test_begin_subtest "Search, don't exclude \"deleted\" messages when --exclude=flag specified"
output=$(notmuch search --query=sexp --exclude=flag '(subject deleted)' | notmuch_search_sanitize)
test_expect_equal "$output" "thread:XXX 2001-01-05 [1/1] Notmuch Test Suite; Not deleted (inbox unread)
thread:XXX 2001-01-05 [1/2] Notmuch Test Suite; Deleted (deleted inbox unread)"
test_begin_subtest "Search, don't exclude \"deleted\" messages from search if not configured"
notmuch config set search.exclude_tags
output=$(notmuch search --query=sexp '(subject deleted)' | notmuch_search_sanitize)
test_expect_equal "$output" "thread:XXX 2001-01-05 [1/1] Notmuch Test Suite; Not deleted (inbox unread)
thread:XXX 2001-01-05 [2/2] Notmuch Test Suite; Deleted (deleted inbox unread)"
test_begin_subtest "regex at top level"
notmuch search --query=sexp '(rx foo)' >& OUTPUT
cat <<EOF > EXPECTED
notmuch search: Syntax error in query
illegal 'rx' outside field
EOF
test_expect_equal_file EXPECTED OUTPUT
test_begin_subtest "regex in illegal field"
notmuch search --query=sexp '(body (regex foo))' >& OUTPUT
cat <<EOF > EXPECTED
notmuch search: Syntax error in query
'regex' not supported in field 'body'
EOF
test_expect_equal_file EXPECTED OUTPUT
notmuch search --output=messages from:cworth > cworth.msg-ids
test_begin_subtest "regexp 'from' search"
notmuch search --output=messages --query=sexp '(from (rx cworth))' > OUTPUT
test_expect_equal_file cworth.msg-ids OUTPUT
test_begin_subtest "regexp search for 'from' 2"
notmuch search from:/cworth@cworth.org/ and subject:patch | notmuch_search_sanitize > EXPECTED
notmuch search --query=sexp '(and (from (rx cworth@cworth.org)) (subject patch))' \
| notmuch_search_sanitize > OUTPUT
test_expect_equal_file EXPECTED OUTPUT
test_begin_subtest "regexp 'folder' search"
notmuch search 'folder:/^bar$/' | notmuch_search_sanitize > EXPECTED
notmuch search --query=sexp '(folder (rx ^bar$))' | notmuch_search_sanitize > OUTPUT
test_expect_equal_file EXPECTED OUTPUT
test_begin_subtest "regexp 'id' search"
notmuch search --output=messages --query=sexp '(id (rx yoom))' > OUTPUT
test_expect_equal_file cworth.msg-ids OUTPUT
test_begin_subtest "unanchored 'is' search"
notmuch search tag:signed or tag:inbox > EXPECTED
notmuch search --query=sexp '(is (rx i))' > OUTPUT
test_expect_equal_file EXPECTED OUTPUT
test_begin_subtest "anchored 'is' search"
notmuch search tag:signed > EXPECTED
notmuch search --query=sexp '(is (rx ^si))' > OUTPUT
test_expect_equal_file EXPECTED OUTPUT
test_begin_subtest "combine regexp mid and subject"
notmuch search subject:/-C/ and mid:/y..m/ | notmuch_search_sanitize > EXPECTED
notmuch search --query=sexp '(and (subject (rx -C)) (mid (rx y..m)))' | notmuch_search_sanitize > OUTPUT
test_expect_equal_file EXPECTED OUTPUT
test_begin_subtest "regexp 'path' search"
notmuch search 'path:/^bar$/' | notmuch_search_sanitize > EXPECTED
notmuch search --query=sexp '(path (rx ^bar$))' | notmuch_search_sanitize > OUTPUT
test_expect_equal_file EXPECTED OUTPUT
test_begin_subtest "regexp 'property' search"
notmuch search property:foo=bar > EXPECTED
notmuch search --query=sexp '(property (rx foo=.*))' > OUTPUT
test_expect_equal_file EXPECTED OUTPUT
test_begin_subtest "anchored 'tag' search"
notmuch search tag:signed > EXPECTED
notmuch search --query=sexp '(tag (rx ^si))' > OUTPUT
test_expect_equal_file EXPECTED OUTPUT
test_begin_subtest "regexp 'thread' search"
notmuch search --output=threads '*' | grep '7$' > EXPECTED
notmuch search --output=threads --query=sexp '(thread (rx 7$))' > OUTPUT
test_expect_equal_file EXPECTED OUTPUT
test_begin_subtest "Basic query that matches no messages"
count=$(notmuch count from:keithp and to:keithp)
test_expect_equal 0 "$count"
test_begin_subtest "Same query against threads"
notmuch search --query=sexp '(and (thread (of (from keithp))) (thread (matching (to keithp))))' \
| notmuch_search_sanitize > OUTPUT
cat<<EOF > EXPECTED
thread:XXX 2009-11-18 [7/7] Lars Kellogg-Stedman, Mikhail Gusarov, Keith Packard, Carl Worth; [notmuch] Working with Maildir storage? (inbox signed unread)
EOF
test_expect_equal_file EXPECTED OUTPUT
test_begin_subtest "Mix thread and non-threads query"
notmuch search --query=sexp '(and (thread (matching keithp)) (to keithp))' | notmuch_search_sanitize > OUTPUT
cat<<EOF > EXPECTED
thread:XXX 2009-11-18 [1/7] Lars Kellogg-Stedman| Mikhail Gusarov, Keith Packard, Carl Worth; [notmuch] Working with Maildir storage? (inbox signed unread)
EOF
test_expect_equal_file EXPECTED OUTPUT
test_begin_subtest "Compound subquery"
notmuch search --query=sexp '(thread (of (from keithp) (subject Maildir)))' | notmuch_search_sanitize > OUTPUT
cat<<EOF > EXPECTED
thread:XXX 2009-11-18 [7/7] Lars Kellogg-Stedman, Mikhail Gusarov, Keith Packard, Carl Worth; [notmuch] Working with Maildir storage? (inbox signed unread)
EOF
test_expect_equal_file EXPECTED OUTPUT
test_begin_subtest "empty subquery"
notmuch search --query=sexp '(thread (of))' 1>OUTPUT 2>&1
notmuch search '*' > EXPECTED
test_expect_equal_file EXPECTED OUTPUT
test_begin_subtest "illegal expansion"
notmuch search --query=sexp '(id (of ego))' 1>OUTPUT 2>&1
cat<<EOF > EXPECTED
notmuch search: Syntax error in query
'of' unsupported inside 'id'
EOF
test_expect_equal_file EXPECTED OUTPUT
test_begin_subtest "(folder (of subquery))"
notmuch search --query=sexp --output=messages '(folder (of (id yun3a4cegoa.fsf@aiko.keithp.com)))' > OUTPUT
cat <<EOF > EXPECTED
id:yun1vjwegii.fsf@aiko.keithp.com
id:yun3a4cegoa.fsf@aiko.keithp.com
id:1258509400-32511-1-git-send-email-stewart@flamingspork.com
id:1258506353-20352-1-git-send-email-stewart@flamingspork.com
id:20091118010116.GC25380@dottiness.seas.harvard.edu
id:20091118005829.GB25380@dottiness.seas.harvard.edu
id:cf0c4d610911171136h1713aa59w9cf9aa31f052ad0a@mail.gmail.com
EOF
test_expect_equal_file EXPECTED OUTPUT
test_begin_subtest "infix query"
notmuch search to:searchbyto | notmuch_search_sanitize > EXPECTED
notmuch search --query=sexp '(infix "to:searchbyto")' | notmuch_search_sanitize > OUTPUT
test_expect_equal_file EXPECTED OUTPUT
test_begin_subtest "bad infix query 1"
notmuch search --query=sexp '(infix "from:/unbalanced")' 2>&1| notmuch_search_sanitize > OUTPUT
cat <<EOF > EXPECTED
notmuch search: Syntax error in query
Syntax error in infix query: from:/unbalanced
EOF
test_expect_equal_file EXPECTED OUTPUT
test_begin_subtest "bad infix query 2"
notmuch search --query=sexp '(infix "thread:{unbalanced")' 2>&1| notmuch_search_sanitize > OUTPUT
cat <<EOF > EXPECTED
notmuch search: Syntax error in query
Syntax error in infix query: thread:{unbalanced
EOF
test_expect_equal_file EXPECTED OUTPUT
test_begin_subtest "bad infix query 3: bad nesting"
notmuch search --query=sexp '(subject (infix "tag:inbox"))' 2>&1| notmuch_search_sanitize > OUTPUT
cat <<EOF > EXPECTED
notmuch search: Syntax error in query
'infix' not supported inside 'subject'
EOF
test_expect_equal_file EXPECTED OUTPUT
test_begin_subtest "infix query that matches no messages"
notmuch search --query=sexp '(and (infix "from:keithp") (infix "to:keithp"))' > OUTPUT
test_expect_equal_file /dev/null OUTPUT
test_begin_subtest "compound infix query"
notmuch search date:2009-11-18..2009-11-18 and tag:unread > EXPECTED
notmuch search --query=sexp '(infix "date:2009-11-18..2009-11-18 and tag:unread")' > OUTPUT
test_expect_equal_file EXPECTED OUTPUT
test_begin_subtest "compound infix query 2"
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
test_expect_equal_file EXPECTED OUTPUT
test_begin_subtest "user header (unknown header)"
notmuch search --query=sexp '(FooBar)' >& OUTPUT
cat <<EOF > EXPECTED
notmuch search: Syntax error in query
unknown prefix 'FooBar'
EOF
test_expect_equal_file EXPECTED OUTPUT
test_begin_subtest "adding user header"
test_expect_code 0 "notmuch config set index.header.List \"List-Id\""
test_begin_subtest "reindexing"
test_expect_code 0 'notmuch reindex "*"'
test_begin_subtest "wildcard search for user header"
grep -Ril List-Id ${MAIL_DIR} | sort | notmuch_dir_sanitize > EXPECTED
notmuch search --output=files --query=sexp '(List *)' | sort | notmuch_dir_sanitize > OUTPUT
test_expect_equal_file EXPECTED OUTPUT
test_begin_subtest "wildcard search for user header 2"
grep -Ril List-Id ${MAIL_DIR} | sort | notmuch_dir_sanitize > EXPECTED
notmuch search --output=files --query=sexp '(List (starts-with not))' | sort | notmuch_dir_sanitize > OUTPUT
test_expect_equal_file EXPECTED OUTPUT
test_begin_subtest "search for user header"
notmuch search List:notmuch | notmuch_search_sanitize > EXPECTED
notmuch search --query=sexp '(List notmuch)' | notmuch_search_sanitize > OUTPUT
test_expect_equal_file EXPECTED OUTPUT
test_begin_subtest "search for user header (list token)"
notmuch search List:notmuch | notmuch_search_sanitize > EXPECTED
notmuch search --query=sexp '(List notmuch.notmuchmail.org)' | notmuch_search_sanitize > OUTPUT
test_expect_equal_file EXPECTED OUTPUT
test_begin_subtest "search for user header (quoted string)"
notmuch search 'List:"notmuch notmuchmail org"' | notmuch_search_sanitize > EXPECTED
notmuch search --query=sexp '(List "notmuch notmuchmail org")' | notmuch_search_sanitize > OUTPUT
test_expect_equal_file EXPECTED OUTPUT
test_begin_subtest "search for user header (atoms)"
notmuch search 'List:"notmuch notmuchmail org"' | notmuch_search_sanitize > EXPECTED
notmuch search --query=sexp '(List notmuch notmuchmail org)' | notmuch_search_sanitize > OUTPUT
test_expect_equal_file EXPECTED OUTPUT
test_begin_subtest "check saved query name"
test_expect_code 1 "notmuch config set squery.test '(subject utf8-sübjéct)'"
test_begin_subtest "roundtrip saved query (database)"
notmuch config set --database squery.Test '(subject utf8-sübjéct)'
output=$(notmuch config get squery.Test)
test_expect_equal "$output" '(subject utf8-sübjéct)'
test_begin_subtest "roundtrip saved query"
notmuch config set squery.Test '(subject override subject)'
output=$(notmuch config get squery.Test)
test_expect_equal "$output" '(subject override subject)'
test_begin_subtest "unknown saved query"
notmuch search --query=sexp '(Unknown foo bar)' >OUTPUT 2>&1
cat <<EOF > EXPECTED
notmuch search: Syntax error in query
unknown prefix 'Unknown'
EOF
test_expect_equal_file EXPECTED OUTPUT
test_begin_subtest "syntax error in saved query"
notmuch config set squery.Bad '(Bad'
notmuch search --query=sexp '(Bad foo bar)' >OUTPUT 2>&1
cat <<EOF > EXPECTED
notmuch search: Syntax error in query
invalid saved s-expression query: '(Bad'
EOF
test_expect_equal_file EXPECTED OUTPUT
test_begin_subtest "Saved Search by 'tag' and 'subject'"
notmuch search tag:inbox and subject:maildir | notmuch_search_sanitize > EXPECTED
notmuch config set squery.TagSubject '(and (tag inbox) (subject maildir))'
notmuch search --query=sexp '(TagSubject)' | notmuch_search_sanitize > OUTPUT
test_expect_equal_file EXPECTED OUTPUT
test_begin_subtest "Saved Search: illegal nesting"
notmuch config set squery.TagSubject '(and (tag inbox) (subject maildir))'
notmuch search --query=sexp '(subject (TagSubject))' >OUTPUT 2>&1
cat <<EOF > EXPECTED
notmuch search: Syntax error in query
nested field: 'tag' inside 'subject'
EOF
test_expect_equal_file EXPECTED OUTPUT
test_begin_subtest "Saved Search: list as prefix"
notmuch config set squery.Bad2 '((and) (tag inbox) (subject maildir))'
notmuch search --query=sexp '(Bad2)' >OUTPUT 2>&1
cat <<EOF > EXPECTED
notmuch search: Syntax error in query
unexpected list in field/operation position
EOF
test_expect_equal_file EXPECTED OUTPUT
test_begin_subtest "Saved Search: bad parameter syntax"
notmuch config set squery.Bad3 '(macro a b)'
notmuch search --query=sexp '(Bad3)' >OUTPUT 2>&1
cat <<EOF > EXPECTED
notmuch search: Syntax error in query
missing (possibly empty) list of arguments to macro
EOF
test_expect_equal_file EXPECTED OUTPUT
test_begin_subtest "Saved Search: bad parameter syntax 2"
notmuch config set squery.Bad4 '(macro ((a b)) a)'
notmuch search --query=sexp '(Bad4 1)' >OUTPUT 2>&1
cat <<EOF > EXPECTED
notmuch search: Syntax error in query
macro parameters must be unquoted atoms
EOF
test_expect_equal_file EXPECTED OUTPUT
test_begin_subtest "Saved Search: bad parameter syntax 3"
notmuch config set squery.Bad5 '(macro (a b) a)'
notmuch search --query=sexp '(Bad5 1)' >OUTPUT 2>&1
cat <<EOF > EXPECTED
notmuch search: Syntax error in query
too few arguments to macro
EOF
test_expect_equal_file EXPECTED OUTPUT
test_begin_subtest "Saved Search: bad parameter syntax 4"
notmuch search --query=sexp '(Bad5 1 2 3)' >OUTPUT 2>&1
cat <<EOF > EXPECTED
notmuch search: Syntax error in query
too many arguments to macro
EOF
test_expect_equal_file EXPECTED OUTPUT
test_begin_subtest "Saved Search: macro without body"
notmuch config set squery.Bad3 '(macro (a b))'
notmuch search --query=sexp '(Bad3)' >OUTPUT 2>&1
cat <<EOF > EXPECTED
notmuch search: Syntax error in query
missing body of macro
EOF
test_expect_equal_file EXPECTED OUTPUT
test_begin_subtest "macro in query"
notmuch search --query=sexp '(macro (a) (and ,b (subject maildir)))' >OUTPUT 2>&1
cat <<EOF > EXPECTED
notmuch search: Syntax error in query
macro definition not permitted here
EOF
test_expect_equal_file EXPECTED OUTPUT
test_begin_subtest "zero argument macro"
notmuch search tag:inbox and subject:maildir | notmuch_search_sanitize > EXPECTED
notmuch config set squery.TagSubject0 '(macro () (and (tag inbox) (subject maildir)))'
notmuch search --query=sexp '(TagSubject0)' | notmuch_search_sanitize > OUTPUT
test_expect_equal_file EXPECTED OUTPUT
test_begin_subtest "undefined argument"
notmuch search tag:inbox and subject:maildir | notmuch_search_sanitize > EXPECTED
notmuch config set squery.Bad6 '(macro (a) (and ,b (subject maildir)))'
notmuch search --query=sexp '(Bad6 foo)' >OUTPUT 2>&1
cat <<EOF > EXPECTED
notmuch search: Syntax error in query
undefined parameter b
EOF
test_expect_equal_file EXPECTED OUTPUT
test_begin_subtest "Single argument macro"
notmuch search tag:inbox and subject:maildir | notmuch_search_sanitize > EXPECTED
notmuch config set squery.TagSubject1 '(macro (tagname) (and (tag ,tagname) (subject maildir)))'
notmuch search --query=sexp '(TagSubject1 inbox)' | notmuch_search_sanitize > OUTPUT
test_expect_equal_file EXPECTED OUTPUT
test_begin_subtest "Single argument macro, list argument"
notmuch search tag:inbox and subject:maildir | notmuch_search_sanitize > EXPECTED
notmuch config set squery.ThingSubject '(macro (thing) (and ,thing (subject maildir)))'
notmuch search --query=sexp '(ThingSubject (tag inbox))' | notmuch_search_sanitize > OUTPUT
test_expect_equal_file EXPECTED OUTPUT
test_begin_subtest "two argument macro"
notmuch search tag:inbox and subject:maildir | notmuch_search_sanitize > EXPECTED
notmuch config set squery.TagSubject2 '(macro (tagname subj) (and (tag ,tagname) (subject ,subj)))'
notmuch search --query=sexp '(TagSubject2 inbox maildir)' | notmuch_search_sanitize > OUTPUT
test_expect_equal_file EXPECTED OUTPUT
test_begin_subtest "nested macros (shadowing)"
notmuch search tag:inbox and subject:maildir | notmuch_search_sanitize > EXPECTED
notmuch config set squery.Inner '(macro (x) (subject ,x))'
notmuch config set squery.Outer '(macro (x y) (and (tag ,x) (Inner ,y)))'
notmuch search --query=sexp '(Outer inbox maildir)' | notmuch_search_sanitize > OUTPUT
test_expect_equal_file EXPECTED OUTPUT
test_begin_subtest "nested macros (no dynamic scope)"
notmuch config set squery.Inner2 '(macro (x) (subject ,y))'
notmuch config set squery.Outer2 '(macro (x y) (and (tag ,x) (Inner2 ,y)))'
notmuch search --query=sexp '(Outer2 inbox maildir)' > OUTPUT 2>&1
cat <<EOF > EXPECTED
notmuch search: Syntax error in query
undefined parameter y
EOF
test_expect_equal_file EXPECTED OUTPUT
test_begin_subtest "combine macro and user defined header"
notmuch config set squery.About '(macro (name) (or (subject ,name) (List ,name)))'
notmuch search subject:notmuch or List:notmuch | notmuch_search_sanitize > EXPECTED
notmuch search --query=sexp '(About notmuch)' | notmuch_search_sanitize > OUTPUT
test_expect_equal_file EXPECTED OUTPUT
test_begin_subtest "combine macro and user defined header"
notmuch config set squery.About '(macro (name) (or (subject ,name) (List ,name)))'
notmuch search subject:notmuch or List:notmuch | notmuch_search_sanitize > EXPECTED
notmuch search --query=sexp '(About notmuch)' | notmuch_search_sanitize > OUTPUT
test_expect_equal_file EXPECTED OUTPUT
test_done

View file

@ -325,4 +325,11 @@ cat <<EOF >EXPECTED
EOF
test_expect_equal_file EXPECTED OUTPUT
if [[ NOTMUCH_HAVE_SFSEXP = 1 ]]; then
test_begin_subtest "sexpr query: all messages"
notmuch address '*' > EXPECTED
notmuch address --query=sexp '()' > OUTPUT
test_expect_equal_file EXPECTED OUTPUT
fi
test_done

View file

@ -2,6 +2,21 @@
test_description='"notmuch tag"'
. $(dirname "$0")/test-lib.sh || exit 1
test_query_syntax () {
# use a tag with a space to stress the query string munging code.
local new_tag="${RANDOM} ${RANDOM}"
test_begin_subtest "sexpr query: $1"
backup_database
notmuch tag --query=sexp "+${new_tag}" -- "$1"
notmuch dump > OUTPUT
restore_database
backup_database
notmuch tag "+${new_tag}" -- "$2"
notmuch dump > EXPECTED
restore_database
test_expect_equal_file_nonempty EXPECTED OUTPUT
}
add_message '[subject]=One'
add_message '[subject]=Two'
@ -310,4 +325,32 @@ output=$(notmuch tag +something '*' 2>&1 | sed 's/: .*$//' )
chmod u+w ${MAIL_DIR}/.notmuch/xapian/*.*
test_expect_equal "$output" "A Xapian exception occurred opening database"
add_email_corpus
if [ $NOTMUCH_HAVE_SFSEXP -eq 1 ]; then
test_query_syntax '(and "wonderful" "wizard")' 'wonderful and wizard'
test_query_syntax '(or "php" "wizard")' 'php or wizard'
test_query_syntax 'wizard' 'wizard'
test_query_syntax 'Wizard' 'Wizard'
test_query_syntax '(attachment notmuch-help.patch)' 'attachment:notmuch-help.patch'
test_query_syntax '(mimetype text/html)' 'mimetype:text/html'
test_begin_subtest "--batch --query=sexp"
notmuch dump --format=batch-tag > backup.tags
notmuch tag --batch --query=sexp <<EOF
+all -- (or One Two)
+none -- (and One Two)
EOF
notmuch dump > OUTPUT
cat <<EOF > EXPECTED
#notmuch-dump batch-tag:3 config,properties,tags
+all +inbox +tag5 +unread -- id:msg-001@notmuch-test-suite
+all +inbox +tag4 +tag5 +unread -- id:msg-002@notmuch-test-suite
EOF
notmuch restore --format=batch-tag < backup.tags
test_expect_equal_file EXPECTED OUTPUT
fi
test_done

View file

@ -2,15 +2,14 @@
test_description="\"notmuch reply\" in several variations"
. $(dirname "$0")/test-lib.sh || exit 1
test_begin_subtest "Basic reply"
add_message '[from]="Sender <sender@example.com>"' \
[to]=test_suite@notmuchmail.org \
[subject]=notmuch-reply-test \
'[date]="Tue, 05 Jan 2010 15:43:56 -0000"' \
'[body]="basic reply test"'
output=$(notmuch reply id:${gen_msg_id} 2>&1 && echo OK)
test_expect_equal "$output" "From: Notmuch Test Suite <test_suite@notmuchmail.org>
cat <<EOF > basic.expected
From: Notmuch Test Suite <test_suite@notmuchmail.org>
Subject: Re: notmuch-reply-test
To: Sender <sender@example.com>
In-Reply-To: <${gen_msg_id}>
@ -18,7 +17,19 @@ References: <${gen_msg_id}>
On Tue, 05 Jan 2010 15:43:56 -0000, Sender <sender@example.com> wrote:
> basic reply test
OK"
OK
EOF
test_begin_subtest "Basic reply"
notmuch reply id:${gen_msg_id} >OUTPUT 2>&1 && echo OK >> OUTPUT
test_expect_equal_file basic.expected OUTPUT
if [ $NOTMUCH_HAVE_SFSEXP -eq 1 ]; then
test_begin_subtest "Basic reply (query=sexp)"
notmuch reply --query=sexp "(id ${gen_msg_id})" >OUTPUT 2>&1 && echo OK >> OUTPUT
test_expect_equal_file basic.expected OUTPUT
fi
test_begin_subtest "Multiple recipients"
add_message '[from]="Sender <sender@example.com>"' \
@ -245,6 +256,26 @@ On Tue, 05 Jan 2010 15:43:56 -0000, Sender <sender@example.com> wrote:
> From guessing
OK"
test_begin_subtest "From guessing: multiple Delivered-To"
add_message '[from]="Sender <sender@example.com>"' \
'[to]="Recipient <recipient@example.com>"' \
'[subject]="From guessing"' \
'[date]="Tue, 05 Jan 2010 15:43:56 -0000"' \
'[body]="From guessing"' \
'[header]="Delivered-To: test_suite_other@notmuchmail.org
Delivered-To: test_suite@notmuchmail.org"'
output=$(notmuch reply id:${gen_msg_id} 2>&1 && echo OK)
test_expect_equal "$output" "From: Notmuch Test Suite <test_suite@notmuchmail.org>
Subject: Re: From guessing
To: Sender <sender@example.com>, Recipient <recipient@example.com>
In-Reply-To: <${gen_msg_id}>
References: <${gen_msg_id}>
On Tue, 05 Jan 2010 15:43:56 -0000, Sender <sender@example.com> wrote:
> From guessing
OK"
test_begin_subtest "Reply with RFC 2047-encoded headers"
add_message '[subject]="=?iso-8859-1?q?=e0=df=e7?="' \
'[from]="=?utf-8?q?=e2=98=83?= <snowman@example.com>"' \
@ -281,7 +312,7 @@ test_expect_equal_json "$output" '
"crypto": {},
"date_relative": "2010-01-05",
"excluded": false,
"filename": ["'${MAIL_DIR}'/msg-014"],
"filename": ["'${MAIL_DIR}'/msg-015"],
"headers": {
"Date": "Tue, 05 Jan 2010 15:43:56 +0000",
"From": "\u2603 <snowman@example.com>",

View file

@ -117,6 +117,19 @@ test_begin_subtest "dump -- from:cworth"
notmuch dump -- from:cworth > dump-dash-cworth.actual
test_expect_equal_file dump-cworth.expected dump-dash-cworth.actual
if [ $NOTMUCH_HAVE_SFSEXP -eq 1 ]; then
test_begin_subtest "dump --query=sexp -- '(from cworth)'"
notmuch dump --query=sexp -- '(from cworth)' > dump-dash-cworth.actual2
test_expect_equal_file_nonempty dump-cworth.expected dump-dash-cworth.actual2
test_begin_subtest "dump --query=sexp --output=outfile '(from cworth)'"
notmuch dump --output=dump-outfile-cworth.actual2 --query=sexp '(from cworth)'
test_expect_equal_file dump-cworth.expected dump-outfile-cworth.actual2
fi
test_begin_subtest "dump --output=outfile from:cworth"
notmuch dump --output=dump-outfile-cworth.actual from:cworth
test_expect_equal_file dump-cworth.expected dump-outfile-cworth.actual

View file

@ -41,6 +41,20 @@ test_emacs '(notmuch-search "tag:inbox")
(test-output)'
test_expect_equal_file $EXPECTED/notmuch-search-tag-inbox OUTPUT
test_begin_subtest "Functions in search-result-format"
test_emacs '(let
((notmuch-search-result-format
(quote ((notmuch-test-result-flags . "%s ")
("date" . "%12s ")
("count" . "%9s ")
("authors" . "%-30s ")
("subject" . "%s ")
("tags" . "(%s)")))))
(notmuch-search "tag:inbox")
(notmuch-test-wait)
(test-output))'
test_expect_equal_file $EXPECTED/search-result-format-function OUTPUT
test_begin_subtest "Incremental parsing of search results"
test_emacs "(cl-letf* (((symbol-function 'orig)
(symbol-function 'notmuch-search-process-filter))
@ -1154,4 +1168,10 @@ This text added by the hook.
EOF
test_expect_equal_file EXPECTED OUTPUT
test_begin_subtest "notmuch-search with nonexistent CWD"
test_emacs '(test-log-error
(let ((default-directory "/nonexistent"))
(notmuch-search "*")))'
test_expect_equal "$(cat MESSAGES)" "COMPLETE"
test_done

View file

@ -226,6 +226,7 @@ output=$(notmuch dump | LC_ALL=C sort)
expected='#= simple-encrypted@crypto.notmuchmail.org index.decryption=failure
#notmuch-dump batch-tag:3 config,properties,tags
+encrypted +inbox +unread -- id:basic-encrypted@crypto.notmuchmail.org
+encrypted +inbox +unread -- id:encrypted-rfc822-attachment@crypto.notmuchmail.org
+encrypted +inbox +unread -- id:encrypted-signed@crypto.notmuchmail.org
+encrypted +inbox +unread -- id:simple-encrypted@crypto.notmuchmail.org'
test_expect_equal \

View file

@ -7,8 +7,14 @@ if [ $NOTMUCH_HAVE_PYTHON3_CFFI -eq 0 -o $NOTMUCH_HAVE_PYTHON3_PYTEST -eq 0 ]; t
fi
test_begin_subtest "python cffi tests"
test_begin_subtest "python cffi tests (NOTMUCH_CONFIG set)"
pytest_dir=$NOTMUCH_BUILDDIR/bindings/python-cffi/build/stage
printf "[pytest]\nminversion = 3.0\naddopts = -ra\n" > $pytest_dir/pytest.ini
test_expect_success "(cd $pytest_dir && ${NOTMUCH_PYTHON} -m pytest --verbose --log-file=$TMP_DIRECTORY/test.output)"
test_begin_subtest "python cffi tests (NOTMUCH_CONFIG unset)"
pytest_dir=$NOTMUCH_BUILDDIR/bindings/python-cffi/build/stage
printf "[pytest]\nminversion = 3.0\naddopts = -ra\n" > $pytest_dir/pytest.ini
unset NOTMUCH_CONFIG
test_expect_success "(cd $pytest_dir && ${NOTMUCH_PYTHON} -m pytest --verbose --log-file=$TMP_DIRECTORY/test.output)"
test_done

View file

@ -68,4 +68,12 @@ test_emacs '(notmuch-hello)
notmuch tag -$tag '*'
test_expect_equal_file $EXPECTED/notmuch-hello-long-names OUTPUT
test_begin_subtest "notmuch-hello with nonexistent CWD"
test_emacs '
(notmuch-hello)
(test-log-error
(let ((default-directory "/nonexistent"))
(notmuch-hello-update)))'
test_expect_equal "$(cat MESSAGES)" "COMPLETE"
test_done

View file

@ -219,6 +219,12 @@ test_emacs '(notmuch-show "id:basic-encrypted@crypto.notmuchmail.org")
(test-visible-output)'
test_expect_equal_file $EXPECTED/notmuch-show-decrypted-message OUTPUT
test_begin_subtest "show encrypted rfc822 message"
test_subtest_known_broken
test_emacs '(notmuch-show "id:encrypted-rfc822-attachment@crypto.notmuchmail.org")
(test-visible-output)'
test_expect_code 1 'fgrep "!!!" OUTPUT'
test_begin_subtest "show undecryptable message"
test_emacs '(notmuch-show "id:simple-encrypted@crypto.notmuchmail.org")
(test-visible-output)'
@ -230,4 +236,11 @@ test_emacs '(let ((notmuch-crypto-process-mime nil))
(test-visible-output))'
test_expect_equal_file $EXPECTED/notmuch-show-decrypted-message-no-crypto OUTPUT
test_begin_subtest "notmuch-show with nonexistent CWD"
tid=$(notmuch search --limit=1 --output=threads '*' | sed s/thread://)
test_emacs "(test-log-error
(let ((default-directory \"/nonexistent\"))
(notmuch-show \"$tid\")))"
test_expect_equal "$(cat MESSAGES)" "COMPLETE"
test_done

View file

@ -179,4 +179,25 @@ output=$(test_emacs '(notmuch-tree "tag:inbox")
(notmuch-show-stash-message-id)')
test_expect_equal "$output" "\"Stashed: id:1258493565-13508-1-git-send-email-keithp@keithp.com\""
test_begin_subtest "Functions in tree-result-format"
test_emacs '
(let
((notmuch-tree-result-format
(quote (("date" . "%12s ")
("authors" . "%-20s")
((("tree" . "%s")
("subject" . "%s")) . " %-54s ")
(notmuch-test-result-flags . "(%s)")))))
(notmuch-tree "tag:inbox")
(notmuch-test-wait)
(test-output))
'
test_expect_equal_file $EXPECTED/result-format-function OUTPUT
test_begin_subtest "notmuch-tree with nonexistent CWD"
test_emacs '(test-log-error
(let ((default-directory "/nonexistent"))
(notmuch-tree "*")))'
test_expect_equal "$(cat MESSAGES)" "COMPLETE"
test_done

View file

@ -6,6 +6,8 @@ test_description="emacs unthreaded interface"
test_require_emacs
EXPECTED=$NOTMUCH_SRCDIR/test/emacs-unthreaded.expected-output
generate_message "[id]=large-thread-1" '[subject]="large thread"'
printf " 2001-01-05 Notmuch Test Suite large thread%43s(inbox unread)\n" >> EXPECTED.unthreaded
@ -34,4 +36,25 @@ output=$(test_emacs '(let ((max-lisp-eval-depth 10))
"SUCCESS")' )
test_expect_equal "$output" '"SUCCESS"'
add_email_corpus
test_begin_subtest "Functions in unthreaded-result-format"
test_emacs '
(let
((notmuch-unthreaded-result-format
(quote (("date" . "%12s ")
("authors" . "%-20s")
("subject" . "%-54s")
(notmuch-test-result-flags . "(%s)")))))
(notmuch-unthreaded "tag:inbox")
(notmuch-test-wait)
(test-output))
'
test_expect_equal_file $EXPECTED/result-format-function OUTPUT
test_begin_subtest "notmuch-unthreaded with nonexistent CWD"
test_emacs '(test-log-error
(let ((default-directory "/nonexistent"))
(notmuch-unthreaded "*")))'
test_expect_equal "$(cat MESSAGES)" "COMPLETE"
test_done

View file

@ -3,6 +3,13 @@ test_description='"notmuch show"'
. $(dirname "$0")/test-lib.sh || exit 1
test_query_syntax () {
test_begin_subtest "sexpr query: $1"
sexp=$(notmuch show --format=json --query=sexp "$1")
infix=$(notmuch show --format=json "$2")
test_expect_equal_json "$sexp" "$infix"
}
add_email_corpus
test_begin_subtest "exit code for show invalid query"
@ -27,4 +34,15 @@ notmuch show --entire-thread=true --sort=newest-first $QUERY > EXPECTED
notmuch show --entire-thread=true --sort=oldest-first $QUERY > OUTPUT
test_expect_equal_file EXPECTED OUTPUT
if [ $NOTMUCH_HAVE_SFSEXP -eq 1 ]; then
test_query_syntax '(and "wonderful" "wizard")' 'wonderful and wizard'
test_query_syntax '(or "php" "wizard")' 'php or wizard'
test_query_syntax 'wizard' 'wizard'
test_query_syntax 'Wizard' 'Wizard'
test_query_syntax '(attachment notmuch-help.patch)' 'attachment:notmuch-help.patch'
fi
test_done

View file

@ -461,7 +461,7 @@ cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR} %NULL% %NULL%
}
EOF
rm -f ${ovconfig}
NOTMUCH_CONFIG=${old_NOTMUCH_CONFIG}
export NOTMUCH_CONFIG=${old_NOTMUCH_CONFIG}
cat <<'EOF' >EXPECTED
== stdout ==
test.key1 = overridden-home
@ -488,7 +488,7 @@ cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR} %NULL% %NULL%
}
EOF
rm -f ${ovconfig}
NOTMUCH_CONFIG=${old_NOTMUCH_CONFIG}
export NOTMUCH_CONFIG=${old_NOTMUCH_CONFIG}
cat <<'EOF' >EXPECTED
== stdout ==
test.key1 = overridden-xdg
@ -515,7 +515,7 @@ cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR} %NULL% work
}
EOF
rm -f ${ovconfig}
NOTMUCH_CONFIG=${old_NOTMUCH_CONFIG}
export NOTMUCH_CONFIG=${old_NOTMUCH_CONFIG}
cat <<'EOF' >EXPECTED
== stdout ==
test.key1 = overridden-xdg-profile
@ -541,7 +541,7 @@ cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR} %NULL% work
}
EOF
#rm -f ${ovconfig}
NOTMUCH_CONFIG=${old_NOTMUCH_CONFIG}
export NOTMUCH_CONFIG=${old_NOTMUCH_CONFIG}
cat <<'EOF' >EXPECTED
== stdout ==
test.key1 = overridden-profile
@ -559,7 +559,7 @@ cat c_head - c_tail <<'EOF' | test_C %NULL% '' %NULL%
printf("NOT RUN");
}
EOF
NOTMUCH_CONFIG=${old_NOTMUCH_CONFIG}
export NOTMUCH_CONFIG=${old_NOTMUCH_CONFIG}
cat <<'EOF' >EXPECTED
== stdout ==
== stderr ==
@ -604,7 +604,7 @@ cat c_head - c_tail <<'EOF' | test_C %NULL% '' %NULL%
printf("test.key2 = %s\n", val);
}
EOF
NOTMUCH_CONFIG=${old_NOTMUCH_CONFIG}
export NOTMUCH_CONFIG=${old_NOTMUCH_CONFIG}
unset NOTMUCH_DATABASE
cat <<'EOF' >EXPECTED
== stdout ==
@ -783,7 +783,7 @@ cat c_head2 - c_tail <<'EOF' | test_C ${MAIL_DIR} %NULL% %NULL%
}
EOF
rm -f ${ovconfig}
NOTMUCH_CONFIG=${old_NOTMUCH_CONFIG}
export NOTMUCH_CONFIG=${old_NOTMUCH_CONFIG}
cat <<'EOF' >EXPECTED
== stdout ==
test.key1 = overridden-home
@ -849,4 +849,128 @@ zzzafter afterval
EOF
test_expect_equal_file EXPECTED OUTPUT
cat <<EOF > c_head3
#include <notmuch-test.h>
int main (int argc, char **argv) {
notmuch_status_t stat;
notmuch_database_t *db = NULL;
EOF
cat <<EOF > c_tail3
printf("db == NULL: %d\n", db == NULL);
}
EOF
test_begin_subtest "open: database set to null on missing config"
cat c_head3 - c_tail3 <<'EOF' | test_C ${MAIL_DIR}
notmuch_status_t st = notmuch_database_open_with_config(argv[1],
NOTMUCH_DATABASE_MODE_READ_ONLY,
"/nonexistent", NULL, &db, NULL);
EOF
cat <<EOF> EXPECTED
== stdout ==
db == NULL: 1
== stderr ==
EOF
test_expect_equal_file EXPECTED OUTPUT
test_begin_subtest "open: database set to null on missing config (env)"
old_NOTMUCH_CONFIG=${NOTMUCH_CONFIG}
export NOTMUCH_CONFIG="/nonexistent"
cat c_head3 - c_tail3 <<'EOF' | test_C ${MAIL_DIR}
notmuch_status_t st = notmuch_database_open_with_config(argv[1],
NOTMUCH_DATABASE_MODE_READ_ONLY,
NULL, NULL, &db, NULL);
EOF
export NOTMUCH_CONFIG=${old_NOTMUCH_CONFIG}
cat <<EOF> EXPECTED
== stdout ==
db == NULL: 1
== stderr ==
EOF
test_expect_equal_file EXPECTED OUTPUT
test_begin_subtest "create: database set to null on missing config"
cat c_head3 - c_tail3 <<'EOF' | test_C ${MAIL_DIR} "/nonexistent"
notmuch_status_t st = notmuch_database_create_with_config(argv[1],argv[2], NULL, &db, NULL);
EOF
cat <<EOF> EXPECTED
== stdout ==
db == NULL: 1
== stderr ==
EOF
test_expect_equal_file EXPECTED OUTPUT
test_begin_subtest "create: database set to null on missing config (env)"
old_NOTMUCH_CONFIG=${NOTMUCH_CONFIG}
export NOTMUCH_CONFIG="/nonexistent"
cat c_head3 - c_tail3 <<'EOF' | test_C ${MAIL_DIR}
notmuch_status_t st = notmuch_database_create_with_config(argv[1],
NULL, NULL, &db, NULL);
EOF
export NOTMUCH_CONFIG=${old_NOTMUCH_CONFIG}
cat <<EOF> EXPECTED
== stdout ==
db == NULL: 1
== stderr ==
EOF
test_expect_equal_file EXPECTED OUTPUT
test_begin_subtest "load_config: database set non-null on missing config"
cat c_head3 - c_tail3 <<'EOF' | test_C ${MAIL_DIR} "/nonexistent"
notmuch_status_t st = notmuch_database_load_config(argv[1],argv[2], NULL, &db, NULL);
EOF
cat <<EOF> EXPECTED
== stdout ==
db == NULL: 0
== stderr ==
EOF
test_expect_equal_file EXPECTED OUTPUT
test_begin_subtest "load_config: database non-null on missing config (env)"
old_NOTMUCH_CONFIG=${NOTMUCH_CONFIG}
export NOTMUCH_CONFIG="/nonexistent"
cat c_head3 - c_tail3 <<'EOF' | test_C ${MAIL_DIR}
notmuch_status_t st = notmuch_database_load_config(argv[1], NULL, NULL, &db, NULL);
EOF
export NOTMUCH_CONFIG=${old_NOTMUCH_CONFIG}
cat <<EOF> EXPECTED
== stdout ==
db == NULL: 0
== stderr ==
EOF
test_expect_equal_file EXPECTED OUTPUT
test_begin_subtest "load_config: database set to NULL on fatal error"
cat c_head3 - c_tail3 <<'EOF' | test_C
notmuch_status_t st = notmuch_database_load_config("relative", NULL, NULL, &db, NULL);
EOF
cat <<EOF> EXPECTED
== stdout ==
db == NULL: 1
== stderr ==
EOF
test_expect_equal_file EXPECTED OUTPUT
test_begin_subtest "open: database parameter overrides implicit config"
notmuch config set database.path ${MAIL_DIR}/nonexistent
cat c_head3 - c_tail3 <<'EOF' | test_C ${MAIL_DIR}
const char *path = NULL;
notmuch_status_t st = notmuch_database_open_with_config(argv[1],
NOTMUCH_DATABASE_MODE_READ_ONLY,
NULL, NULL, &db, NULL);
printf ("status: %d\n", st);
path = notmuch_database_get_path (db);
printf ("path: %s\n", path ? path : "(null)");
EOF
cat <<EOF> EXPECTED
== stdout ==
status: 0
path: MAIL_DIR
db == NULL: 0
== stderr ==
EOF
notmuch_dir_sanitize < OUTPUT > OUTPUT.clean
test_expect_equal_file EXPECTED OUTPUT.clean
test_done

View file

@ -4,6 +4,21 @@ test_description='reindexing messages'
add_email_corpus
if [ $NOTMUCH_HAVE_SFSEXP -eq 1 ]; then
count=$(notmuch count --lastmod '*' | cut -f 3)
for query in '()' '(not)' '(and)' '(or ())' '(or (not))' '(or (and))' \
'(or (and) (or) (not (and)))'; do
test_begin_subtest "reindex all messages: $query"
notmuch reindex --query=sexp "$query"
output=$(notmuch count --lastmod '*' | cut -f 3)
count=$((count + 1))
test_expect_equal "$output" "$count"
done
fi
notmuch tag +usertag1 '*'
notmuch search '*' 2>1 | notmuch_search_sanitize > initial-threads
@ -41,6 +56,7 @@ notmuch dump > OUTPUT
notmuch tag -attachment2 -encrypted2 -signed2 '*'
test_expect_equal_file EXPECTED OUTPUT
backup_database
test_begin_subtest 'reindex moves a message between threads'
notmuch search --output=threads id:87iqd9rn3l.fsf@vertex.dottedmag > EXPECTED
# re-parent
@ -48,7 +64,9 @@ sed -i 's/1258471718-6781-1-git-send-email-dottedmag@dottedmag.net/87iqd9rn3l.fs
notmuch reindex id:1258471718-6781-2-git-send-email-dottedmag@dottedmag.net
notmuch search --output=threads id:1258471718-6781-2-git-send-email-dottedmag@dottedmag.net > OUTPUT
test_expect_equal_file EXPECTED OUTPUT
restore_database
backup_database
test_begin_subtest 'reindex detects removal of all files'
notmuch search --output=messages not id:20091117232137.GA7669@griffis1.net> EXPECTED
# remove both copies
@ -56,6 +74,17 @@ mv $MAIL_DIR/cur/51:2,* duplicate-message-2.eml
notmuch reindex id:20091117232137.GA7669@griffis1.net
notmuch search --output=messages '*' > OUTPUT
test_expect_equal_file EXPECTED OUTPUT
restore_database
backup_database
test_begin_subtest 'reindex detects removal of all files'
notmuch search --output=messages not id:20091117232137.GA7669@griffis1.net> EXPECTED
# remove both copies
mv $MAIL_DIR/cur/51:2,* duplicate-message-2.eml
notmuch reindex id:20091117232137.GA7669@griffis1.net
notmuch search --output=messages '*' > OUTPUT
test_expect_equal_file EXPECTED OUTPUT
restore_database
test_begin_subtest "reindex preserves properties"
cat <<EOF > prop-dump

View file

@ -0,0 +1,52 @@
Content-Type: multipart/encrypted; protocol="application/pgp-encrypted"; boundary="===============9060418334135509864=="
MIME-Version: 1.0
From: Notmuch test suite <test_suite@notmuchmail.org>
To: test_suite@notmuchmail.org
Subject: testing encrypted rfc822 attachments
Date: Sat, 03 Jul 2021 16:00:02 -0300
Message-ID: <encrypted-rfc822-attachment@crypto.notmuchmail.org>
User-Agent: alot/0.9.1
--===============9060418334135509864==
MIME-Version: 1.0
Content-Transfer-Encoding: 7bit
Content-Type: application/pgp-encrypted; charset="us-ascii"
Version: 1
--===============9060418334135509864==
MIME-Version: 1.0
Content-Transfer-Encoding: 7bit
Content-Type: application/octet-stream; charset="us-ascii"
-----BEGIN PGP MESSAGE-----
hIwDxE023q1UqxYBBACGKSDv5/rcwScSf9n33cZZPPxltQgxkDaClMGY2DARgebE
9rpE2O/4eoaEbdu+2shahPLIbD0wuRiGVpMIIloNNucl3f15h1wXPZbTwK7sNxJq
HycSf8sT1fkolmC9s9X0r5xHgk0G4klAqr5C3GOk7Y6wsHTYGqzDvBFEB0LvaoUC
DANw48DehwaEUQEP/iaiYeMDUsOzFZodKZOlQWese2JPTsRwF7KrTl8C93MrZqAh
A1pQjUH9cafj8mwDXA9ZCsYZs7r84IxShI+dUhinBSCq8F61OlLP859wq+wpKU7n
PNVA5bfd//4hRFvDT33ZlgeeeCcRo7h4IDjJwFDYsf0Ysqvo+IKipVNXXlAcGYYI
DVBucB0fYaVHWRKxw50mo02zKP2/GW6K3p1nxGTf73cKjc9of+VOvvMByODaJ+ne
WIuzZMqz0vfQ0UvRVBjlsXtB7VpCqJZWkubqqwwP6+2WOCA/c5LC3z2f/BK90EQh
/JrKfDR083UNhu4SjNwL/TF4ET33JHf1u5Gmzqx+eO00pfhVvkyz6LYImkE8ky/+
bXyJY8iDq7dxtfqhzZbeNe4fafU/avXxTA5UkWTnYhCqyd2bvAYH3Ep3L7lSv6SQ
Tsy0jjTsWJoSq6jRIzJuo2mX2MBKPBfLZs4tH71/4RppECletNnS4ZlxiV4LNrWE
LrXQvE1V+mJ82muucIe7w52nf3UWjQqTA73+Ml0aK+lIhbckRIovAw1sGzRrbTEX
xLCgz7BYDMhs5mgtfiMAzGox4xGxi56Ge519vdbddan4G92mPlLl1IPOXkO8GyLO
D4IiPp5ilPy6uThuxxIFemxxUREbPrfLJNA8W7aRPrHz4YcgZhrAV9I4C+xE0ukB
i8MoJeFvbCGPyTwVDn8XfFKynlZYm1f8NIVMSj5JfV8J3Om9jzDp6hx+52iUQEbW
C9g4kfPZY8h0RMggdOlZsaR8j26xuW+fEtz7ucJIqfJ/ElTH+4bm8MK2qPZniRWv
ej2md4bP4Bo5DXydzxz7O7TBL6/Jsp7pJhHUUb36OnTWvInyg71LPT1QIxdRvXr9
vNhrEBDX3MNf7RyXczvBcc+cLRo+zV+T4b8wd2kwXskWgKrGUJEe2TItdsafaQ9B
BkuVGu6cIDa6STyCJiOB68KIXiDuADSWJiiR+gZr4eU6vzfhR27LMQt/g+oPW+U4
1AvzUl9uXjTMC2vFuTQ4M2g0WmksCNpPpzOu/QlBmRqpP2Fg9UuLv6ITWeCxp439
g5NT5KXE2IiruL/DS0KEpWVNe4ayGzRvMawFuU582xbIzXjvilUZrW+p6req+oeF
QWTWHGDojTvULQPV2c91lWnLaNXVethfF4hrM5MIL+EwVs3sUXFMr1kX7VNrK0QH
Uos7nc4G9xngpdvwF4ImldD83O8qxOVzIfk5Dhz+etTH4HbnnDvmQ/FIYvjzGviR
ZeVwdCjv/9TC4yY3nJFKMwGp70jVa1vbmWL68HVNRyAVwnQnu2UlI8UR43iVZyUH
ZY0Nr0rbse/pvZyX4//EVOFLUR7K4GG0N4Kz41q5ZB8rI4Fl6QJhgIFJds13iM77
n+wqLQE7kllgI32E4U9B
=YlXg
-----END PGP MESSAGE-----
--===============9060418334135509864==--

View file

@ -0,0 +1,53 @@
2010-12-29 François Boulogne ─►[aur-general] Guidelines: cp, mkdir vs install ( ui)
2010-12-16 Olivier Berger ─►Essai accentué ( ui)
2009-11-18 Chris Wilson ─►[notmuch] [PATCH 1/2] Makefile: evaluate pkg-config once ( ui)
2009-11-18 Alex Botero-Lowry ┬►[notmuch] [PATCH] Error out if no query is supplied to search instead of going into an infinite loop (& ui)
2009-11-18 Carl Worth ╰─►[notmuch] [PATCH] Error out if no query is supplied to search instead of going into an infinite loop ( ui)
2009-11-17 Ingmar Vanhassel ┬►[notmuch] [PATCH] Typsos ( ui)
2009-11-18 Carl Worth ╰─► ... ( ui)
2009-11-17 Adrian Perez de Cast ┬►[notmuch] Introducing myself ( =ui)
2009-11-18 Keith Packard ├─► ... ( ui)
2009-11-18 Carl Worth ╰─► ... ( ui)
2009-11-17 Israel Herraiz ┬►[notmuch] New to the list ( ui)
2009-11-18 Keith Packard ├─► ... ( ui)
2009-11-18 Carl Worth ╰─► ... ( ui)
2009-11-17 Jan Janak ┬►[notmuch] What a great idea! ( ui)
2009-11-17 Jan Janak ├─► ... ( ui)
2009-11-18 Carl Worth ╰─► ... ( ui)
2009-11-17 Jan Janak ┬►[notmuch] [PATCH] Older versions of install do not support -C. ( ui)
2009-11-18 Carl Worth ╰─► ... ( ui)
2009-11-17 Aron Griffis ┬►[notmuch] archive ( ui)
2009-11-18 Keith Packard ╰┬► ... ( ui)
2009-11-18 Carl Worth ╰─► ... ( ui)
2009-11-17 Keith Packard ┬►[notmuch] [PATCH] Make notmuch-show 'X' (and 'x') commands remove inbox (and unread) tags ( ui)
2009-11-18 Carl Worth ╰─►[notmuch] [PATCH] Make notmuch-show 'X' (and 'x') commands remove inbox (and unread) tags ( ui)
2009-11-17 Lars Kellogg-Stedman ┬►[notmuch] Working with Maildir storage? ( = i)
2009-11-17 Mikhail Gusarov ├┬► ... ( =ui)
2009-11-17 Lars Kellogg-Stedman │╰┬► ... ( =ui)
2009-11-17 Mikhail Gusarov │ ├─► ... ( ui)
2009-11-17 Keith Packard │ ╰┬► ... ( ui)
2009-11-18 Lars Kellogg-Stedman │ ╰─► ... ( =ui)
2009-11-18 Carl Worth ╰─► ... ( ui)
2009-11-17 Mikhail Gusarov ┬►[notmuch] [PATCH 1/2] Close message file after parsing message headers ( i)
2009-11-17 Mikhail Gusarov ├─►[notmuch] [PATCH 2/2] Include <stdint.h> to get uint32_t in C++ file with gcc 4.4 ( ui)
2009-11-17 Carl Worth ╰┬►[notmuch] [PATCH 1/2] Close message file after parsing message headers ( ui)
2009-11-17 Keith Packard ╰┬► ... ( ui)
2009-11-18 Carl Worth ╰─► ... ( ui)
2009-11-18 Keith Packard ┬►[notmuch] [PATCH] Create a default notmuch-show-hook that highlights URLs and uses word-wrap ( ui)
2009-11-18 Alexander Botero-Low ╰─►[notmuch] [PATCH] Create a default notmuch-show-hook that highlights URLs and uses word-wrap ( ui)
2009-11-18 Alexander Botero-Low ─►[notmuch] request for pull ( ui)
2009-11-18 Jjgod Jiang ┬►[notmuch] Mac OS X/Darwin compatibility issues ( ui)
2009-11-18 Alexander Botero-Low ╰┬► ... ( ui)
2009-11-18 Jjgod Jiang ╰┬► ... ( ui)
2009-11-18 Alexander Botero-Low ╰─► ... ( ui)
2009-11-18 Rolland Santimano ─►[notmuch] Link to mailing list archives ? ( ui)
2009-11-18 Jan Janak ─►[notmuch] [PATCH] notmuch new: Support for conversion of spool subdirectories into tags ( ui)
2009-11-18 Stewart Smith ─►[notmuch] [PATCH] count_files: sort directory in inode order before statting ( ui)
2009-11-18 Stewart Smith ─►[notmuch] [PATCH 2/2] Read mail directory in inode number order ( ui)
2009-11-18 Stewart Smith ─►[notmuch] [PATCH] Fix linking with gcc to use g++ to link in C++ libs. ( ui)
2009-11-18 Lars Kellogg-Stedman ┬►[notmuch] "notmuch help" outputs to stderr? (&=ui)
2009-11-18 Lars Kellogg-Stedman ╰─► ... (&=ui)
2009-11-17 Mikhail Gusarov ─►[notmuch] [PATCH] Handle rename of message file ( ui)
2009-11-17 Alex Botero-Lowry ┬►[notmuch] preliminary FreeBSD support (& ui)
2009-11-17 Carl Worth ╰─► ... ( ui)
End of search results.

View file

@ -0,0 +1,53 @@
2010-12-29 François Boulogne [aur-general] Guidelines: cp, mkdir vs install ( ui)
2010-12-16 Olivier Berger Essai accentué ( ui)
2009-11-18 Chris Wilson [notmuch] [PATCH 1/2] Makefile: evaluate pkg-config once( ui)
2009-11-18 Carl Worth [notmuch] [PATCH] Error out if no query is supplied to search instead of going into an infinite loop( ui)
2009-11-18 Carl Worth [notmuch] [PATCH] Typsos ( ui)
2009-11-18 Carl Worth [notmuch] Introducing myself ( ui)
2009-11-18 Carl Worth [notmuch] New to the list ( ui)
2009-11-18 Carl Worth [notmuch] What a great idea! ( ui)
2009-11-18 Carl Worth [notmuch] [PATCH] Older versions of install do not support -C.( ui)
2009-11-18 Carl Worth [notmuch] archive ( ui)
2009-11-18 Carl Worth [notmuch] [PATCH] Make notmuch-show 'X' (and 'x') commands remove inbox (and unread) tags( ui)
2009-11-18 Carl Worth [notmuch] Working with Maildir storage? ( ui)
2009-11-18 Carl Worth [notmuch] [PATCH 1/2] Close message file after parsing message headers( ui)
2009-11-18 Alexander Botero-Low[notmuch] [PATCH] Create a default notmuch-show-hook that highlights URLs and uses word-wrap( ui)
2009-11-18 Keith Packard [notmuch] [PATCH] Create a default notmuch-show-hook that highlights URLs and uses word-wrap( ui)
2009-11-18 Alexander Botero-Low[notmuch] request for pull ( ui)
2009-11-18 Alexander Botero-Low[notmuch] Mac OS X/Darwin compatibility issues ( ui)
2009-11-18 Jjgod Jiang [notmuch] Mac OS X/Darwin compatibility issues ( ui)
2009-11-18 Alexander Botero-Low[notmuch] Mac OS X/Darwin compatibility issues ( ui)
2009-11-18 Rolland Santimano [notmuch] Link to mailing list archives ? ( ui)
2009-11-18 Jan Janak [notmuch] [PATCH] notmuch new: Support for conversion of spool subdirectories into tags( ui)
2009-11-18 Jjgod Jiang [notmuch] Mac OS X/Darwin compatibility issues ( ui)
2009-11-18 Stewart Smith [notmuch] [PATCH] count_files: sort directory in inode order before statting( ui)
2009-11-18 Keith Packard [notmuch] archive ( ui)
2009-11-18 Keith Packard [notmuch] Introducing myself ( ui)
2009-11-18 Keith Packard [notmuch] New to the list ( ui)
2009-11-18 Stewart Smith [notmuch] [PATCH 2/2] Read mail directory in inode number order( ui)
2009-11-18 Stewart Smith [notmuch] [PATCH] Fix linking with gcc to use g++ to link in C++ libs.( ui)
2009-11-18 Lars Kellogg-Stedman[notmuch] "notmuch help" outputs to stderr? (&=ui)
2009-11-18 Lars Kellogg-Stedman[notmuch] "notmuch help" outputs to stderr? (&=ui)
2009-11-18 Lars Kellogg-Stedman[notmuch] Working with Maildir storage? ( =ui)
2009-11-18 Alex Botero-Lowry [notmuch] [PATCH] Error out if no query is supplied to search instead of going into an infinite loop(& ui)
2009-11-17 Ingmar Vanhassel [notmuch] [PATCH] Typsos ( ui)
2009-11-17 Aron Griffis [notmuch] archive ( ui)
2009-11-17 Adrian Perez de Cast[notmuch] Introducing myself ( =ui)
2009-11-17 Israel Herraiz [notmuch] New to the list ( ui)
2009-11-17 Jan Janak [notmuch] What a great idea! ( ui)
2009-11-17 Jan Janak [notmuch] What a great idea! ( ui)
2009-11-17 Jan Janak [notmuch] [PATCH] Older versions of install do not support -C.( ui)
2009-11-17 Keith Packard [notmuch] [PATCH] Make notmuch-show 'X' (and 'x') commands remove inbox (and unread) tags( ui)
2009-11-17 Keith Packard [notmuch] Working with Maildir storage? ( ui)
2009-11-17 Keith Packard [notmuch] [PATCH 1/2] Close message file after parsing message headers( ui)
2009-11-17 Mikhail Gusarov [notmuch] [PATCH] Handle rename of message file ( ui)
2009-11-17 Mikhail Gusarov [notmuch] Working with Maildir storage? ( ui)
2009-11-17 Lars Kellogg-Stedman[notmuch] Working with Maildir storage? ( =ui)
2009-11-17 Carl Worth [notmuch] preliminary FreeBSD support ( ui)
2009-11-17 Alex Botero-Lowry [notmuch] preliminary FreeBSD support (& ui)
2009-11-17 Mikhail Gusarov [notmuch] Working with Maildir storage? ( =ui)
2009-11-17 Lars Kellogg-Stedman[notmuch] Working with Maildir storage? ( =ui)
2009-11-17 Carl Worth [notmuch] [PATCH 1/2] Close message file after parsing message headers( ui)
2009-11-17 Mikhail Gusarov [notmuch] [PATCH 2/2] Include <stdint.h> to get uint32_t in C++ file with gcc 4.4( ui)
2009-11-17 Mikhail Gusarov [notmuch] [PATCH 1/2] Close message file after parsing message headers( ui)
End of search results.

View file

@ -0,0 +1,25 @@
ui 2010-12-29 [1/1] François Boulogne [aur-general] Guidelines: cp, mkdir vs install (inbox unread)
ui 2010-12-16 [1/1] Olivier Berger Essai accentué (inbox unread)
ui 2009-11-18 [1/1] Chris Wilson [notmuch] [PATCH 1/2] Makefile: evaluate pkg-config once (inbox unread)
& ui 2009-11-18 [2/2] Alex Botero-Lowry, Carl Worth [notmuch] [PATCH] Error out if no query is supplied to search instead of going into an infinite loop (attachment inbox unread)
ui 2009-11-18 [2/2] Ingmar Vanhassel, Carl Worth [notmuch] [PATCH] Typsos (inbox unread)
=ui 2009-11-18 [3/3] Adrian Perez de Castro, Keith Packard, Carl Worth [notmuch] Introducing myself (inbox signed unread)
ui 2009-11-18 [3/3] Israel Herraiz, Keith Packard, Carl Worth [notmuch] New to the list (inbox unread)
ui 2009-11-18 [3/3] Jan Janak, Carl Worth [notmuch] What a great idea! (inbox unread)
ui 2009-11-18 [2/2] Jan Janak, Carl Worth [notmuch] [PATCH] Older versions of install do not support -C. (inbox unread)
ui 2009-11-18 [3/3] Aron Griffis, Keith Packard, Carl Worth [notmuch] archive (inbox unread)
ui 2009-11-18 [2/2] Keith Packard, Carl Worth [notmuch] [PATCH] Make notmuch-show 'X' (and 'x') commands remove inbox (and unread) tags (inbox unread)
=ui 2009-11-18 [7/7] Lars Kellogg-Stedman, Mikhail Gusarov, Keith Packard, Carl Worth [notmuch] Working with Maildir storage? (inbox signed unread)
ui 2009-11-18 [5/5] Mikhail Gusarov, Carl Worth, Keith Packard [notmuch] [PATCH 1/2] Close message file after parsing message headers (inbox unread)
ui 2009-11-18 [2/2] Keith Packard, Alexander Botero-Lowry [notmuch] [PATCH] Create a default notmuch-show-hook that highlights URLs and uses word-wrap (inbox unread)
ui 2009-11-18 [1/1] Alexander Botero-Lowry [notmuch] request for pull (inbox unread)
ui 2009-11-18 [4/4] Jjgod Jiang, Alexander Botero-Lowry [notmuch] Mac OS X/Darwin compatibility issues (inbox unread)
ui 2009-11-18 [1/1] Rolland Santimano [notmuch] Link to mailing list archives ? (inbox unread)
ui 2009-11-18 [1/1] Jan Janak [notmuch] [PATCH] notmuch new: Support for conversion of spool subdirectories into tags (inbox unread)
ui 2009-11-18 [1/1] Stewart Smith [notmuch] [PATCH] count_files: sort directory in inode order before statting (inbox unread)
ui 2009-11-18 [1/1] Stewart Smith [notmuch] [PATCH 2/2] Read mail directory in inode number order (inbox unread)
ui 2009-11-18 [1/1] Stewart Smith [notmuch] [PATCH] Fix linking with gcc to use g++ to link in C++ libs. (inbox unread)
&=ui 2009-11-18 [2/2] Lars Kellogg-Stedman [notmuch] "notmuch help" outputs to stderr? (attachment inbox signed unread)
ui 2009-11-17 [1/1] Mikhail Gusarov [notmuch] [PATCH] Handle rename of message file (inbox unread)
& ui 2009-11-17 [2/2] Alex Botero-Lowry, Carl Worth [notmuch] preliminary FreeBSD support (attachment inbox unread)
End of search results.

View file

@ -122,7 +122,8 @@ const notmuch_opt_desc_t notmuch_shared_options[] = {
const char *notmuch_requested_db_uuid = NULL;
void
notmuch_process_shared_options (unused (const char *dummy))
notmuch_process_shared_options (unused (notmuch_database_t *notmuch),
unused (const char *dummy))
{
}

62
test/smime/0xE0972A47.p12 Normal file
View file

@ -0,0 +1,62 @@
Issuer ...: /CN=Notmuch Test Suite
Serial ...: 6F748C94BD0C67A9
Subject ..: /CN=Notmuch Test Suite
aka ..: test_suite@notmuchmail.org
Keygrip ..: 1727B9C7108D50333614F3B1DD0807F624B31130
-----BEGIN PKCS12-----
MIIJ+AIBAzCCCb4GCSqGSIb3DQEHAaCCCa8EggmrMIIJpzCCBAcGCSqGSIb3DQEH
BqCCA/gwggP0AgEAMIID7QYJKoZIhvcNAQcBMBwGCiqGSIb3DQEMAQYwDgQIcfMY
MS7tOpcCAggAgIIDwFu7ZRNrXCb0eKei44aeBZPRs9YI/5EpMcFuc8j4/8T1HkIt
GuRe/HzRmoiLZcAMOzGC/hF8TkHlNeUZ7rOSpCg4UlBVWJS6avTMHHsakDvTV/7q
X5VNi4pLUuyEToGTAPHV+s5P/gYYG6mFPkwG/pDDlAcgMhgtuPY/lQp6IS/E6CaR
fhcnQiPq9ySTqO7UNwIyMwtAtSHkgBaje8UbOkQch4lg51i97rm9m4EMvklKtjXc
Ud4aTEuoZguPmdBdLvF5QxqJf6Bm9lHa1Awhru2gBWQf9TjX8bwK9Xsv8G6gPOwc
LVpIR9fMZtgBbc+heeJTjfn6VqEy881ckbkz+38hiN3pbLMuATM7QAY3u3N4whM6
Hmfyl3iqba84Pl93zaUzqazAUeFdqcqSpAUGkS4gU6klr9qi3NicaGbry1DySYU7
2h4xy3j7eiHxqdWaibdPoBC8CEbPaFj2qnOVsZykxG6zPvbEB+5sJ/a+T6xm1Btx
N6vXR7ObbXlpC4pRkS32ehuRbY6wc6H2KKepOMCu7x10tN0Up5ccNxvkT26QIrEE
LW296ijCLbsRhWymDtopWAZHcXXIu0fJ4tocSp2c3lojSEYu1jlMXR+Pa4R8EtgZ
lb5+NqISxjUlMMWzGDyhrp9ImcsZmpv6N8zPcZVyU+M1/h+p9ur/IOVZU9P1vIKy
kcM4pslr0JhLfnZCLZ+3Ux1yKAcndGZFPb1vZ83jyZKR38BVSGu53ODaBJBqSMHu
Mv2Na/qzvQBSVJuWF9cAhiVd7v9R/EvT0zmljN4w7l4EXsB5wRsO1wvlL+MhwaET
dIHbRH2GD3gERX6oTc3t3cgritVePk70rCxQDxn5zUbjW7dNIlIobAumLHBfgSxR
QCE6gxdTm5MW2O9hnfTSQvliVaGU1gd0M3BRiqeNpPPxnloGKnOEODM381F4HxyR
CzO2r/2aKJP+U5HxSf4cljp3/Lripxykzfqc9/xZshl+jGixsSSm+Ul916Hpj2Rt
j9vHg4H9YfJTGdvzxZcvZCvNSy3ygtjx0++SrI5hGHKjpVJIK2/9Wi39q5s6LkiA
RCjvuoBBcQXm++69X7QGWSsGFtwerCGnq3nAxGpHVKVGTvFYMAg6y1RR0zvE0SuM
MZegD8w45QyrmiPqSRM7/RtqVdA+r/wiJwWerUBq+mrCvJHB2NRcjiUiCJY1bjRU
ATMfB0uZaNInUXiLDGxp2mdBgdFVq7sYTbq+OvprzxeAjIvodxl3J9ThvJnt1fzK
RPCJw5COI60ibE3XTTCCBZgGCSqGSIb3DQEHAaCCBYkEggWFMIIFgTCCBX0GCyqG
SIb3DQEMCgECoIIE7jCCBOowHAYKKoZIhvcNAQwBAzAOBAiEe8CcxIIv9wICCAAE
ggTIujut93lYPUsKc/JNhZhUWS/RHHog6d8ZAjpFvXpyD8Z2z4A4PpgIn8eUSRW5
Gwp8izR+16Tj3ht52pJ5Y1x27/S3l3sDlekEZ/33X/AdLFWAXbcibmwtRea1ucKZ
ze3DJM7CvuRvVSBG8XubPGi3pZkEjHBGQqgtsTnxlBp0PXl7wxfyT7F6gOH2DGYP
bYzNa2fnY8twEcUYhuksI/eh9Zwj9TrF0HWq1hwp0tDCfqutzshSX2GQ/p0raL3B
C2stHBjl0OVUfDHpqQ5OJWbQvGcJntECqu4gmSJohunObaUKcN8xs+FzB5czpmsT
W/pyR58nc8QhTttByqZN3EerhEogWDZj4tQ6dK8p6bqLO/0qqBehZGchfof5Evwj
VFsvVGD8xVLQWWAFnrQs5+U56NQEbmZzN5RCI7FEK2VVOeG03dpXyoAQyxuYrsYU
3znmoSleIqDDBFD21YePUcJZ0R8AQsvgV11tdwPWqr1hk0bIazLQ9rappGrTgkK8
DFdQKSH1dRvjqtbuDyY7j5PXXJTXthVv9T9N7Vp6qU+pWBQ1Mz30J+fHX2ilEnbi
tQ49hwt1+/2Zkmwz3reoEnxYOKzCg/ySIpQ27/Hx4xZ+ecEzX/0IxCkHeAV3V3bB
1z8wFxWEh1s9hL6C8lRk/wQ9KsKaxM7BdLw7RjiqEwR4HgeCqMPdCVQQpILARDC8
Poz8xUmjv7HyIvvyBUP12YdIj74Jjj0Mm2r/FDj7nsXxkjXMZEMMKK3oVaAMq8Bd
cO4VQXDd7bgNzLF9PKxWNjoCuQcPJXwMPqlFoc06BLPstEaR4enafv0Pd4l0pyME
YgezyVW+3yFEsbbB2UUs0r7oqxsDFU9/iHf8O3nu3NuKTJkux4uMlOTBKsm6sY7k
GduP2UA+WU27jHrf4zQQbkDLG1lJFfcaKzlcOmz5B9iZwugBz9Y28w5f2/12Kqrh
4tibFBUG0E85KAb1wnFUNUx06OMX229U1M0E1LHbcUJ9mcRipONPVn0FRi8XzaLK
023XRoihuoWhVUiB1OJ2eZW1JnUYRztfa3nfmGjXv4VGkxYlnTkE9z0PAAhf6t5A
7Ir0y1JUeOlBITTcojOp6qQ8tMQQ5wRk1oncHiw3WwJvFN6fOa9Q/+4ZmULHz0vV
Xl+Qio8B7/4jqZoT4e/gK6U/zHriznLzqp63LjP47eFRXTfuXslaCt7YF75Mq2J6
VPA+qfYRw0K5BvDUkr8c+nLP2AiDaEYVBHGdBRTlWO9UkcB1F4cuZZiU5MZbxVrb
Db+zGWW6AT+4XTO4z9KmAqgTTv1+BQrLxNI+RG8JfQapUKQyB794F4kXK2yhd1P3
XS9cwh24COiqbOpI1nB5qn7cn4RRHW156LWGF+VJFdxR6Wu3vZx/kZGevG9o1ARF
z1l9mbGyhwnUJO1EQwjbppvRou1bZuNbuRgLmHKEVPAv+J+7hLXZAnRdwoV0x91t
bpmy4qyxA/90DHguIhRVcKsYBrdShY7LXdZArECBhMY9R41D6v1yyhC6fL6PKR5g
DaluN2K9TBALzZH7NnNdE14l+56+kLc9Fq8JXsq3rxdeBTsNl09fHPf9w5VLkq4I
doNcPPlta0Q0xJNa/RYENCJpAMZdMFIJ558uMXwwVQYJKoZIhvcNAQkUMUgeRgBH
AG4AdQBQAEcAIABlAHgAcABvAHIAdABlAGQAIABjAGUAcgB0AGkAZgBpAGMAYQB0
AGUAIABlADAAOQA3ADIAYQA0ADcwIwYJKoZIhvcNAQkVMRYEFGFvRs1zg0xjhHdW
rw37ZKbglypHMDEwITAJBgUrDgMCGgUABBSluQBa+tVpYVYmB/zAZuPE9NnargQI
XWSQTDEONWgCAggA
-----END PKCS12-----

View file

@ -159,6 +159,33 @@ running, quit if it terminated."
(lambda (x) `(prog1 ,x (notmuch-post-command)))
body)))
;; For testing functions in
;; notmuch-{search,tree,unsorted}-result-format
(defun notmuch-test-result-flags (format-string result)
(let ((tags-to-letters (quote (("attachment" . "&")
("signed" . "=")
("unread" . "u")
("inbox" . "i"))))
(tags (plist-get result :tags)))
(format format-string
(mapconcat (lambda (t2l)
(if (member (car t2l) tags)
(cdr t2l)
" "))
tags-to-letters ""))))
;; Log any signalled error (and other messages) to MESSAGES
;; Log "COMPLETE" if forms complete without error.
(defmacro test-log-error (&rest body)
`(progn
(with-current-buffer "*Messages*"
(let ((inhibit-read-only t)) (erase-buffer)))
(condition-case err
(progn ,@body
(message "COMPLETE"))
(t (message "%s" err)))
(with-current-buffer "*Messages*" (test-output "MESSAGES"))))
;; For historical reasons, we hide deleted tags by default in the test
;; suite
(setq notmuch-tag-deleted-formats

View file

@ -143,10 +143,8 @@ add_gpgsm_home () {
_gnupg_exit () { gpgconf --kill all 2>/dev/null || true; }
at_exit_function _gnupg_exit
mkdir -p -m 0700 "$GNUPGHOME"
openssl pkcs12 -export -passout pass: -inkey "$NOTMUCH_SRCDIR/test/smime/key+cert.pem" \
< "$NOTMUCH_SRCDIR/test/smime/test.crt" | \
gpgsm --batch --no-tty --no-common-certs-import --pinentry-mode=loopback --passphrase-fd 3 \
--disable-dirmngr --import >"$GNUPGHOME"/import.log 2>&1 3<<<''
gpgsm --batch --no-tty --no-common-certs-import --pinentry-mode=loopback --passphrase-fd 3 \
--disable-dirmngr --import >"$GNUPGHOME"/import.log 2>&1 3<<<'' <$NOTMUCH_SRCDIR/test/smime/0xE0972A47.p12
fpr=$(gpgsm --batch --list-key test_suite@notmuchmail.org | sed -n 's/.*fingerprint: //p')
echo "$fpr S relax" >> "$GNUPGHOME/trustlist.txt"
gpgsm --quiet --batch --no-tty --no-common-certs-import --disable-dirmngr --import < $NOTMUCH_SRCDIR/test/smime/ca.crt
@ -911,7 +909,7 @@ test_done () {
test_python () {
# Note: if there is need to print debug information from python program,
# use stdout = os.fdopen(6, 'w') or stderr = os.fdopen(7, 'w')
PYTHONPATH="$NOTMUCH_SRCDIR/bindings/python${PYTHONPATH:+:$PYTHONPATH}" \
PYTHONPATH="$NOTMUCH_BUILDDIR/bindings/python-cffi/build/stage:$NOTMUCH_SRCDIR/bindings/python${PYTHONPATH:+:$PYTHONPATH}" \
$NOTMUCH_PYTHON -B - > OUTPUT
}

View file

@ -4,9 +4,16 @@
#include <stdbool.h>
#include <gmodule.h>
#ifdef __cplusplus
extern "C" {
#endif
/* The utf8 encoded string would tokenize as a single word, according
* to xapian. */
bool unicode_word_utf8 (const char *str);
typedef gunichar notmuch_unichar;
#ifdef __cplusplus
}
#endif
#endif

View file

@ -1 +1 @@
0.33.1
0.34.2