From: David Bremner Date: Wed, 16 Feb 2022 00:53:12 +0000 (-0400) Subject: Merge tag '0.35' into debian/bullseye-backports X-Git-Tag: archive/debian/0.35-1_bpo11+1^0 X-Git-Url: https://git.cworth.org/git?a=commitdiff_plain;h=f1b2ab70c39cacb53c0b3c1d49358260a8d1818d;hp=cf342d7302544532a1f66fd7a1cc42df99fcd228;p=notmuch Merge tag '0.35' into debian/bullseye-backports notmuch 0.35 release --- diff --git a/.gitignore b/.gitignore index 3edd1768..f846ebec 100644 --- a/.gitignore +++ b/.gitignore @@ -16,5 +16,6 @@ /sh.config /sphinx.config /version.stamp +/bindings/python-cffi/_notmuch_config.py TAGS tags diff --git a/Makefile.global b/Makefile.global index fe79121d..7a7a3c6d 100644 --- a/Makefile.global +++ b/Makefile.global @@ -50,9 +50,9 @@ DETACHED_SIG_FILE=$(TAR_FILE).asc PV_FILE=bindings/python/notmuch/version.py # Smash together user's values with our extra values -FINAL_CFLAGS = -DNOTMUCH_VERSION=$(VERSION) $(CPPFLAGS) $(CFLAGS) $(WARN_CFLAGS) $(extra_cflags) $(CONFIGURE_CFLAGS) -FINAL_CXXFLAGS = $(CPPFLAGS) $(CXXFLAGS) $(WARN_CXXFLAGS) $(extra_cflags) $(extra_cxxflags) $(CONFIGURE_CXXFLAGS) -FINAL_NOTMUCH_LDFLAGS = $(LDFLAGS) -Lutil -lnotmuch_util -Llib -lnotmuch +FINAL_CFLAGS = -DNOTMUCH_VERSION=$(VERSION) $(WARN_CFLAGS) $(extra_cflags) $(CPPFLAGS) $(CONFIGURE_CFLAGS) $(CFLAGS) +FINAL_CXXFLAGS = $(WARN_CXXFLAGS) $(extra_cflags) $(extra_cxxflags) $(CPPFLAGS) $(CONFIGURE_CXXFLAGS) $(CXXFLAGS) +FINAL_NOTMUCH_LDFLAGS = -Lutil -lnotmuch_util -Llib -lnotmuch $(LDFLAGS) ifeq ($(LIBDIR_IN_LDCONFIG),0) FINAL_NOTMUCH_LDFLAGS += $(RPATH_LDFLAGS) endif diff --git a/Makefile.local b/Makefile.local index e12b94cd..10fb9908 100644 --- a/Makefile.local +++ b/Makefile.local @@ -54,7 +54,6 @@ update-versions: sed -i -e "s/^__VERSION__[[:blank:]]*=.*$$/__VERSION__ = \'${VERSION}\'/" \ -e "s/^SOVERSION[[:blank:]]*=.*$$/SOVERSION = \'${LIBNOTMUCH_VERSION_MAJOR}\'/" \ ${PV_FILE} - cp version.txt bindings/python-cffi # We invoke make recursively only to force ordering of our phony # targets in the case of parallel invocation of make (-j). diff --git a/NEWS b/NEWS index 27e43156..c6ce2eea 100644 --- a/NEWS +++ b/NEWS @@ -1,3 +1,109 @@ +Notmuch 0.35 (2022-02-06) +========================= + +Library +------- + +Implement the `date` and `lastmod` fields in the S-expression parser. + +Ignore trailing `/` for pathnames in both query parsers. + +Rename configuration option `built_with.sexpr_query` to +`built_with.sexp_queries`. + +Do not assume a default mail root in split (e.g. XDG) configurations. + +Fix some small memory leaks in `notmuch_database_open_with_config`. + +CLI +--- + +Improve handling of leading/trailing punctation and space for +configuration lists. + +Only ignore `.notmuch` at the top level in `notmuch new`. + +Optionally show extra headers in `notmuch show`. See +`show.extra_headers` in notmuch-config(1). + +Emacs +----- + +Drop `C-TAB` binding in hello mode, document `backtab`. + +Fix visual glitch in search mode by running `notmuch-search-hook` +lazily. + +Don't add space to completion candidates, improves compatibility with +third party completion frameworks. + +Make citation formating more robust against whitespace. + +Use `--excludes=false` when generating the 'All tags' section. + +Use cached copy of message body for `Fcc`, avoiding variant bodies for +signed and/or encrypted messages. + +Add notmuch-logo.svg and use it in notmuch-hello view, replacing +the .png version. + +Make header line in show buffers optional. + +Add customizable names for search buffers. + +Build +----- + +Fix out-of-tree build for `python-cffi` bindings. + +Rearrange position of {C,CXX,CPP,LD}FLAGS, prevent some clashes with +installed version of notmuch. + +Ignore more configure options. + +Test Suite +---------- + +Replace some uses of `gdb` in the test suite with `LD_PRELOAD` based +shims. + +Use `--with-colons` for gpgsm, fix compatibility with newer gnupg. + +Python bindings +--------------- + +Add `matched` property to message objects. + +Users are reminded that the old python bindings in bindings/python are +deprecated; this will probably be the last major release that ships +them. + +Completion +---------- + +Use `database.mail_root` for path completion in bash/zsh. + +Notmuch 0.34.3 (2022-01-09) +=========================== + +Library +------- + +Do not crash when presented with a .notmuch directory without a +xapian/ subdirectory. + +Python Bindings (notmuch2) +-------------------------- + +Database constructor now searches for configuration by default. Pass +`config=Database.CONFIG.EMPTY` to disable. + +The `Message.replies()` method now returns OwnedMessage objects, to +prevent certain memory de-allocation errors. + +Fix for importing `notmuch2` module when building bindings +documentation. + Notmuch 0.34.2 (2021-12-09) =========================== diff --git a/bindings/Makefile.local b/bindings/Makefile.local index 3672e69f..7b10af08 100644 --- a/bindings/Makefile.local +++ b/bindings/Makefile.local @@ -3,21 +3,26 @@ dir := bindings # force the shared library to be built -ruby-bindings: lib/$(LINKER_NAME) +ruby-bindings: $(dir)/ruby.stamp + +$(dir)/ruby.stamp: lib/$(LINKER_NAME) ifeq ($(HAVE_RUBY_DEV),1) cd $(dir)/ruby && \ EXTRA_LDFLAGS="$(NO_UNDEFINED_LDFLAGS)" \ LIBNOTMUCH="../../lib/$(LINKER_NAME)" \ NOTMUCH_SRCDIR='$(NOTMUCH_SRCDIR)' \ $(RUBY) extconf.rb --vendor - $(MAKE) -C $(dir)/ruby CFLAGS="$(CFLAGS) -pipe -fno-plt -fPIC" + $(MAKE) -C $(dir)/ruby CFLAGS="$(CFLAGS) -pipe -fno-plt -fPIC" && touch $@ endif -python-cffi-bindings: lib/$(LINKER_NAME) +python-cffi-bindings: $(dir)/python-cffi.stamp + +$(dir)/python-cffi.stamp: lib/$(LINKER_NAME) ifeq ($(HAVE_PYTHON3_CFFI),1) cd $(dir)/python-cffi && \ ${PYTHON} setup.py build --build-lib build/stage && \ - mkdir -p build/stage/tests && cp tests/*.py build/stage/tests + mkdir -p build/stage/tests && cp tests/*.py build/stage/tests && \ + touch ../python-cffi.stamp endif CLEAN += $(patsubst %,$(dir)/ruby/%, \ @@ -26,6 +31,6 @@ CLEAN += $(patsubst %,$(dir)/ruby/%, \ init.o message.o messages.o mkmf.log notmuch.so query.o \ status.o tags.o thread.o threads.o) -CLEAN += bindings/ruby/.vendorarchdir.time +CLEAN += bindings/ruby/.vendorarchdir.time $(dir)/ruby.stamp -CLEAN += bindings/python-cffi/build +CLEAN += bindings/python-cffi/build $(dir)/python-cffi.stamp diff --git a/bindings/python-cffi/notmuch2/_build.py b/bindings/python-cffi/notmuch2/_build.py index f6184b97..a55b484f 100644 --- a/bindings/python-cffi/notmuch2/_build.py +++ b/bindings/python-cffi/notmuch2/_build.py @@ -1,5 +1,5 @@ import cffi - +from _notmuch_config import * ffibuilder = cffi.FFI() ffibuilder.set_source( @@ -16,8 +16,8 @@ ffibuilder.set_source( #ERROR libnotmuch version < 5.1 not supported #endif """, - include_dirs=['../../lib'], - library_dirs=['../../lib'], + include_dirs=[NOTMUCH_INCLUDE_DIR], + library_dirs=[NOTMUCH_LIB_DIR], libraries=['notmuch'], ) ffibuilder.cdef( @@ -54,6 +54,7 @@ ffibuilder.cdef( NOTMUCH_STATUS_NO_DATABASE, NOTMUCH_STATUS_DATABASE_EXISTS, NOTMUCH_STATUS_BAD_QUERY_SYNTAX, + NOTMUCH_STATUS_NO_MAIL_ROOT, NOTMUCH_STATUS_LAST_STATUS } notmuch_status_t; typedef enum { diff --git a/bindings/python-cffi/notmuch2/_database.py b/bindings/python-cffi/notmuch2/_database.py index 14a8f15c..d7485b4d 100644 --- a/bindings/python-cffi/notmuch2/_database.py +++ b/bindings/python-cffi/notmuch2/_database.py @@ -139,7 +139,7 @@ class Database(base.NotmuchObject): path = os.fsencode(path) return path - def __init__(self, path=None, mode=MODE.READ_ONLY, config=CONFIG.EMPTY): + def __init__(self, path=None, mode=MODE.READ_ONLY, config=CONFIG.SEARCH): if isinstance(mode, str): mode = self.STR_MODE_MAP[mode] self.mode = mode diff --git a/bindings/python-cffi/notmuch2/_message.py b/bindings/python-cffi/notmuch2/_message.py index 2f232076..aa1cb875 100644 --- a/bindings/python-cffi/notmuch2/_message.py +++ b/bindings/python-cffi/notmuch2/_message.py @@ -205,6 +205,20 @@ class Message(base.NotmuchObject): self._msg_p, capi.lib.NOTMUCH_MESSAGE_FLAG_EXCLUDED) return bool(ret) + @property + def matched(self): + """Indicates whether this message was matched by the query. + + When a thread is created from a search, some of the + messages may not match the original query. This property + is set to *True* for those that do match. + + :raises ObjectDestroyedError: if used after destroyed. + """ + ret = capi.lib.notmuch_message_get_flag( + self._msg_p, capi.lib.NOTMUCH_MESSAGE_FLAG_MATCH) + return bool(ret) + @property def date(self): """The message date as an integer. @@ -357,14 +371,14 @@ class Message(base.NotmuchObject): This method will only work if the message was created from a thread. Otherwise it will yield no results. - :returns: An iterator yielding :class:`Message` instances. + :returns: An iterator yielding :class:`OwnedMessage` instances. :rtype: MessageIter """ # The notmuch_messages_valid call accepts NULL and this will # become an empty iterator, raising StopIteration immediately. # Hence no return value checking here. msgs_p = capi.lib.notmuch_message_get_replies(self._msg_p) - return MessageIter(self, msgs_p, db=self._db) + return MessageIter(self, msgs_p, db=self._db, msg_cls=OwnedMessage) def __hash__(self): return hash(self.messageid) diff --git a/bindings/python-cffi/setup.py b/bindings/python-cffi/setup.py index cda52338..55fb2d24 100644 --- a/bindings/python-cffi/setup.py +++ b/bindings/python-cffi/setup.py @@ -1,6 +1,7 @@ import setuptools +from _notmuch_config import * -with open('version.txt') as fp: +with open(NOTMUCH_VERSION_FILE) as fp: VERSION = fp.read().strip() setuptools.setup( diff --git a/bindings/python-cffi/tests/test_config.py b/bindings/python-cffi/tests/test_config.py index 1b2695f5..67b0dea4 100644 --- a/bindings/python-cffi/tests/test_config.py +++ b/bindings/python-cffi/tests/test_config.py @@ -23,9 +23,9 @@ class TestIter: def test_set_get(self, maildir): # Ensure get-set works from different db objects - with dbmod.Database.create(maildir.path) as db0: + with dbmod.Database.create(maildir.path, config=dbmod.Database.CONFIG.EMPTY) as db0: db0.config['spam'] = 'ham' - with dbmod.Database(maildir.path) as db1: + with dbmod.Database(maildir.path, config=dbmod.Database.CONFIG.EMPTY) as db1: assert db1.config['spam'] == 'ham' def test_get_keyerror(self, db): diff --git a/bindings/python-cffi/tests/test_database.py b/bindings/python-cffi/tests/test_database.py index 9b3219c0..f1d12ea6 100644 --- a/bindings/python-cffi/tests/test_database.py +++ b/bindings/python-cffi/tests/test_database.py @@ -13,7 +13,7 @@ import notmuch2._message as message @pytest.fixture def db(maildir): - with dbmod.Database.create(maildir.path) as db: + with dbmod.Database.create(maildir.path, config=notmuch2.Database.CONFIG.EMPTY) as db: yield db @@ -293,7 +293,7 @@ class TestQuery: maildir.deliver(body='baz', headers=[('In-Reply-To', '<{}>'.format(msgid))]) notmuch('new') - with dbmod.Database(maildir.path, 'rw') as db: + with dbmod.Database(maildir.path, 'rw', config=notmuch2.Database.CONFIG.EMPTY) as db: yield db def test_count_messages(self, db): diff --git a/bindings/python-cffi/tests/test_message.py b/bindings/python-cffi/tests/test_message.py index 532bf921..56701d05 100644 --- a/bindings/python-cffi/tests/test_message.py +++ b/bindings/python-cffi/tests/test_message.py @@ -97,6 +97,9 @@ class TestMessage: def test_ghost_no(self, msg): assert not msg.ghost + def test_matched_no(self,msg): + assert not msg.matched + def test_date(self, msg): # XXX Someone seems to treat things as local time instead of # UTC or the other way around. diff --git a/bindings/python-cffi/tests/test_tags.py b/bindings/python-cffi/tests/test_tags.py index faf3947b..f2c6209d 100644 --- a/bindings/python-cffi/tests/test_tags.py +++ b/bindings/python-cffi/tests/test_tags.py @@ -23,7 +23,7 @@ class TestImmutable: """ maildir.deliver() notmuch('new') - with database.Database(maildir.path) as db: + with database.Database(maildir.path, config=database.Database.CONFIG.EMPTY) as db: yield db.tags def test_type(self, tagset): @@ -33,7 +33,7 @@ class TestImmutable: def test_hash(self, tagset, maildir, notmuch): h0 = hash(tagset) notmuch('tag', '+foo', '*') - with database.Database(maildir.path) as db: + with database.Database(maildir.path, config=database.Database.CONFIG.EMPTY) as db: h1 = hash(db.tags) assert h0 != h1 @@ -42,7 +42,7 @@ class TestImmutable: def test_neq(self, tagset, maildir, notmuch): notmuch('tag', '+foo', '*') - with database.Database(maildir.path) as db: + with database.Database(maildir.path, config=database.Database.CONFIG.EMPTY) as db: assert tagset != db.tags def test_contains(self, tagset): @@ -159,7 +159,8 @@ class TestMutableTagset: _, pathname = maildir.deliver() notmuch('new') with database.Database(maildir.path, - mode=database.Mode.READ_WRITE) as db: + mode=database.Mode.READ_WRITE, + config=database.Database.CONFIG.EMPTY) as db: msg = db.get(pathname) yield msg.tags @@ -195,7 +196,8 @@ class TestMutableTagset: _, pathname = maildir.deliver(flagged=True) notmuch('new') with database.Database(maildir.path, - mode=database.Mode.READ_WRITE) as db: + mode=database.Mode.READ_WRITE, + config=database.Database.CONFIG.EMPTY) as db: msg = db.get(pathname) msg.tags.discard('flagged') msg.tags.from_maildir_flags() @@ -205,7 +207,8 @@ class TestMutableTagset: _, pathname = maildir.deliver(flagged=True) notmuch('new') with database.Database(maildir.path, - mode=database.Mode.READ_WRITE) as db: + mode=database.Mode.READ_WRITE, + config=database.Database.CONFIG.EMPTY) as db: msg = db.get(pathname) flags = msg.path.name.split(',')[-1] assert 'F' in flags diff --git a/bindings/python-cffi/tests/test_thread.py b/bindings/python-cffi/tests/test_thread.py index 1f44b35d..619d2aac 100644 --- a/bindings/python-cffi/tests/test_thread.py +++ b/bindings/python-cffi/tests/test_thread.py @@ -13,7 +13,7 @@ def thread(maildir, notmuch): maildir.deliver(body='bar', headers=[('In-Reply-To', '<{}>'.format(msgid))]) notmuch('new') - with notmuch2.Database(maildir.path) as db: + with notmuch2.Database(maildir.path, config=notmuch2.Database.CONFIG.EMPTY) as db: yield next(db.threads('foo')) @@ -57,6 +57,13 @@ def test_iter(thread): def test_matched(thread): assert thread.matched == 1 +def test_matched_iter(thread): + count = 0 + msgs = list(iter(thread)) + for msg in msgs: + if msg.matched: + count += 1 + assert count == thread.matched def test_authors_type(thread): assert isinstance(thread.authors, notmuch2.BinString) diff --git a/bindings/python-cffi/version.txt b/bindings/python-cffi/version.txt deleted file mode 100644 index 3f8003cd..00000000 --- a/bindings/python-cffi/version.txt +++ /dev/null @@ -1 +0,0 @@ -0.34.2 diff --git a/bindings/python/notmuch/version.py b/bindings/python/notmuch/version.py index 7a872f5f..f7392174 100644 --- a/bindings/python/notmuch/version.py +++ b/bindings/python/notmuch/version.py @@ -1,3 +1,3 @@ # this file should be kept in sync with ../../../version -__VERSION__ = '0.34.2' +__VERSION__ = '0.35' SOVERSION = '5' diff --git a/completion/notmuch-completion.bash b/completion/notmuch-completion.bash index 15425697..0022b54b 100644 --- a/completion/notmuch-completion.bash +++ b/completion/notmuch-completion.bash @@ -103,12 +103,12 @@ _notmuch_search_terms() COMPREPLY=( $(compgen -P "from:" -W "`_notmuch_email ${cur}`" -- ${cur##from:}) ) ;; path:*) - local path=`notmuch config get database.path` + local path=`notmuch config get database.mail_root` compopt -o nospace COMPREPLY=( $(compgen -d "$path/${cur##path:}" | sed "s|^$path/||" ) ) ;; folder:*) - local path=`notmuch config get database.path` + local path=`notmuch config get database.mail_root` compopt -o nospace COMPREPLY=( $(compgen -d "$path/${cur##folder:}" | \ sed "s|^$path/||" | grep -v "\(^\|/\)\(cur\|new\|tmp\)$" ) ) @@ -281,7 +281,7 @@ _notmuch_insert() $split && case "${prev}" in --folder) - local path=`notmuch config get database.path` + local path=`notmuch config get database.mail_root` compopt -o nospace COMPREPLY=( $(compgen -d "$path/${cur}" | \ sed "s|^$path/||" | grep -v "\(^\|/\)\(cur\|new\|tmp\)$" ) ) diff --git a/completion/zsh/_notmuch b/completion/zsh/_notmuch index e920f10b..e207d90b 100644 --- a/completion/zsh/_notmuch +++ b/completion/zsh/_notmuch @@ -69,8 +69,8 @@ _notmuch_term_mimetype() { _notmuch_term_path() { local ret=1 expl - local maildir="$(notmuch config get database.path)" - [[ -d $maildir ]] || { _message -e "database.path not found" ; return $ret } + local maildir="$(notmuch config get database.mail_root)" + [[ -d $maildir ]] || { _message -e "database.mail_root not found" ; return $ret } _description notmuch-folder expl 'maildir folder' _files "$expl[@]" -W $maildir -/ && ret=0 @@ -79,8 +79,8 @@ _notmuch_term_path() { _notmuch_term_folder() { local ret=1 expl - local maildir="$(notmuch config get database.path)" - [[ -d $maildir ]] || { _message -e "database.path not found" ; return $ret } + local maildir="$(notmuch config get database.mail_root)" + [[ -d $maildir ]] || { _message -e "database.mail_root not found" ; return $ret } _description notmuch-folder expl 'maildir folder' local ignoredfolders=( '*/(cur|new|tmp)' ) diff --git a/configure b/configure index 6c3a38f1..36f3f606 100755 --- a/configure +++ b/configure @@ -55,6 +55,8 @@ subdirs="${subdirs} bindings" # the directory structure and copy Makefiles. if [ "$srcdir" != "." ]; then + NOTMUCH_BUILDDIR=$PWD + for dir in . ${subdirs}; do mkdir -p "$dir" cp "$srcdir"/"$dir"/Makefile.local "$dir" @@ -78,6 +80,8 @@ if [ "$srcdir" != "." ]; then "$srcdir"/bindings/python-cffi/notmuch2 \ "$srcdir"/bindings/python-cffi/setup.py \ bindings/python-cffi/ +else + NOTMUCH_BUILDDIR=$NOTMUCH_SRCDIR fi # Set several defaults (optionally specified by the user in @@ -308,12 +312,22 @@ for option; do true elif [ "${option%%=*}" = '--host' ] ; then true + elif [ "${option%%=*}" = '--bindir' ] ; then + true + elif [ "${option%%=*}" = '--sbindir' ] ; then + true elif [ "${option%%=*}" = '--datadir' ] ; then true elif [ "${option%%=*}" = '--localstatedir' ] ; then true + elif [ "${option%%=*}" = '--sharedstatedir' ] ; then + true elif [ "${option%%=*}" = '--libexecdir' ] ; then true + elif [ "${option%%=*}" = '--exec-prefix' ] ; then + true + elif [ "${option%%=*}" = '--program-prefix' ] ; then + true elif [ "${option}" = '--disable-maintainer-mode' ] ; then true elif [ "${option}" = '--disable-dependency-tracking' ] ; then @@ -396,6 +410,18 @@ EOF exit 1 fi +printf "C compiler supports address sanitizer... " +test_cmdline="${CC} ${CFLAGS} ${CPPFLAGS} -fsanitize=address minimal.c ${LDFLAGS} -o minimal" +if ${test_cmdline} >/dev/null 2>&1 && ./minimal +then + printf "Yes.\n" + have_asan=1 +else + printf "Nope, skipping those tests.\n" + have_asan=0 +fi +unset test_cmdline + printf "Reading libnotmuch version from source... " cat > _libversion.c < @@ -734,6 +760,7 @@ if command -v ${BASHCMD} > /dev/null; then printf "Yes (%s).\n" "$bash_absolute" else have_bash=0 + bash_absolute= printf "No. (%s not found)\n" "${BASHCMD}" fi @@ -744,6 +771,7 @@ if command -v ${PERL} > /dev/null; then printf "Yes (%s).\n" "$perl_absolute" else have_perl=0 + perl_absolute= printf "No. (%s not found)\n" "${PERL}" fi @@ -1245,6 +1273,7 @@ cat > Makefile.config < sphinx.config +cat > bindings/python-cffi/_notmuch_config.py < Tue, 15 Feb 2022 20:54:43 -0400 + +notmuch (0.35-1) unstable; urgency=medium + + * New upstream release + + -- David Bremner Sun, 06 Feb 2022 12:15:19 -0400 + +notmuch (0.35~rc0-2) experimental; urgency=medium + + * Reupload with binaries + + -- David Bremner Sat, 29 Jan 2022 21:53:29 -0400 + +notmuch (0.35~rc0-1) experimental; urgency=medium + + * New upstream release candidate + + -- David Bremner Sat, 29 Jan 2022 18:14:57 -0400 + +notmuch (0.34.3-1) unstable; urgency=medium + + * New upstream bugfix release, with several fixes for the notmuch2 + python module. + + -- David Bremner Sun, 09 Jan 2022 15:30:38 -0400 + notmuch (0.34.2-1~bpo11+1) bullseye-backports; urgency=medium * Rebuild for bullseye-backports. diff --git a/debian/control b/debian/control index 9872d602..a11d4130 100644 --- a/debian/control +++ b/debian/control @@ -56,7 +56,8 @@ Recommends: gnupg-agent, gpgsm, Suggests: - mailscripts + mailscripts, + notmuch-doc, Description: thread-based email index, search and tagging Notmuch is a system for indexing, searching, reading, and tagging large collections of email messages in maildir or mh format. It uses @@ -65,6 +66,21 @@ Description: thread-based email index, search and tagging . This package contains the notmuch command-line interface +Package: notmuch-doc +Architecture: all +Depends: + ${misc:Depends}, + ${sphinxdoc:Depends}, +Suggests: + notmuch +Description: thread-based email index, search and tagging + Notmuch is a system for indexing, searching, reading, and tagging + large collections of email messages in maildir or mh format. It uses + the Xapian library to provide fast, full-text search with a very + convenient search syntax. + . + This package contains the HTML documentation + Package: libnotmuch5 Section: libs Architecture: any diff --git a/debian/elpa-notmuch.elpa b/debian/elpa-notmuch.elpa index 4712b73f..7b3ce0fa 100644 --- a/debian/elpa-notmuch.elpa +++ b/debian/elpa-notmuch.elpa @@ -1,3 +1,3 @@ debian/tmp/usr/share/emacs/site-lisp/*.el -debian/tmp/usr/share/emacs/site-lisp/notmuch-logo.png +debian/tmp/usr/share/emacs/site-lisp/notmuch-logo.svg emacs/notmuch-pkg.el diff --git a/debian/notmuch-doc.install b/debian/notmuch-doc.install new file mode 100644 index 00000000..fa902fe1 --- /dev/null +++ b/debian/notmuch-doc.install @@ -0,0 +1 @@ +doc/_build/html usr/share/doc/notmuch diff --git a/debian/rules b/debian/rules index fa0551a9..55867126 100755 --- a/debian/rules +++ b/debian/rules @@ -3,7 +3,7 @@ export DEB_BUILD_MAINT_OPTIONS = hardening=+all %: - dh $@ --with python3,elpa + dh $@ --with python3,elpa,sphinxdoc override_dh_auto_configure: BASHCMD=/bin/bash ./configure --prefix=/usr \ @@ -19,7 +19,7 @@ override_dh_auto_test: dh_auto_test -- V=1 override_dh_auto_build: - dh_auto_build -- V=1 + dh_auto_build -- V=1 all sphinx-html PYBUILD_NAME=notmuch dh_auto_build --buildsystem=pybuild --sourcedirectory bindings/python PYBUILD_NAME=notmuch2 dh_auto_build --buildsystem=pybuild --sourcedirectory bindings/python-cffi $(MAKE) -C contrib/notmuch-mutt diff --git a/devel/schemata b/devel/schemata index 01e3a3df..01810888 100644 --- a/devel/schemata +++ b/devel/schemata @@ -145,9 +145,11 @@ headers = { Cc?: string, Bcc?: string, Reply-To?: string, - Date: string + Date: string, + extra_header_pair* } +extra_header_pair= (header_name: string) # Encryption status (format_part_sprinter) encstatus = [{status: "good"|"bad"}] diff --git a/doc/Makefile.local b/doc/Makefile.local index 730ad4fb..d43ef269 100644 --- a/doc/Makefile.local +++ b/doc/Makefile.local @@ -35,7 +35,7 @@ endif INFO_INFO_FILES := $(INFO_TEXI_FILES:.texi=.info) -.PHONY: sphinx-html sphinx-texinfo sphinx-info doc-prereqs +.PHONY: sphinx-html sphinx-texinfo sphinx-info .PHONY: install-man build-man apidocs install-apidocs @@ -47,23 +47,28 @@ $(DOCBUILDDIR)/.roff.stamp $(DOCBUILDDIR)/.html.stamp $(DOCBUILDDIR)/.texi.stamp endif ifeq ($(HAVE_PYTHON3_CFFI),1) -doc-prereqs: python-cffi-bindings +DOC_PREREQS=bindings/python-cffi.stamp +else +DOC_PREREQS= endif sphinx-html: $(DOCBUILDDIR)/.html.stamp -$(DOCBUILDDIR)/.html.stamp: $(ALL_RST_FILES) doc-prereqs +$(DOCBUILDDIR)/.html.stamp: $(ALL_RST_FILES) $(DOC_PREREQS) $(SPHINXBUILD) -b html -d $(DOCBUILDDIR)/html_doctrees $(ALLSPHINXOPTS) $(DOCBUILDDIR)/html touch $@ sphinx-texinfo: $(DOCBUILDDIR)/.texi.stamp -$(DOCBUILDDIR)/.texi.stamp: $(ALL_RST_FILES) doc-prereqs +$(DOCBUILDDIR)/.texi.stamp: $(ALL_RST_FILES) $(DOC_PREREQS) $(SPHINXBUILD) -b texinfo -d $(DOCBUILDDIR)/texinfo_doctrees $(ALLSPHINXOPTS) $(DOCBUILDDIR)/texinfo touch $@ -sphinx-info: sphinx-texinfo +sphinx-info: $(DOCBUILDDIR)/.info.stamp + +$(DOCBUILDDIR)/.info.stamp: $(DOCBUILDDIR)/.texi.stamp $(DOC_PREREQS) $(MAKE) -C $(DOCBUILDDIR)/texinfo info + touch $@ # Use the man page converter that is available. We should never depend # on MAN_ROFF_FILES if a converter is not available. @@ -112,6 +117,11 @@ build-man: install-man: @echo "No sphinx, will not install man pages." else + +# it should be safe to depend on the stamp file, because it is created +# after all roff files are moved into place. +${MAN_GZIP_FILES}: ${DOCBUILDDIR}/.roff.stamp + build-man: ${MAN_GZIP_FILES} install-man: ${MAN_GZIP_FILES} mkdir -m0755 -p "$(DESTDIR)$(mandir)/man1" @@ -127,7 +137,7 @@ ifneq ($(HAVE_SPHINX)$(HAVE_MAKEINFO),11) build-info: @echo "Missing sphinx or makeinfo, not building info pages" else -build-info: sphinx-info +build-info: $(DOCBUILDDIR)/.info.stamp endif ifneq ($(HAVE_SPHINX)$(HAVE_MAKEINFO)$(HAVE_INSTALL_INFO),111) @@ -145,5 +155,5 @@ $(dir)/config.dox: version.stamp echo "INPUT=${srcdir}/lib/notmuch.h" >> $@ CLEAN := $(CLEAN) $(DOCBUILDDIR) $(DOCBUILDDIR)/.roff.stamp $(DOCBUILDDIR)/.texi.stamp -CLEAN := $(CLEAN) $(DOCBUILDDIR)/.html.stamp +CLEAN := $(CLEAN) $(DOCBUILDDIR)/.html.stamp $(DOCBUILDDIR)/.info.stamp CLEAN := $(CLEAN) $(MAN_GZIP_FILES) $(MAN_ROFF_FILES) $(dir)/conf.pyc $(dir)/config.dox diff --git a/doc/conf.py b/doc/conf.py index c7fd8f5a..e46e1d4e 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -14,7 +14,7 @@ master_doc = 'index' # General information about the project. project = u'notmuch' -copyright = u'2009-2021, Carl Worth and many others' +copyright = u'2009-2022, Carl Worth and many others' location = os.path.dirname(__file__) diff --git a/doc/man1/notmuch-address.rst b/doc/man1/notmuch-address.rst index 7423b629..c70dde35 100644 --- a/doc/man1/notmuch-address.rst +++ b/doc/man1/notmuch-address.rst @@ -42,7 +42,7 @@ Supported options for **address** include neither ``--output=sender`` nor ``--output=recipients`` is given, ``--output=sender`` is implied. - **sender** + sender Output all addresses from the *From* header. Note: Searching for **sender** should be much faster than @@ -50,17 +50,17 @@ Supported options for **address** include cached directly in the database whereas other addresses need to be fetched from message files. - **recipients** + recipients Output all addresses from the *To*, *Cc* and *Bcc* headers. - **count** + count Print the count of how many times was the address encountered during search. Note: With this option, addresses are printed only after the whole search is finished. This may take long time. - **address** + address Output only the email addresses instead of the full mailboxes with names and email addresses. This option has no effect on the JSON or S-Expression output formats. @@ -69,17 +69,17 @@ Supported options for **address** include Control the deduplication of results. - **no** + no Output all occurrences of addresses in the matching messages. This is not applicable with ``--output=count``. - **mailbox** + mailbox Deduplicate addresses based on the full, case sensitive name and email address, or mailbox. This is effectively the same as piping the ``--deduplicate=no`` output to **sort | uniq**, except for the order of results. This is the default. - **address** + address Deduplicate addresses based on the case insensitive address part of the mailbox. Of all the variants (with different name or case), print the one occurring most frequently among the diff --git a/doc/man1/notmuch-config.rst b/doc/man1/notmuch-config.rst index 36e57ea6..41e1338b 100644 --- a/doc/man1/notmuch-config.rst +++ b/doc/man1/notmuch-config.rst @@ -55,14 +55,14 @@ The available configuration items are described below. Non-absolute paths are presumed relative to `$HOME` for items in section **database**. -**database.path** +database.path Notmuch will store its database here, (in sub-directory named ``.notmuch`` if **database.mail\_root** is unset). Default: see :ref:`database` -**database.mail_root** +database.mail_root The top-level directory where your mail currently exists and to where mail will be delivered in the future. Files should be individual email messages. @@ -72,7 +72,7 @@ paths are presumed relative to `$HOME` for items in section Default: For compatibility with older configurations, the value of database.path is used if **database.mail\_root** is unset. -**database.backup_dir** +database.backup_dir Directory to store tag dumps when upgrading database. History: this configuration value was introduced in notmuch 0.32. @@ -80,7 +80,7 @@ paths are presumed relative to `$HOME` for items in section Default: A sibling directory of the Xapian database called `backups`. -**database.hook_dir** +database.hook_dir Directory containing hooks run by notmuch commands. See :any:`notmuch-hooks(5)`. @@ -88,7 +88,7 @@ paths are presumed relative to `$HOME` for items in section Default: See HOOKS, below. -**database.autocommit** +database.autocommit How often to commit transactions to disk. `0` means wait until command completes, otherwise an integer `n` specifies to commit to @@ -96,30 +96,30 @@ paths are presumed relative to `$HOME` for items in section History: this configuration value was introduced in notmuch 0.33. -**user.name** +user.name Your full name. Default: ``$NAME`` variable if set, otherwise read from ``/etc/passwd``. -**user.primary\_email** +user.primary\_email Your primary email address. Default: ``$EMAIL`` variable if set, otherwise constructed from the username and hostname of the current machine. -**user.other\_email** +user.other\_email A list of other email addresses at which you receive email. Default: not set. -**new.tags** +new.tags A list of tags that will be added to all messages incorporated by **notmuch new**. Default: ``unread;inbox``. -**new.ignore** +new.ignore A list to specify files and directories that will not be searched for messages by :any:`notmuch-new(1)`. Each entry in the list is either: @@ -137,7 +137,7 @@ paths are presumed relative to `$HOME` for items in section Default: empty list. -**search.exclude\_tags** +search.exclude\_tags A list of tags that will be excluded from search results by default. Using an excluded tag in a query will override that exclusion. @@ -145,7 +145,21 @@ paths are presumed relative to `$HOME` for items in section Default: empty list. Note that :any:`notmuch-setup(1)` puts ``deleted;spam`` here when creating new configuration file. -**maildir.synchronize\_flags** +.. _show.extra_headers: + +show.extra\_headers + + By default :any:`notmuch-show(1)` includes the following headers + in structured output if they are present in the message: + `Subject`, `From`, `To`, `Cc`, `Bcc`, `Reply-To`, `Date`. This + option allows the specification of a list of further + headers to output. + + History: This configuration value was introduced in notmuch 0.35. + + Default: empty list. + +maildir.synchronize\_flags If true, then the following maildir flags (in message filenames) will be synchronized with the corresponding notmuch tags: @@ -178,7 +192,7 @@ paths are presumed relative to `$HOME` for items in section Default: ``true``. -**index.decrypt** +index.decrypt Policy for decrypting encrypted messages during indexing. Must be one of: ``false``, ``auto``, ``nostash``, or ``true``. @@ -231,7 +245,7 @@ paths are presumed relative to `$HOME` for items in section Default: ``auto``. -**index.header.** +index.header. Define the query prefix , based on a mail header. For example ``index.header.List=List-Id`` will add a probabilistic prefix ``List:`` that searches the ``List-Id`` field. User @@ -240,18 +254,18 @@ paths are presumed relative to `$HOME` for items in section supported. See :any:`notmuch-search-terms(7)` for a list of existing prefixes, and an explanation of probabilistic prefixes. -**built_with.** +built_with. Compile time feature . Current possibilities include "retry_lock" (configure option, included by default). (since notmuch 0.30, "compact" and "field_processor" are always included.) -**query.** +query. Expansion for named query called . See :any:`notmuch-search-terms(7)` for more information about named queries. -**squery.** +squery. Expansion for named query called , using s-expression syntax. See :any:`notmuch-sexp-queries(7)` for more information about s-expression queries. diff --git a/doc/man1/notmuch-count.rst b/doc/man1/notmuch-count.rst index 9a7e4bac..4c9c9a1c 100644 --- a/doc/man1/notmuch-count.rst +++ b/doc/man1/notmuch-count.rst @@ -28,13 +28,13 @@ Supported options for **count** include .. option:: --output=(messages|threads|files) - **messages** + messages Output the number of matching messages. This is the default. - **threads** + threads Output the number of matching threads. - **files** + files Output the number of files associated with matching messages. This may be bigger than the number of matching messages due to duplicates (i.e. multiple files having the diff --git a/doc/man1/notmuch-dump.rst b/doc/man1/notmuch-dump.rst index 4319c5c3..a7ca39d0 100644 --- a/doc/man1/notmuch-dump.rst +++ b/doc/man1/notmuch-dump.rst @@ -39,7 +39,7 @@ Supported options for **dump** include Notmuch restore supports two plain text dump formats, both with one message-id per line, followed by a list of tags. - **batch-tag** + batch-tag The default **batch-tag** dump format is intended to more robust against malformed message-ids and tags containing whitespace or non-\ :manpage:`ascii(7)` characters. Each line @@ -58,7 +58,7 @@ Supported options for **dump** include :any:`notmuch-tag(1)`; note that the single message-id query is mandatory for :any:`notmuch-restore(1)`. - **sup** + sup The **sup** dump file format is specifically chosen to be compatible with the format of files produced by :manpage:`sup-dump(1)`. So if you've previously been using sup @@ -77,18 +77,18 @@ Supported options for **dump** include Control what kind of metadata is included in the output. - **config** + config Output configuration data stored in the database. Each line starts with "#@ ", followed by a space separated key-value pair. Both key and value are hex encoded if needed. - **properties** + properties Output per-message (key,value) metadata. Each line starts with "#= ", followed by a message id, and a space separated list of key=value pairs. Ids, keys and values are hex encoded if needed. See :any:`notmuch-properties(7)` for more details. - **tags** + tags Output per-message boolean metadata, namely tags. See *format* above for description of the output. diff --git a/doc/man1/notmuch-insert.rst b/doc/man1/notmuch-insert.rst index 82c4a7a0..da9ca791 100644 --- a/doc/man1/notmuch-insert.rst +++ b/doc/man1/notmuch-insert.rst @@ -14,7 +14,7 @@ DESCRIPTION **notmuch insert** reads a message from standard input and delivers it into the maildir directory given by configuration option -**database.path**, then incorporates the message into the notmuch +**database.mail_root**, then incorporates the message into the notmuch database. It is an alternative to using a separate tool to deliver the message then running :any:`notmuch-new(1)` afterwards. @@ -38,7 +38,7 @@ Supported options for **insert** include .. option:: --folder= Deliver the message to the specified folder, relative to the - top-level directory given by the value of **database.path**. The + top-level directory given by the value of **database.mail_root**. The default is the empty string, which means delivering to the top-level directory. diff --git a/doc/man1/notmuch-reply.rst b/doc/man1/notmuch-reply.rst index 4a78a90b..fa0371f9 100644 --- a/doc/man1/notmuch-reply.rst +++ b/doc/man1/notmuch-reply.rst @@ -40,22 +40,22 @@ Supported options for **reply** include .. option:: --format=(default|json|sexp|headers-only) - **default** + default Includes subject and quoted message body as an RFC 2822 message. - **json** + json Produces JSON output containing headers for a reply message and the contents of the original message. This output can be used by a client to create a reply message intelligently. - **sexp** + sexp Produces S-Expression output containing headers for a reply message and the contents of the original message. This output can be used by a client to create a reply message intelligently. - **headers-only** + headers-only Only produces In-Reply-To, References, To, Cc, and Bcc headers. @@ -67,10 +67,10 @@ Supported options for **reply** include .. option:: --reply-to=(all|sender) - **all** (default) + all (default) Replies to all addresses. - **sender** + sender Replies only to the sender. If replying to user's own message (Reply-to: or From: header is one of the user's configured email addresses), try To:, Cc:, and Bcc: headers in this diff --git a/doc/man1/notmuch-restore.rst b/doc/man1/notmuch-restore.rst index bd452475..ac6b4245 100644 --- a/doc/man1/notmuch-restore.rst +++ b/doc/man1/notmuch-restore.rst @@ -32,14 +32,14 @@ Supported options for **restore** include line specifying a message-id and a set of tags. For details of the actual formats, see :any:`notmuch-dump(1)`. - **sup** + sup The **sup** dump file format is specifically chosen to be compatible with the format of files produced by sup-dump. So if you've previously been using sup for mail, then the **notmuch restore** command provides you a way to import all of your tags (or labels as sup calls them). - **batch-tag** + batch-tag The **batch-tag** dump format is intended to more robust against malformed message-ids and tags containing whitespace or non-\ **ascii(7)** characters. See :any:`notmuch-dump(1)` for @@ -49,7 +49,7 @@ Supported options for **restore** include changes if the **maildir.synchronize\_flags** configuration option is enabled. See :any:`notmuch-config(1)` for details. - **auto** + auto This option (the default) tries to guess the format from the input. For correctly formed input in either supported format, this heuristic, based the fact that batch-tag format contains @@ -59,18 +59,18 @@ Supported options for **restore** include Control what kind of metadata is restored. - **config** + config Restore configuration data to the database. Each configuration line starts with "#@ ", followed by a space separated key-value pair. Both key and value are hex encoded if needed. - **properties** + properties Restore per-message (key,value) metadata. Each line starts with "#= ", followed by a message id, and a space separated list of key=value pairs. Ids, keys and values are hex encoded if needed. See :any:`notmuch-properties(7)` for more details. - **tags** + tags Restore per-message metadata, namely tags. See *format* above for more details. diff --git a/doc/man1/notmuch-search.rst b/doc/man1/notmuch-search.rst index 2d9ca2d5..ad305efd 100644 --- a/doc/man1/notmuch-search.rst +++ b/doc/man1/notmuch-search.rst @@ -43,7 +43,7 @@ Supported options for **search** include .. option:: --output=(summary|threads|messages|files|tags) - **summary** + summary Output a summary of each thread with any message matching the search terms. The summary includes the thread ID, date, the number of messages in the thread (both the number matched and @@ -52,19 +52,19 @@ Supported options for **search** include for some messages, the total number of files is printed in parentheses (see below for an example). - **threads** + threads Output the thread IDs of all threads with any message matching the search terms, either one per line (``--format=text``), separated by null characters (``--format=text0``), as a JSON array (``--format=json``), or an S-Expression list (``--format=sexp``). - **messages** + messages Output the message IDs of all messages matching the search terms, either one per line (``--format=text``), separated by null characters (``--format=text0``), as a JSON array (``--format=json``), or as an S-Expression list (``--format=sexp``). - **files** + files Output the filenames of all messages matching the search terms, either one per line (``--format=text``), separated by null characters (``--format=text0``), as a JSON array (``--format=json``), @@ -78,7 +78,7 @@ Supported options for **search** include in other directories that are included in the output, although these files alone would not match the search. - **tags** + tags Output all tags that appear on any message matching the search terms, either one per line (``--format=text``), separated by null characters (``--format=text0``), as a JSON array (``--format=json``), @@ -115,20 +115,20 @@ Supported options for **search** include terms. This option specifies whether to omit excluded messages in the search process. - **true** (default) + true (default) Prevent excluded messages from matching the search terms. - **all** + all Additionally prevent excluded messages from appearing in displayed results, in effect behaving as though the excluded messages do not exist. - **false** + false Allow excluded messages to match search terms and appear in displayed results. Excluded messages are still marked in the relevant outputs. - **flag** + flag Only has an effect when ``--output=summary``. The output is almost identical to **false**, but the "match count" is the number of matching non-excluded messages in the thread, rather diff --git a/doc/man1/notmuch-show.rst b/doc/man1/notmuch-show.rst index 64639174..1b02d407 100644 --- a/doc/man1/notmuch-show.rst +++ b/doc/man1/notmuch-show.rst @@ -36,7 +36,7 @@ Supported options for **show** include .. option:: --format=(text|json|sexp|mbox|raw) - **text** (default for messages) + text (default for messages) The default plain-text format has all text-content MIME parts decoded. Various components in the output, (**message**, **header**, **body**, **attachment**, and MIME **part**), will @@ -46,7 +46,7 @@ Supported options for **show** include '}'), to either open or close the component. For a multipart MIME message, these parts will be nested. - **json** + json The output is formatted with Javascript Object Notation (JSON). This format is more robust than the text format for automated processing. The nested structure of multipart MIME @@ -58,7 +58,7 @@ Supported options for **show** include as UTF-8 and any message content included in the output will be charset-converted to UTF-8. - **sexp** + sexp The output is formatted as the Lisp s-expression (sexp) equivalent of the JSON format above. Objects are formatted as property lists whose keys are keywords (symbols preceded by a @@ -66,7 +66,7 @@ Supported options for **show** include formatted as ``nil``. As for JSON, the s-expression output is always encoded as UTF-8. - **mbox** + mbox All matching messages are output in the traditional, Unix mbox format with each message being prefixed by a line beginning with "From " and a blank line separating each message. Lines @@ -77,7 +77,7 @@ Supported options for **show** include http://homepage.ntlworld.com/jonathan.deboynepollard/FGA/mail-mbox-formats.html - **raw** (default if ``--part`` is given) + raw (default if ``--part`` is given) Write the raw bytes of the given MIME part of a message to standard out. For this format, it is an error to specify a query that matches more than one message. @@ -221,6 +221,13 @@ email messages. For this, use a search term of "thread:" as can be seen in the first column of output from the :any:`notmuch-search(1)` command. +CONFIGURATION +============= + +Structured output (json / sexp) is influenced by the configuration +option :ref:`show.extra_headers `. See +:any:`notmuch-config(1)` for details. + EXIT STATUS =========== diff --git a/doc/man5/notmuch-hooks.rst b/doc/man5/notmuch-hooks.rst index 268917cd..0ab5efbc 100644 --- a/doc/man5/notmuch-hooks.rst +++ b/doc/man5/notmuch-hooks.rst @@ -19,7 +19,7 @@ must have executable permissions. The currently available hooks are described below. -**pre-new** +pre-new This hook is invoked by the :any:`notmuch-new(1)` command before scanning or importing new messages into the database. If this hook exits with a non-zero status, notmuch will abort further @@ -28,7 +28,7 @@ The currently available hooks are described below. Typically this hook is used for fetching or delivering new mail to be imported into the database. -**post-new** +post-new This hook is invoked by the :any:`notmuch-new(1)` command after new messages have been imported into the database and initial tags have been applied. The hook will not be run if there have been any @@ -37,7 +37,7 @@ The currently available hooks are described below. Typically this hook is used to perform additional query-based tagging on the imported messages. -**post-insert** +post-insert This hook is invoked by the :any:`notmuch-insert(1)` command after the message has been delivered, added to the database, and initial tags have been applied. The hook will not be run if there have diff --git a/doc/man7/notmuch-properties.rst b/doc/man7/notmuch-properties.rst index 7f128b5c..ff79f4c2 100644 --- a/doc/man7/notmuch-properties.rst +++ b/doc/man7/notmuch-properties.rst @@ -55,7 +55,7 @@ MESSAGE PROPERTIES The following properties are set by notmuch internally in the course of its normal activity. -**index.decryption** +index.decryption If a message contains encrypted content, and notmuch tries to decrypt that content during indexing, it will add the property ``index.decryption=success`` when the cleartext was successfully @@ -75,8 +75,7 @@ of its normal activity. :any:`notmuch-config(1)`), then this property will not be set on that message. -**session-key** - +session-key When :any:`notmuch-show(1)` or :any:`notmuch-reply(1)` encounters a message with an encrypted part, if notmuch finds a ``session-key`` property associated with the message, it will try @@ -111,8 +110,7 @@ of its normal activity. example, an AES-128 key might be stashed in a notmuch property as: ``session-key=7:14B16AF65536C28AF209828DFE34C9E0``. -**index.repaired** - +index.repaired Some messages arrive in forms that are confusing to view; they can be mangled by mail transport agents, or the sending mail user agent may structure them in a way that is confusing. If notmuch diff --git a/doc/man7/notmuch-sexp-queries.rst b/doc/man7/notmuch-sexp-queries.rst index 019d15f0..bc8e5086 100644 --- a/doc/man7/notmuch-sexp-queries.rst +++ b/doc/man7/notmuch-sexp-queries.rst @@ -21,7 +21,7 @@ build of notmuch supports it with :: - $ notmuch config get built_with.sexpr_query + $ notmuch config get built_with.sexp_queries S-EXPRESSIONS @@ -31,10 +31,12 @@ 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 \""``. @@ -48,9 +50,11 @@ 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* @@ -62,19 +66,23 @@ subqueries. 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 @@ -164,26 +172,31 @@ MODIFIERS 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 `, or outside of fields [#not-body]_. Note that a ``starts-with`` query cannot be part of a phrase. The @@ -193,63 +206,80 @@ 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`` @@ -306,10 +336,10 @@ NOTES 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` +.. |q1| replace:: `q`\ :sub:`1` +.. |q2| replace:: `q`\ :sub:`2` +.. |qn| replace:: `q`\ :sub:`n` -.. |p1| replace:: :math:`p_1` -.. |p2| replace:: :math:`p_2` -.. |pn| replace:: :math:`p_n` +.. |p1| replace:: `p`\ :sub:`1` +.. |p2| replace:: `p`\ :sub:`2` +.. |pn| replace:: `p`\ :sub:`n` diff --git a/doc/notmuch-emacs.rst b/doc/notmuch-emacs.rst index 12ee25e5..85b2c0ea 100644 --- a/doc/notmuch-emacs.rst +++ b/doc/notmuch-emacs.rst @@ -56,7 +56,7 @@ notmuch-hello key bindings ```` Move to the next widget (button or text entry field) -```` +```` Move to the previous widget. ```` @@ -175,6 +175,16 @@ variables. :index:`notmuch-search-oldest-first` Display the oldest threads at the top of the buffer +It is also possible to customize how the name of buffers containing +search results is formatted using the following variables: + +:index:`notmuch-search-buffer-name-format` + |docstring::notmuch-search-buffer-name-format| + +:index:`notmuch-saved-search-buffer-name-format` + |docstring::notmuch-saved-search-buffer-name-format| + + .. _notmuch-show: notmuch-show @@ -222,6 +232,9 @@ Display of messages can be controlled by the following variables :index:`notmuch-message-headers-visible` |docstring::notmuch-message-headers-visible| +:index:`notmuch-show-header-line` + |docstring::notmuch-show-header-line| + .. _show-copy: Copy to kill-ring diff --git a/emacs/Makefile.local b/emacs/Makefile.local index d1b320c3..0f1f0eb2 100644 --- a/emacs/Makefile.local +++ b/emacs/Makefile.local @@ -42,7 +42,7 @@ emacs_mua := $(dir)/notmuch-emacs-mua emacs_mua_desktop := $(dir)/notmuch-emacs-mua.desktop emacs_images := \ - $(srcdir)/$(dir)/notmuch-logo.png + $(srcdir)/$(dir)/notmuch-logo.svg emacs_bytecode = $(emacs_sources:.el=.elc) emacs_docstrings = $(emacs_sources:.el=.rsti) diff --git a/emacs/coolj.el b/emacs/coolj.el index d820525b..79d2a1b7 100644 --- a/emacs/coolj.el +++ b/emacs/coolj.el @@ -45,7 +45,7 @@ Otherwise respect `fill-column'." :group 'coolj :type 'boolean) -(defcustom coolj-line-prefix-regexp "^\\(>+ \\)*" +(defcustom coolj-line-prefix-regexp "^\\(>+ ?\\)*" "Regular expression that matches line prefixes." :group 'coolj :type 'regexp) diff --git a/emacs/notmuch-hello.el b/emacs/notmuch-hello.el index 71487bd9..beb25382 100644 --- a/emacs/notmuch-hello.el +++ b/emacs/notmuch-hello.el @@ -198,7 +198,7 @@ fields of the search." (defvar notmuch-hello-indent 4 "How much to indent non-headers.") -(defimage notmuch-hello-logo ((:type png :file "notmuch-logo.png"))) +(defimage notmuch-hello-logo ((:type svg :file "notmuch-logo.svg"))) (defcustom notmuch-show-logo t "Should the notmuch logo be shown?" @@ -486,11 +486,14 @@ diagonal." (defun notmuch-hello-widget-search (widget &rest _ignore) (cl-case (widget-get widget :notmuch-search-type) (tree - (notmuch-tree (widget-get widget :notmuch-search-terms) - nil nil nil nil nil nil - (widget-get widget :notmuch-search-oldest-first))) + (let ((n (notmuch-search-format-buffer-name (widget-value widget) "tree" t))) + (notmuch-tree (widget-get widget :notmuch-search-terms) + nil nil n nil nil nil + (widget-get widget :notmuch-search-oldest-first)))) (unthreaded - (notmuch-unthreaded (widget-get widget :notmuch-search-terms))) + (let ((n (notmuch-search-format-buffer-name (widget-value widget) + "unthreaded" t))) + (notmuch-unthreaded (widget-get widget :notmuch-search-terms) nil nil n))) (t (notmuch-search (widget-get widget :notmuch-search-terms) (widget-get widget :notmuch-search-oldest-first))))) @@ -557,7 +560,8 @@ with any properties in the original saved-search. The values :show-empty-searches, :filter and :filter-count from options will be handled as specified for -`notmuch-hello-insert-searches'." +`notmuch-hello-insert-searches'. :disable-includes can be used to +turn off the default exclude processing in `notmuch-count(1)'" (with-temp-buffer (dolist (elem query-list nil) (let ((count-query (or (notmuch-saved-search-get elem :count-query) @@ -570,7 +574,11 @@ options will be handled as specified for (plist-get options :filter)))) "\n"))) (unless (= (notmuch--call-process-region (point-min) (point-max) notmuch-command - t t nil "count" "--batch") 0) + t t nil "count" + (if (plist-get options :disable-excludes) + "--exclude=false" + "--exclude=true") + "--batch") 0) (notmuch-logged-error "notmuch count --batch failed" "Please check that the notmuch CLI is new enough to support `count @@ -702,7 +710,6 @@ with `notmuch-hello-query-counts'." ;; that when we modify map it does not modify widget-keymap). (let ((map (make-composed-keymap (list (make-sparse-keymap) widget-keymap)))) (set-keymap-parent map notmuch-common-keymap) - (define-key map (kbd "") 'widget-backward) map) "Keymap for \"notmuch hello\" buffers.") @@ -786,7 +793,7 @@ Complete list of currently available key bindings: :help-echo "Refresh" (notmuch-hello-nice-number (string-to-number - (car (notmuch--process-lines notmuch-command "count"))))) + (car (notmuch--process-lines notmuch-command "count" "--exclude=false"))))) (widget-insert " messages.\n"))) (defun notmuch-hello-insert-saved-searches () @@ -918,7 +925,8 @@ following: nil :initially-hidden (not notmuch-show-all-tags-list) :hide-tags notmuch-hello-hide-tags - :filter notmuch-hello-tag-list-make-query)) + :filter notmuch-hello-tag-list-make-query + :disable-excludes t)) (defun notmuch-hello-insert-footer () "Insert the notmuch-hello footer." diff --git a/emacs/notmuch-logo.svg b/emacs/notmuch-logo.svg new file mode 100644 index 00000000..2c65a73b --- /dev/null +++ b/emacs/notmuch-logo.svg @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/emacs/notmuch-maildir-fcc.el b/emacs/notmuch-maildir-fcc.el index 7e177bf7..51020788 100644 --- a/emacs/notmuch-maildir-fcc.el +++ b/emacs/notmuch-maildir-fcc.el @@ -149,6 +149,7 @@ Otherwise set it according to `notmuch-fcc-dirs'." (buf (current-buffer)) (mml-externalize-attachments message-fcc-externalize-attachments)) (with-current-buffer (get-buffer-create " *message temp*") + (message-clone-locals buf) ;; for message-encoded-mail-cache (erase-buffer) (insert-buffer-substring buf) ,@body))) @@ -158,7 +159,10 @@ Otherwise set it according to `notmuch-fcc-dirs'." This should be called on a temporary copy. This is taken from the function message-do-fcc." - (message-encode-message-body) + (if (not message-encoded-mail-cache) + (message-encode-message-body) + (erase-buffer) + (insert message-encoded-mail-cache)) (save-restriction (message-narrow-to-headers) (mail-encode-encoded-word-buffer)) @@ -179,12 +183,12 @@ This is a rearranged version of message mode's message-do-fcc." (setq file (message-fetch-field "fcc" t))) (when file (with-temporary-notmuch-message-buffer + (notmuch-maildir-setup-message-for-saving) (save-restriction (message-narrow-to-headers) (while (setq file (message-fetch-field "fcc" t)) (push file files) (message-remove-header "fcc" nil t))) - (notmuch-maildir-setup-message-for-saving) ;; Process FCC operations. (mapc #'notmuch-fcc-handler files) (kill-buffer (current-buffer))))))) diff --git a/emacs/notmuch-show.el b/emacs/notmuch-show.el index 4de3e423..7c1f02c9 100644 --- a/emacs/notmuch-show.el +++ b/emacs/notmuch-show.el @@ -84,6 +84,11 @@ visible for any given message." :type 'boolean :group 'notmuch-show) +(defcustom notmuch-show-header-line t + "Show a header line with the current message's subject." + :type 'boolean + :group 'notmuch-show) + (defcustom notmuch-show-relative-dates t "Display relative dates in the message summary line." :type 'boolean @@ -1345,11 +1350,12 @@ If no messages match the query return NIL." (notmuch-show-mapc (lambda () (notmuch-show-set-prop :orig-tags (notmuch-show-get-tags)))) ;; Set the header line to the subject of the first message. - (setq header-line-format - (replace-regexp-in-string "%" "%%" - (notmuch-sanitize - (notmuch-show-strip-re - (notmuch-show-get-subject))))) + (when notmuch-show-header-line + (setq header-line-format + (replace-regexp-in-string "%" "%%" + (notmuch-sanitize + (notmuch-show-strip-re + (notmuch-show-get-subject)))))) (run-hooks 'notmuch-show-hook) (if state (notmuch-show-apply-state state) diff --git a/emacs/notmuch-tag.el b/emacs/notmuch-tag.el index 536315e9..8af09e68 100644 --- a/emacs/notmuch-tag.el +++ b/emacs/notmuch-tag.el @@ -241,7 +241,7 @@ DATA is the content of an SVG picture (e.g., as returned by "Return SVG data representing a star icon. This can be used with `notmuch-tag-format-image-data'." " - + - + - + xapian_db) continue; + val = g_key_file_get_string (file, *grp, *keys_p, NULL); + if (! val) { + status = NOTMUCH_STATUS_FILE_ERROR; + goto DONE; + } + normalized_val = _expand_path (notmuch, absolute_key, val); _notmuch_string_map_set (notmuch->config, absolute_key, normalized_val); g_free (val); @@ -596,6 +597,8 @@ _notmuch_config_key_to_string (notmuch_config_key_t key) return "user.name"; case NOTMUCH_CONFIG_AUTOCOMMIT: return "database.autocommit"; + case NOTMUCH_CONFIG_EXTRA_HEADERS: + return "show.extra_headers"; default: return NULL; } @@ -643,6 +646,7 @@ _notmuch_config_default (notmuch_database_t *notmuch, notmuch_config_key_t key) return ""; case NOTMUCH_CONFIG_AUTOCOMMIT: return "8000"; + case NOTMUCH_CONFIG_EXTRA_HEADERS: case NOTMUCH_CONFIG_HOOK_DIR: case NOTMUCH_CONFIG_BACKUP_DIR: case NOTMUCH_CONFIG_OTHER_EMAIL: @@ -657,6 +661,10 @@ notmuch_status_t _notmuch_config_load_defaults (notmuch_database_t *notmuch) { notmuch_config_key_t key; + notmuch_status_t status = NOTMUCH_STATUS_SUCCESS; + + if (notmuch->config == NULL) + notmuch->config = _notmuch_string_map_create (notmuch); for (key = NOTMUCH_CONFIG_FIRST; key < NOTMUCH_CONFIG_LAST; @@ -666,11 +674,14 @@ _notmuch_config_load_defaults (notmuch_database_t *notmuch) val = _notmuch_string_map_get (notmuch->config, key_string); if (! val) { + if (key == NOTMUCH_CONFIG_MAIL_ROOT && (notmuch->params & NOTMUCH_PARAM_SPLIT)) + status = NOTMUCH_STATUS_NO_MAIL_ROOT; + _notmuch_string_map_set (notmuch->config, key_string, _notmuch_config_default (notmuch, key)); } } - return NOTMUCH_STATUS_SUCCESS; + return status; } const char * diff --git a/lib/database-private.h b/lib/database-private.h index 8dd77281..657b1aa1 100644 --- a/lib/database-private.h +++ b/lib/database-private.h @@ -160,11 +160,12 @@ operator&= (_notmuch_features &a, _notmuch_features b) /* * Configuration options for xapian database fields */ -typedef enum notmuch_field_flags { +typedef enum { NOTMUCH_FIELD_NO_FLAGS = 0, NOTMUCH_FIELD_EXTERNAL = 1 << 0, NOTMUCH_FIELD_PROBABILISTIC = 1 << 1, NOTMUCH_FIELD_PROCESSOR = 1 << 2, + NOTMUCH_FIELD_STRIP_TRAILING_SLASH = 1 << 3, } notmuch_field_flag_t; /* @@ -191,12 +192,17 @@ operator& (notmuch_field_flag_t a, notmuch_field_flag_t b) Xapian::QueryParser::FLAG_PURE_NOT) /* - * Which parameters were explicit when the database was opened */ + * explicit and implied parameters to open */ typedef enum { NOTMUCH_PARAM_NONE = 0, + /* database passed explicitely */ NOTMUCH_PARAM_DATABASE = 1 << 0, + /* config file passed explicitely */ NOTMUCH_PARAM_CONFIG = 1 << 1, + /* profile name passed explicitely */ NOTMUCH_PARAM_PROFILE = 1 << 2, + /* split (e.g. XDG) configuration */ + NOTMUCH_PARAM_SPLIT = 1 << 3, } notmuch_open_param_t; /* @@ -374,5 +380,10 @@ notmuch_status_t _notmuch_sexp_string_to_xapian_query (notmuch_database_t *notmuch, const char *querystr, Xapian::Query &output); #endif + +/* parse-time-vrp.h */ +notmuch_status_t +_notmuch_date_strings_to_query (Xapian::valueno slot, const std::string &from, const std::string &to, + Xapian::Query &output, std::string &msg); #endif #endif diff --git a/lib/database.cc b/lib/database.cc index 7eb0de79..df83e204 100644 --- a/lib/database.cc +++ b/lib/database.cc @@ -311,6 +311,8 @@ notmuch_status_to_string (notmuch_status_t status) return "Database exists, not recreated"; case NOTMUCH_STATUS_BAD_QUERY_SYNTAX: return "Syntax error in query"; + case NOTMUCH_STATUS_NO_MAIL_ROOT: + return "No mail root found"; default: case NOTMUCH_STATUS_LAST_STATUS: return "Unknown error status value"; @@ -590,10 +592,12 @@ notmuch_database_compact (const char *path, notmuch_database_t *notmuch = NULL; char *message = NULL; - ret = notmuch_database_open_verbose (path, - NOTMUCH_DATABASE_MODE_READ_WRITE, - ¬much, - &message); + ret = notmuch_database_open_with_config (path, + NOTMUCH_DATABASE_MODE_READ_WRITE, + "", + NULL, + ¬much, + &message); if (ret) { if (status_cb) status_cb (message, closure); return ret; @@ -751,6 +755,8 @@ notmuch_database_destroy (notmuch_database_t *notmuch) notmuch->date_range_processor = NULL; delete notmuch->last_mod_range_processor; notmuch->last_mod_range_processor = NULL; + delete notmuch->stemmer; + notmuch->stemmer = NULL; talloc_free (notmuch); diff --git a/lib/indexopts.c b/lib/indexopts.c index 4a860858..2ffd1942 100644 --- a/lib/indexopts.c +++ b/lib/indexopts.c @@ -20,6 +20,10 @@ #include "notmuch-private.h" +struct _notmuch_indexopts { + _notmuch_crypto_t crypto; +}; + notmuch_indexopts_t * notmuch_database_get_default_indexopts (notmuch_database_t *db) { diff --git a/lib/notmuch-private.h b/lib/notmuch-private.h index 093c29b1..3cc79bc4 100644 --- a/lib/notmuch-private.h +++ b/lib/notmuch-private.h @@ -121,7 +121,7 @@ typedef enum { */ #define NOTMUCH_MESSAGE_ID_MAX (200 - sizeof (NOTMUCH_METADATA_THREAD_ID_PREFIX)) -typedef enum _notmuch_private_status { +typedef enum { /* First, copy all the public status values. */ NOTMUCH_PRIVATE_STATUS_SUCCESS = NOTMUCH_STATUS_SUCCESS, NOTMUCH_PRIVATE_STATUS_OUT_OF_MEMORY = NOTMUCH_STATUS_OUT_OF_MEMORY, @@ -173,7 +173,7 @@ typedef enum _notmuch_private_status { (notmuch_status_t) private_status) /* Flags shared by various lookup functions. */ -typedef enum _notmuch_find_flags { +typedef enum { /* Lookup without creating any documents. This is the default * behavior. */ NOTMUCH_FIND_LOOKUP = 0, @@ -711,9 +711,7 @@ _notmuch_thread_create (void *ctx, /* indexopts.c */ -struct _notmuch_indexopts { - _notmuch_crypto_t crypto; -}; +struct _notmuch_indexopts; #define CONFIG_HEADER_PREFIX "index.header." diff --git a/lib/notmuch.h b/lib/notmuch.h index 5c5a024e..2e6ec2af 100644 --- a/lib/notmuch.h +++ b/lib/notmuch.h @@ -58,7 +58,7 @@ NOTMUCH_BEGIN_DECLS * version in Makefile.local. */ #define LIBNOTMUCH_MAJOR_VERSION 5 -#define LIBNOTMUCH_MINOR_VERSION 5 +#define LIBNOTMUCH_MINOR_VERSION 6 #define LIBNOTMUCH_MICRO_VERSION 0 @@ -112,7 +112,7 @@ typedef int notmuch_bool_t; * A zero value (NOTMUCH_STATUS_SUCCESS) indicates that the function * completed without error. Any other value indicates an error. */ -typedef enum _notmuch_status { +typedef enum { /** * No error occurred. */ @@ -224,6 +224,10 @@ typedef enum _notmuch_status { * Syntax error in query */ NOTMUCH_STATUS_BAD_QUERY_SYNTAX, + /** + * No mail root could be deduced from parameters and environment + */ + NOTMUCH_STATUS_NO_MAIL_ROOT, /** * Not an actual status value. Just a way to find out how many * valid status values there are. @@ -323,7 +327,7 @@ typedef enum { * config_path="" and error_message=NULL * @deprecated Deprecated as of libnotmuch 5.4 (notmuch 0.32) */ -/* NOTMUCH_DEPRECATED(5, 4) */ +NOTMUCH_DEPRECATED(5, 4) notmuch_status_t notmuch_database_open (const char *path, notmuch_database_mode_t mode, @@ -335,7 +339,7 @@ notmuch_database_open (const char *path, * @deprecated Deprecated as of libnotmuch 5.4 (notmuch 0.32) * */ -/* NOTMUCH_DEPRECATED(5, 4) */ +NOTMUCH_DEPRECATED(5, 4) notmuch_status_t notmuch_database_open_verbose (const char *path, notmuch_database_mode_t mode, @@ -1686,7 +1690,7 @@ notmuch_message_reindex (notmuch_message_t *message, /** * Message flags. */ -typedef enum _notmuch_message_flag { +typedef enum { NOTMUCH_MESSAGE_FLAG_MATCH, NOTMUCH_MESSAGE_FLAG_EXCLUDED, @@ -2532,7 +2536,7 @@ notmuch_config_list_destroy (notmuch_config_list_t *config_list); /** * Configuration keys known to libnotmuch */ -typedef enum _notmuch_config_key { +typedef enum { NOTMUCH_CONFIG_FIRST, NOTMUCH_CONFIG_DATABASE_PATH = NOTMUCH_CONFIG_FIRST, NOTMUCH_CONFIG_MAIL_ROOT, @@ -2546,6 +2550,7 @@ typedef enum _notmuch_config_key { NOTMUCH_CONFIG_OTHER_EMAIL, NOTMUCH_CONFIG_USER_NAME, NOTMUCH_CONFIG_AUTOCOMMIT, + NOTMUCH_CONFIG_EXTRA_HEADERS, NOTMUCH_CONFIG_LAST } notmuch_config_key_t; diff --git a/lib/open.cc b/lib/open.cc index a942383b..30cfcf9e 100644 --- a/lib/open.cc +++ b/lib/open.cc @@ -19,9 +19,8 @@ notmuch_database_open (const char *path, char *status_string = NULL; notmuch_status_t status; - status = notmuch_database_open_verbose (path, mode, database, - &status_string); - + status = notmuch_database_open_with_config (path, mode, "", NULL, + database, &status_string); if (status_string) { fputs (status_string, stderr); free (status_string); @@ -187,11 +186,10 @@ _db_dir_exists (const char *database_path, char **message) } static notmuch_status_t -_choose_database_path (void *ctx, +_choose_database_path (notmuch_database_t *notmuch, const char *profile, GKeyFile *key_file, const char **database_path, - bool *split, char **message) { if (! *database_path) { @@ -199,24 +197,24 @@ _choose_database_path (void *ctx, } if (! *database_path && key_file) { - char *path = g_key_file_get_value (key_file, "database", "path", NULL); + char *path = g_key_file_get_string (key_file, "database", "path", NULL); if (path) { if (path[0] == '/') - *database_path = talloc_strdup (ctx, path); + *database_path = talloc_strdup (notmuch, path); else - *database_path = talloc_asprintf (ctx, "%s/%s", getenv ("HOME"), path); + *database_path = talloc_asprintf (notmuch, "%s/%s", getenv ("HOME"), path); g_free (path); } } if (! *database_path) { notmuch_status_t status; - *database_path = _xdg_dir (ctx, "XDG_DATA_HOME", ".local/share", profile); + *database_path = _xdg_dir (notmuch, "XDG_DATA_HOME", ".local/share", profile); status = _db_dir_exists (*database_path, message); if (status) { *database_path = NULL; } else { - *split = true; + notmuch->params |= NOTMUCH_PARAM_SPLIT; } } @@ -227,7 +225,7 @@ _choose_database_path (void *ctx, if (! *database_path) { notmuch_status_t status; - *database_path = talloc_asprintf (ctx, "%s/mail", getenv ("HOME")); + *database_path = talloc_asprintf (notmuch, "%s/mail", getenv ("HOME")); status = _db_dir_exists (*database_path, message); if (status) { *database_path = NULL; @@ -511,11 +509,9 @@ notmuch_database_open_with_config (const char *database_path, char **status_string) { notmuch_status_t status = NOTMUCH_STATUS_SUCCESS; - void *local = talloc_new (NULL); notmuch_database_t *notmuch = NULL; char *message = NULL; GKeyFile *key_file = NULL; - bool split = false; _notmuch_init (); @@ -531,8 +527,8 @@ notmuch_database_open_with_config (const char *database_path, goto DONE; } - if ((status = _choose_database_path (local, profile, key_file, - &database_path, &split, + if ((status = _choose_database_path (notmuch, profile, key_file, + &database_path, &message))) goto DONE; @@ -550,8 +546,6 @@ notmuch_database_open_with_config (const char *database_path, status = _finish_open (notmuch, profile, mode, key_file, &message); DONE: - talloc_free (local); - if (key_file) g_key_file_free (key_file); @@ -613,9 +607,7 @@ notmuch_database_create_with_config (const char *database_path, const char *notmuch_path = NULL; char *message = NULL; GKeyFile *key_file = NULL; - void *local = talloc_new (NULL); int err; - bool split = false; _notmuch_init (); @@ -631,8 +623,8 @@ notmuch_database_create_with_config (const char *database_path, goto DONE; } - if ((status = _choose_database_path (local, profile, key_file, - &database_path, &split, &message))) + if ((status = _choose_database_path (notmuch, profile, key_file, + &database_path, &message))) goto DONE; status = _db_dir_exists (database_path, &message); @@ -641,37 +633,34 @@ notmuch_database_create_with_config (const char *database_path, _set_database_path (notmuch, database_path); - if (key_file && ! split) { + if (key_file && ! (notmuch->params & NOTMUCH_PARAM_SPLIT)) { char *mail_root = notmuch_canonicalize_file_name ( - g_key_file_get_value (key_file, "database", "mail_root", NULL)); + g_key_file_get_string (key_file, "database", "mail_root", NULL)); char *db_path = notmuch_canonicalize_file_name (database_path); - split = (mail_root && (0 != strcmp (mail_root, db_path))); + if (mail_root && (0 != strcmp (mail_root, db_path))) + notmuch->params |= NOTMUCH_PARAM_SPLIT; free (mail_root); free (db_path); } - if (split) { + if (notmuch->params & NOTMUCH_PARAM_SPLIT) { notmuch_path = database_path; } else { - if (! (notmuch_path = talloc_asprintf (local, "%s/%s", database_path, ".notmuch"))) { + if (! (notmuch_path = talloc_asprintf (notmuch, "%s/%s", database_path, ".notmuch"))) { status = NOTMUCH_STATUS_OUT_OF_MEMORY; goto DONE; } err = mkdir (notmuch_path, 0755); if (err) { - if (errno == EEXIST) { - status = NOTMUCH_STATUS_DATABASE_EXISTS; - talloc_free (notmuch); - notmuch = NULL; - } else { + if (errno != EEXIST) { IGNORE_RESULT (asprintf (&message, "Error: Cannot create directory %s: %s.\n", notmuch_path, strerror (errno))); status = NOTMUCH_STATUS_FILE_ERROR; + goto DONE; } - goto DONE; } } @@ -712,8 +701,6 @@ notmuch_database_create_with_config (const char *database_path, } DONE: - talloc_free (local); - if (key_file) g_key_file_free (key_file); @@ -813,11 +800,9 @@ notmuch_database_load_config (const char *database_path, char **status_string) { notmuch_status_t status = NOTMUCH_STATUS_SUCCESS, warning = NOTMUCH_STATUS_SUCCESS; - void *local = talloc_new (NULL); notmuch_database_t *notmuch = NULL; char *message = NULL; GKeyFile *key_file = NULL; - bool split = false; _notmuch_init (); @@ -839,8 +824,8 @@ notmuch_database_load_config (const char *database_path, goto DONE; } - status = _choose_database_path (local, profile, key_file, - &database_path, &split, &message); + status = _choose_database_path (notmuch, profile, key_file, + &database_path, &message); switch (status) { case NOTMUCH_STATUS_NO_DATABASE: case NOTMUCH_STATUS_SUCCESS: @@ -875,8 +860,6 @@ notmuch_database_load_config (const char *database_path, goto DONE; DONE: - talloc_free (local); - if (status_string) *status_string = message; diff --git a/lib/parse-sexp.cc b/lib/parse-sexp.cc index 356c32ea..08fd7037 100644 --- a/lib/parse-sexp.cc +++ b/lib/parse-sexp.cc @@ -32,6 +32,8 @@ typedef enum { SEXP_FLAG_EXPAND = 1 << 6, SEXP_FLAG_DO_EXPAND = 1 << 7, SEXP_FLAG_ORPHAN = 1 << 8, + SEXP_FLAG_RANGE = 1 << 9, + SEXP_FLAG_PATHNAME = 1 << 10, } _sexp_flag_t; /* @@ -66,16 +68,21 @@ static _sexp_prefix_t prefixes[] = SEXP_FLAG_FIELD | SEXP_FLAG_WILDCARD | SEXP_FLAG_EXPAND }, { "body", Xapian::Query::OP_AND, Xapian::Query::MatchAll, SEXP_FLAG_FIELD }, + { "date", Xapian::Query::OP_INVALID, Xapian::Query::MatchAll, + SEXP_FLAG_RANGE }, { "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 }, + SEXP_FLAG_FIELD | SEXP_FLAG_BOOLEAN | SEXP_FLAG_WILDCARD | SEXP_FLAG_REGEX | SEXP_FLAG_EXPAND | + SEXP_FLAG_PATHNAME }, { "id", Xapian::Query::OP_OR, Xapian::Query::MatchNothing, 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 }, + { "lastmod", Xapian::Query::OP_INVALID, Xapian::Query::MatchAll, + SEXP_FLAG_RANGE }, { "matching", Xapian::Query::OP_AND, Xapian::Query::MatchAll, SEXP_FLAG_DO_EXPAND }, { "mid", Xapian::Query::OP_OR, Xapian::Query::MatchNothing, @@ -89,7 +96,8 @@ static _sexp_prefix_t prefixes[] = { "or", Xapian::Query::OP_OR, Xapian::Query::MatchNothing, SEXP_FLAG_NONE }, { "path", Xapian::Query::OP_OR, Xapian::Query::MatchNothing, - SEXP_FLAG_FIELD | SEXP_FLAG_BOOLEAN | SEXP_FLAG_WILDCARD | SEXP_FLAG_REGEX }, + SEXP_FLAG_FIELD | SEXP_FLAG_BOOLEAN | SEXP_FLAG_WILDCARD | SEXP_FLAG_REGEX | + SEXP_FLAG_PATHNAME }, { "property", Xapian::Query::OP_AND, Xapian::Query::MatchAll, SEXP_FLAG_FIELD | SEXP_FLAG_BOOLEAN | SEXP_FLAG_WILDCARD | SEXP_FLAG_REGEX | SEXP_FLAG_EXPAND }, { "query", Xapian::Query::OP_INVALID, Xapian::Query::MatchNothing, @@ -446,6 +454,79 @@ _sexp_expand_param (notmuch_database_t *notmuch, const _sexp_prefix_t *parent, return NOTMUCH_STATUS_BAD_QUERY_SYNTAX; } +static notmuch_status_t +_sexp_parse_range (notmuch_database_t *notmuch, const _sexp_prefix_t *prefix, + const sexp_t *sx, Xapian::Query &output) +{ + const char *from, *to; + std::string msg; + + /* empty range matches everything */ + if (! sx) { + output = Xapian::Query::MatchAll; + return NOTMUCH_STATUS_SUCCESS; + } + + if (sx->ty == SEXP_LIST) { + _notmuch_database_log (notmuch, "expected atom as first argument of '%s'\n", prefix->name); + return NOTMUCH_STATUS_BAD_QUERY_SYNTAX; + } + + from = sx->val; + to = from; + + if (sx->next) { + if (sx->next->ty == SEXP_LIST) { + _notmuch_database_log (notmuch, "expected atom as second argument of '%s'\n", + prefix->name); + return NOTMUCH_STATUS_BAD_QUERY_SYNTAX; + } + + if (sx->next->next) { + _notmuch_database_log (notmuch, "'%s' expects maximum of two arguments\n", prefix->name); + return NOTMUCH_STATUS_BAD_QUERY_SYNTAX; + } + + to = sx->next->val; + } + + if (strcmp (prefix->name, "date") == 0) { + notmuch_status_t status; + status = _notmuch_date_strings_to_query (NOTMUCH_VALUE_TIMESTAMP, from, to, output, msg); + if (status) { + if (! msg.empty ()) + _notmuch_database_log (notmuch, "%s\n", msg.c_str ()); + } + return status; + } + + if (strcmp (prefix->name, "lastmod") == 0) { + long from_idx, to_idx; + + try { + from_idx = std::stol (from); + } catch (std::logic_error &e) { + _notmuch_database_log (notmuch, "bad 'from' revision: '%s'\n", from); + return NOTMUCH_STATUS_BAD_QUERY_SYNTAX; + } + + try { + to_idx = std::stol (to); + } catch (std::logic_error &e) { + _notmuch_database_log (notmuch, "bad 'to' revision: '%s'\n", to); + return NOTMUCH_STATUS_BAD_QUERY_SYNTAX; + } + + output = Xapian::Query (Xapian::Query::OP_VALUE_RANGE, NOTMUCH_VALUE_LAST_MOD, + Xapian::sortable_serialise (from_idx), + Xapian::sortable_serialise (to_idx)); + return NOTMUCH_STATUS_SUCCESS; + } + + _notmuch_database_log (notmuch, "unimplimented range prefix: '%s'\n", prefix->name); + return NOTMUCH_STATUS_BAD_QUERY_SYNTAX; +} + /* Here we expect the s-expression to be a proper list, with first * element defining and operation, or as a special case the empty * list */ @@ -467,8 +548,13 @@ _sexp_to_xapian_query (notmuch_database_t *notmuch, const _sexp_prefix_t *parent return _sexp_parse_wildcard (notmuch, parent, env, "", output); } + char *atom = sx->val; + + if (parent && parent->flags & SEXP_FLAG_PATHNAME) + strip_trailing (atom, '/'); + if (parent && (parent->flags & SEXP_FLAG_BOOLEAN)) { - output = Xapian::Query (term_prefix + sx->val); + output = Xapian::Query (term_prefix + atom); return NOTMUCH_STATUS_SUCCESS; } @@ -519,7 +605,7 @@ _sexp_to_xapian_query (notmuch_database_t *notmuch, const _sexp_prefix_t *parent for (_sexp_prefix_t *prefix = prefixes; prefix && prefix->name; prefix++) { if (strcmp (prefix->name, sx->list->val) == 0) { - if (prefix->flags & SEXP_FLAG_FIELD) { + if (prefix->flags & (SEXP_FLAG_FIELD | SEXP_FLAG_RANGE)) { if (parent) { _notmuch_database_log (notmuch, "nested field: '%s' inside '%s'\n", prefix->name, parent->name); @@ -541,6 +627,9 @@ _sexp_to_xapian_query (notmuch_database_t *notmuch, const _sexp_prefix_t *parent return NOTMUCH_STATUS_BAD_QUERY_SYNTAX; } + if (prefix->flags & SEXP_FLAG_RANGE) + return _sexp_parse_range (notmuch, prefix, sx->list->next, output); + if (strcmp (prefix->name, "infix") == 0) { return _sexp_parse_infix (notmuch, sx->list->next, output); } diff --git a/lib/parse-time-vrp.cc b/lib/parse-time-vrp.cc index 22bf2ab5..6b07970b 100644 --- a/lib/parse-time-vrp.cc +++ b/lib/parse-time-vrp.cc @@ -24,22 +24,28 @@ #include "parse-time-vrp.h" #include "parse-time-string.h" -Xapian::Query -ParseTimeRangeProcessor::operator() (const std::string &begin, const std::string &end) +notmuch_status_t +_notmuch_date_strings_to_query (Xapian::valueno slot, + const std::string &begin, const std::string &end, + Xapian::Query &output, std::string &msg) { double from = DBL_MIN, to = DBL_MAX; time_t parsed_time, now; std::string str; /* Use the same 'now' for begin and end. */ - if (time (&now) == (time_t) -1) - throw Xapian::QueryParserError ("unable to get current time"); + if (time (&now) == (time_t) -1) { + msg = "unable to get current time"; + return NOTMUCH_STATUS_ILLEGAL_ARGUMENT; + } if (! begin.empty ()) { - if (parse_time_string (begin.c_str (), &parsed_time, &now, PARSE_TIME_ROUND_DOWN)) - throw Xapian::QueryParserError ("Didn't understand date specification '" + begin + "'"); - else - from = (double) parsed_time; + if (parse_time_string (begin.c_str (), &parsed_time, &now, PARSE_TIME_ROUND_DOWN)) { + msg = "Didn't understand date specification '" + begin + "'"; + return NOTMUCH_STATUS_BAD_QUERY_SYNTAX; + } + + from = (double) parsed_time; } if (! end.empty ()) { @@ -48,15 +54,30 @@ ParseTimeRangeProcessor::operator() (const std::string &begin, const std::string else str = end; - if (parse_time_string (str.c_str (), &parsed_time, &now, PARSE_TIME_ROUND_UP_INCLUSIVE)) - throw Xapian::QueryParserError ("Didn't understand date specification '" + str + "'"); - else - to = (double) parsed_time; + if (parse_time_string (str.c_str (), &parsed_time, &now, PARSE_TIME_ROUND_UP_INCLUSIVE)) { + msg = "Didn't understand date specification '" + str + "'"; + return NOTMUCH_STATUS_BAD_QUERY_SYNTAX; + } + to = (double) parsed_time; } - return Xapian::Query (Xapian::Query::OP_VALUE_RANGE, slot, - Xapian::sortable_serialise (from), - Xapian::sortable_serialise (to)); + output = Xapian::Query (Xapian::Query::OP_VALUE_RANGE, slot, + Xapian::sortable_serialise (from), + Xapian::sortable_serialise (to)); + return NOTMUCH_STATUS_SUCCESS; +} + +Xapian::Query +ParseTimeRangeProcessor::operator() (const std::string &begin, const std::string &end) +{ + + Xapian::Query output; + std::string msg; + + if (_notmuch_date_strings_to_query (slot, begin, end, output, msg)) + throw Xapian::QueryParserError (msg); + + return output; } /* XXX TODO: is throwing an exception the right thing to do here? */ diff --git a/lib/prefix.cc b/lib/prefix.cc index 0d92bdd7..857c05b9 100644 --- a/lib/prefix.cc +++ b/lib/prefix.cc @@ -46,7 +46,7 @@ prefix_t prefix_table[] = { { "mid", "Q", NOTMUCH_FIELD_EXTERNAL | NOTMUCH_FIELD_PROCESSOR }, { "path", "P", NOTMUCH_FIELD_EXTERNAL | - NOTMUCH_FIELD_PROCESSOR }, + NOTMUCH_FIELD_PROCESSOR | NOTMUCH_FIELD_STRIP_TRAILING_SLASH }, { "property", "XPROPERTY", NOTMUCH_FIELD_EXTERNAL }, /* * Unconditionally add ':' to reduce potential ambiguity with @@ -55,7 +55,7 @@ prefix_t prefix_table[] = { * discussion. */ { "folder", "XFOLDER:", NOTMUCH_FIELD_EXTERNAL | - NOTMUCH_FIELD_PROCESSOR }, + NOTMUCH_FIELD_PROCESSOR | NOTMUCH_FIELD_STRIP_TRAILING_SLASH }, { "date", NULL, NOTMUCH_FIELD_EXTERNAL | NOTMUCH_FIELD_PROCESSOR }, { "query", NULL, NOTMUCH_FIELD_EXTERNAL | diff --git a/lib/regexp-fields.cc b/lib/regexp-fields.cc index c6d9d94f..7e9d959c 100644 --- a/lib/regexp-fields.cc +++ b/lib/regexp-fields.cc @@ -235,7 +235,15 @@ RegexpFieldProcessor::operator() (const std::string & str) return parser.parse_query (query_str, NOTMUCH_QUERY_PARSER_FLAGS, term_prefix); } else { /* Boolean prefix */ - std::string term = term_prefix + str; + std::string query_str; + std::string term; + + if (str.length () > 1 && str.at (str.size () - 1) == '/') + query_str = str.substr (0, str.size () - 1); + else + query_str = str; + + term = term_prefix + query_str; return Xapian::Query (term); } } diff --git a/notmuch-client.h b/notmuch-client.h index 96d81166..9f57ac5e 100644 --- a/notmuch-client.h +++ b/notmuch-client.h @@ -65,7 +65,7 @@ struct sprinter; struct notmuch_show_params; typedef struct notmuch_show_format { - struct sprinter *(*new_sprinter)(const void *ctx, FILE *stream); + struct sprinter *(*new_sprinter)(notmuch_database_t * db, FILE *stream); notmuch_status_t (*part)(const void *ctx, struct sprinter *sprinter, struct mime_node *node, int indent, const struct notmuch_show_params *params); @@ -426,13 +426,13 @@ mime_node_seek_dfs (mime_node_t *node, int n); const _notmuch_message_crypto_t * mime_node_get_message_crypto_status (mime_node_t *node); -typedef enum dump_formats { +typedef enum { DUMP_FORMAT_AUTO, DUMP_FORMAT_BATCH_TAG, DUMP_FORMAT_SUP } dump_format_t; -typedef enum dump_includes { +typedef enum { DUMP_INCLUDE_TAGS = 1, DUMP_INCLUDE_CONFIG = 2, DUMP_INCLUDE_PROPERTIES = 4 @@ -499,11 +499,10 @@ int notmuch_minimal_options (const char *subcommand_name, struct _notmuch_client_indexing_cli_choices { int decrypt_policy; bool decrypt_policy_set; - notmuch_indexopts_t *opts; }; extern struct _notmuch_client_indexing_cli_choices indexing_cli_choices; extern const notmuch_opt_desc_t notmuch_shared_indexing_options []; notmuch_status_t -notmuch_process_shared_indexing_options (notmuch_database_t *notmuch); +notmuch_process_shared_indexing_options (notmuch_indexopts_t *opts); #endif diff --git a/notmuch-config.c b/notmuch-config.c index db00a26c..e9456d79 100644 --- a/notmuch-config.c +++ b/notmuch-config.c @@ -383,7 +383,10 @@ _config_set_list (notmuch_conffile_t *config, const char *list[], size_t length) { - g_key_file_set_string_list (config->key_file, group, key, list, length); + if (length > 1) + g_key_file_set_string_list (config->key_file, group, key, list, length); + else + g_key_file_set_string (config->key_file, group, key, list[0]); } void @@ -680,9 +683,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", + printf ("%ssexp_queries=%s\n", BUILT_WITH_PREFIX, - notmuch_built_with ("sexpr_query") ? "true" : "false"); + notmuch_built_with ("sexp_queries") ? "true" : "false"); } static int diff --git a/notmuch-insert.c b/notmuch-insert.c index 72e2e35f..214d4d03 100644 --- a/notmuch-insert.c +++ b/notmuch-insert.c @@ -461,6 +461,8 @@ notmuch_insert_command (notmuch_database_t *notmuch, int argc, char *argv[]) char *maildir; char *newpath; int opt_index; + notmuch_indexopts_t *indexopts = notmuch_database_get_default_indexopts (notmuch); + void *local = talloc_new (NULL); notmuch_opt_desc_t options[] = { @@ -550,7 +552,7 @@ notmuch_insert_command (notmuch_database_t *notmuch, int argc, char *argv[]) return EXIT_FAILURE; } - status = notmuch_process_shared_indexing_options (notmuch); + status = notmuch_process_shared_indexing_options (indexopts); if (status != NOTMUCH_STATUS_SUCCESS) { fprintf (stderr, "Error: Failed to process index options. (%s)\n", notmuch_status_to_string (status)); @@ -558,7 +560,7 @@ notmuch_insert_command (notmuch_database_t *notmuch, int argc, char *argv[]) } /* Index the message. */ - status = add_file (notmuch, newpath, tag_ops, synchronize_flags, keep, indexing_cli_choices.opts); + status = add_file (notmuch, newpath, tag_ops, synchronize_flags, keep, indexopts); /* Commit changes. */ close_status = notmuch_database_close (notmuch); diff --git a/notmuch-new.c b/notmuch-new.c index b7a5f2ea..346e6469 100644 --- a/notmuch-new.c +++ b/notmuch-new.c @@ -45,6 +45,7 @@ typedef struct { const char *db_path; const char *mail_root; + notmuch_indexopts_t *indexopts; int output_is_a_tty; enum verbosity verbosity; bool debug; @@ -376,7 +377,7 @@ add_file (notmuch_database_t *notmuch, const char *filename, if (status) goto DONE; - status = notmuch_database_index_file (notmuch, filename, indexing_cli_choices.opts, &message); + status = notmuch_database_index_file (notmuch, filename, state->indexopts, &message); switch (status) { /* Success. */ case NOTMUCH_STATUS_SUCCESS: @@ -600,11 +601,12 @@ add_files (notmuch_database_t *notmuch, continue; } - /* Ignore the .notmuch directory and any "tmp" directory + /* Ignore any top level .notmuch directory and any "tmp" directory * that appears within a maildir. */ if ((is_maildir && strcmp (entry->d_name, "tmp") == 0) || - strcmp (entry->d_name, ".notmuch") == 0) + (strcmp (entry->d_name, ".notmuch") == 0 + && (strcmp (path, state->mail_root)) == 0)) continue; next = talloc_asprintf (notmuch, "%s/%s", path, entry->d_name); @@ -1150,6 +1152,8 @@ notmuch_new_command (notmuch_database_t *notmuch, int argc, char *argv[]) else if (verbose) add_files_state.verbosity = VERBOSITY_VERBOSE; + add_files_state.indexopts = notmuch_database_get_default_indexopts (notmuch); + add_files_state.new_tags = notmuch_config_get_values (notmuch, NOTMUCH_CONFIG_NEW_TAGS); if (print_status_database ( @@ -1217,7 +1221,7 @@ notmuch_new_command (notmuch_database_t *notmuch, int argc, char *argv[]) if (notmuch == NULL) return EXIT_FAILURE; - status = notmuch_process_shared_indexing_options (notmuch); + status = notmuch_process_shared_indexing_options (add_files_state.indexopts); if (status != NOTMUCH_STATUS_SUCCESS) { fprintf (stderr, "Error: Failed to process index options. (%s)\n", notmuch_status_to_string (status)); diff --git a/notmuch-reindex.c b/notmuch-reindex.c index 49eacd47..e9a65456 100644 --- a/notmuch-reindex.c +++ b/notmuch-reindex.c @@ -90,6 +90,7 @@ notmuch_reindex_command (notmuch_database_t *notmuch, int argc, char *argv[]) int opt_index; int ret; notmuch_status_t status; + notmuch_indexopts_t *indexopts = notmuch_database_get_default_indexopts (notmuch); /* Set up our handler for SIGINT */ memset (&action, 0, sizeof (struct sigaction)); @@ -110,7 +111,7 @@ notmuch_reindex_command (notmuch_database_t *notmuch, int argc, char *argv[]) notmuch_process_shared_options (notmuch, argv[0]); - status = notmuch_process_shared_indexing_options (notmuch); + status = notmuch_process_shared_indexing_options (indexopts); if (status != NOTMUCH_STATUS_SUCCESS) { fprintf (stderr, "Error: Failed to process index options. (%s)\n", notmuch_status_to_string (status)); @@ -128,7 +129,7 @@ notmuch_reindex_command (notmuch_database_t *notmuch, int argc, char *argv[]) return EXIT_FAILURE; } - ret = reindex_query (notmuch, query_string, indexing_cli_choices.opts); + ret = reindex_query (notmuch, query_string, indexopts); notmuch_database_destroy (notmuch); diff --git a/notmuch-show.c b/notmuch-show.c index 2848c9c3..6a54d9c1 100644 --- a/notmuch-show.c +++ b/notmuch-show.c @@ -209,6 +209,30 @@ _is_from_line (const char *line) return 0; } +/* Output extra headers if configured with the `show.extra_headers' + * configuration option + */ +static void +format_extra_headers_sprinter (sprinter_t *sp, GMimeMessage *message) +{ + GMimeHeaderList *header_list = g_mime_object_get_header_list (GMIME_OBJECT (message)); + + for (notmuch_config_values_t *extra_headers = notmuch_config_get_values ( + sp->notmuch, NOTMUCH_CONFIG_EXTRA_HEADERS); + notmuch_config_values_valid (extra_headers); + notmuch_config_values_move_to_next (extra_headers)) { + GMimeHeader *header; + const char *header_name = notmuch_config_values_get (extra_headers); + + header = g_mime_header_list_get_header (header_list, header_name); + if (header == NULL) + continue; + + sp->map_key (sp, g_mime_header_get_name (header)); + sp->string (sp, g_mime_header_get_value (header)); + } +} + void format_headers_sprinter (sprinter_t *sp, GMimeMessage *message, bool reply, const _notmuch_message_crypto_t *msg_crypto) @@ -269,6 +293,9 @@ format_headers_sprinter (sprinter_t *sp, GMimeMessage *message, sp->string (sp, g_mime_message_get_date_string (sp, message)); } + /* Output extra headers the user has configured, if any */ + if (! reply) + format_extra_headers_sprinter (sp, message); sp->end (sp); talloc_free (local); } diff --git a/notmuch.c b/notmuch.c index 3fb58bf2..ac25ae18 100644 --- a/notmuch.c +++ b/notmuch.c @@ -141,21 +141,18 @@ const notmuch_opt_desc_t notmuch_shared_indexing_options [] = { notmuch_status_t -notmuch_process_shared_indexing_options (notmuch_database_t *notmuch) +notmuch_process_shared_indexing_options (notmuch_indexopts_t *opts) { - if (indexing_cli_choices.opts == NULL) - indexing_cli_choices.opts = notmuch_database_get_default_indexopts (notmuch); + if (opts == NULL) + return NOTMUCH_STATUS_NULL_POINTER; + if (indexing_cli_choices.decrypt_policy_set) { notmuch_status_t status; - if (indexing_cli_choices.opts == NULL) - return NOTMUCH_STATUS_OUT_OF_MEMORY; - status = notmuch_indexopts_set_decrypt_policy (indexing_cli_choices.opts, + status = notmuch_indexopts_set_decrypt_policy (opts, indexing_cli_choices.decrypt_policy); if (status != NOTMUCH_STATUS_SUCCESS) { fprintf (stderr, "Error: Failed to set index decryption policy to %d. (%s)\n", indexing_cli_choices.decrypt_policy, notmuch_status_to_string (status)); - notmuch_indexopts_destroy (indexing_cli_choices.opts); - indexing_cli_choices.opts = NULL; return status; } } diff --git a/sprinter-json.c b/sprinter-json.c index c7f4851c..502f89fb 100644 --- a/sprinter-json.c +++ b/sprinter-json.c @@ -172,7 +172,7 @@ json_separator (struct sprinter *sp) } struct sprinter * -sprinter_json_create (const void *ctx, FILE *stream) +sprinter_json_create (notmuch_database_t *db, FILE *stream) { static const struct sprinter_json template = { .vtable = { @@ -192,11 +192,12 @@ sprinter_json_create (const void *ctx, FILE *stream) }; struct sprinter_json *res; - res = talloc (ctx, struct sprinter_json); + res = talloc (db, struct sprinter_json); if (! res) return NULL; *res = template; + res->vtable.notmuch = db; res->stream = stream; return &res->vtable; } diff --git a/sprinter-sexp.c b/sprinter-sexp.c index 63b25428..e37cb1f9 100644 --- a/sprinter-sexp.c +++ b/sprinter-sexp.c @@ -207,7 +207,7 @@ sexp_separator (struct sprinter *sp) } struct sprinter * -sprinter_sexp_create (const void *ctx, FILE *stream) +sprinter_sexp_create (notmuch_database_t *db, FILE *stream) { static const struct sprinter_sexp template = { .vtable = { @@ -227,11 +227,12 @@ sprinter_sexp_create (const void *ctx, FILE *stream) }; struct sprinter_sexp *res; - res = talloc (ctx, struct sprinter_sexp); + res = talloc (db, struct sprinter_sexp); if (! res) return NULL; *res = template; + res->vtable.notmuch = db; res->stream = stream; return &res->vtable; } diff --git a/sprinter-text.c b/sprinter-text.c index c75ec5be..99330a94 100644 --- a/sprinter-text.c +++ b/sprinter-text.c @@ -114,7 +114,7 @@ text_map_key (unused (struct sprinter *sp), unused (const char *key)) } struct sprinter * -sprinter_text_create (const void *ctx, FILE *stream) +sprinter_text_create (notmuch_database_t *db, FILE *stream) { static const struct sprinter_text template = { .vtable = { @@ -134,21 +134,22 @@ sprinter_text_create (const void *ctx, FILE *stream) }; struct sprinter_text *res; - res = talloc (ctx, struct sprinter_text); + res = talloc (db, struct sprinter_text); if (! res) return NULL; *res = template; + res->vtable.notmuch = db; res->stream = stream; return &res->vtable; } struct sprinter * -sprinter_text0_create (const void *ctx, FILE *stream) +sprinter_text0_create (notmuch_database_t *db, FILE *stream) { struct sprinter *sp; - sp = sprinter_text_create (ctx, stream); + sp = sprinter_text_create (db, stream); if (! sp) return NULL; diff --git a/sprinter.h b/sprinter.h index 528d8a2d..fd08641c 100644 --- a/sprinter.h +++ b/sprinter.h @@ -9,6 +9,11 @@ * (strings, integers and booleans). */ typedef struct sprinter { + /* + * Open notmuch database + */ + notmuch_database_t *notmuch; + /* Start a new map/dictionary structure. This should be followed by * a sequence of alternating calls to map_key and one of the * value-printing functions until the map is ended by end. @@ -65,20 +70,20 @@ typedef struct sprinter { /* Create a new unstructured printer that emits the default text format * for "notmuch search". */ struct sprinter * -sprinter_text_create (const void *ctx, FILE *stream); +sprinter_text_create (notmuch_database_t *db, FILE *stream); /* Create a new unstructured printer that emits the text format for * "notmuch search", with each field separated by a null character * instead of the newline character. */ struct sprinter * -sprinter_text0_create (const void *ctx, FILE *stream); +sprinter_text0_create (notmuch_database_t *db, FILE *stream); /* Create a new structure printer that emits JSON. */ struct sprinter * -sprinter_json_create (const void *ctx, FILE *stream); +sprinter_json_create (notmuch_database_t *db, FILE *stream); /* Create a new structure printer that emits S-Expressions. */ struct sprinter * -sprinter_sexp_create (const void *ctx, FILE *stream); +sprinter_sexp_create (notmuch_database_t *db, FILE *stream); #endif // NOTMUCH_SPRINTER_H diff --git a/test/T030-config.sh b/test/T030-config.sh index 3a585d1b..43bbce31 100755 --- a/test/T030-config.sh +++ b/test/T030-config.sh @@ -51,7 +51,7 @@ cat < EXPECTED built_with.compact=something built_with.field_processor=something built_with.retry_lock=something -built_with.sexpr_query=something +built_with.sexp_queries=something database.autocommit=8000 database.mail_root=MAIL_DIR database.path=MAIL_DIR @@ -67,6 +67,35 @@ user.primary_email=test_suite@notmuchmail.org EOF test_expect_equal_file EXPECTED OUTPUT +test_begin_subtest "Round trip config item with leading spaces" +test_subtest_known_broken +notmuch config set foo.bar " thing" +output=$(notmuch config get foo.bar) +test_expect_equal "${output}" " thing" + +test_begin_subtest "Round trip config item with leading tab" +test_subtest_known_broken +notmuch config set foo.bar " thing" +output=$(notmuch config get foo.bar) +test_expect_equal "${output}" " thing" + +test_begin_subtest "Round trip config item with embedded tab" +notmuch config set foo.bar "thing other" +output=$(notmuch config get foo.bar) +test_expect_equal "${output}" "thing other" + +test_begin_subtest "Round trip config item with embedded backslash" +notmuch config set foo.bar 'thing\other' +output=$(notmuch config get foo.bar) +test_expect_equal "${output}" "thing\other" + +test_begin_subtest "Round trip config item with embedded NL/CR" +notmuch config set foo.bar 'thing + other' +output=$(notmuch config get foo.bar) +test_expect_equal "${output}" "thing + other" + test_begin_subtest "Top level --config=FILE option" cp "${NOTMUCH_CONFIG}" alt-config notmuch --config=alt-config config set user.name "Another Name" diff --git a/test/T040-setup.sh b/test/T040-setup.sh index 42c621c8..10b29ec3 100755 --- a/test/T040-setup.sh +++ b/test/T040-setup.sh @@ -23,6 +23,13 @@ EOF expected_dir=$NOTMUCH_SRCDIR/test/setup.expected-output test_expect_equal_file ${expected_dir}/config-with-comments new-notmuch-config +test_begin_subtest "setup consistent with config-set for single items" +# note this relies on the config state from the previous test. +notmuch --config=new-notmuch-config config list > list.setup +notmuch --config=new-notmuch-config config set search.exclude_tags baz +notmuch --config=new-notmuch-config config list > list.config +test_expect_equal_file list.setup list.config + test_begin_subtest "notmuch with a config but without a database suggests notmuch new" notmuch 2>&1 | notmuch_dir_sanitize > OUTPUT cat < EXPECTED diff --git a/test/T050-new.sh b/test/T050-new.sh index 1141c1e3..6791f87c 100755 --- a/test/T050-new.sh +++ b/test/T050-new.sh @@ -329,6 +329,18 @@ notmuch config set new.tags "foo;;bar" output=$(NOTMUCH_NEW --quiet 2>&1) test_expect_equal "$output" "" +test_begin_subtest "leading/trailing whitespace in new.tags is ignored" +# avoid complications with leading spaces and "notmuch config" +sed -i 's/^tags=.*$/tags= fu bar ; ; bar /' notmuch-config +add_message +NOTMUCH_NEW --quiet +notmuch dump id:$gen_msg_id | sed 's/ --.*$//' > OUTPUT +cat <EXPECTED +#notmuch-dump batch-tag:3 config,properties,tags ++bar +fu%20bar +EOF +test_expect_equal_file EXPECTED OUTPUT + test_begin_subtest "Tags starting with '-' in new.tags are forbidden" notmuch config set new.tags "-foo;bar" output=$(NOTMUCH_NEW --debug 2>&1) @@ -339,6 +351,16 @@ test_expect_code 1 "NOTMUCH_NEW --debug 2>&1" notmuch config set new.tags $OLDCONFIG +test_begin_subtest ".notmuch only ignored at top level" +generate_message '[dir]=foo/bar/.notmuch/cur' '[subject]="Do not ignore, very important"' +NOTMUCH_NEW > OUTPUT +notmuch search subject:Do-not-ignore | notmuch_search_sanitize >> OUTPUT +cat < EXPECTED +Added 1 new message to the database. +thread:XXX 2001-01-05 [1/1] Notmuch Test Suite; Do not ignore, very important (inbox unread) +EOF +test_expect_equal_file EXPECTED OUTPUT + test_begin_subtest "RFC822 group names are indexed" test_subtest_known_broken generate_message [to]="undisclosed-recipients:" @@ -368,31 +390,26 @@ chmod u+w ${MAIL_DIR}/.notmuch/xapian/*.* test_expect_equal "$output" "A Xapian exception occurred opening database" -test_begin_subtest "Handle files vanishing between scandir and add_file" +make_shim dif-shim< -# A file for scandir to find. It won't get indexed, so can be empty. -touch ${MAIL_DIR}/vanish +WRAP_DLFUNC(notmuch_status_t, notmuch_database_index_file, \ + (notmuch_database_t *database, const char *filename, notmuch_indexopts_t *indexopts, notmuch_message_t **message)) -# Breakpoint to remove the file before indexing -cat < notmuch-new-vanish.gdb -set breakpoint pending on -set logging file notmuch-new-vanish-gdb.log -set logging on -break notmuch_database_index_file -commands -shell rm -f ${MAIL_DIR}/vanish -continue -end -run + if (unlink ("${MAIL_DIR}/vanish")) { + fprintf (stderr, "unlink failed\n"); + exit (42); + } + return notmuch_database_index_file_orig (database, filename, indexopts, message); +} EOF -${TEST_GDB} --batch-silent --return-child-result -x notmuch-new-vanish.gdb \ - --args notmuch new 2>OUTPUT 1>/dev/null -echo "exit status: $?" >> OUTPUT - -# Clean up the file in case gdb isn't available. -rm -f ${MAIL_DIR}/vanish +test_begin_subtest "Handle files vanishing between scandir and add_file" +# A file for scandir to find. It won't get indexed, so can be empty. +touch ${MAIL_DIR}/vanish +notmuch_with_shim dif-shim new 2>OUTPUT 1>/dev/null +echo "exit status: $?" >> OUTPUT cat < EXPECTED Unexpected error with file ${MAIL_DIR}/vanish add_file: Something went wrong trying to read or write a file diff --git a/test/T051-new-renames.sh b/test/T051-new-renames.sh new file mode 100755 index 00000000..ebd06be1 --- /dev/null +++ b/test/T051-new-renames.sh @@ -0,0 +1,40 @@ +#!/usr/bin/env bash +test_description='"notmuch new" with directory renames' +. $(dirname "$0")/test-lib.sh || exit 1 + +for loop in {1..10}; do + +rm -rf ${MAIL_DIR} + +for i in {1..10}; do + generate_message '[dir]=foo' '[subject]="Message foo $i"' +done + +for i in {1..10}; do + generate_message '[dir]=bar' '[subject]="Message bar $i"' +done + +test_begin_subtest "Index the messages, round $loop" +output=$(NOTMUCH_NEW) +test_expect_equal "$output" "Added 20 new messages to the database." + +all_files=$(notmuch search --output=files \*) +count_foo=$(notmuch count folder:foo) + +test_begin_subtest "Rename folder" +mv ${MAIL_DIR}/foo ${MAIL_DIR}/baz +output=$(NOTMUCH_NEW) +test_expect_equal "$output" "No new mail. Detected $count_foo file renames." + +test_begin_subtest "Rename folder back" +mv ${MAIL_DIR}/baz ${MAIL_DIR}/foo +output=$(NOTMUCH_NEW) +test_expect_equal "$output" "No new mail. Detected $count_foo file renames." + +test_begin_subtest "Files remain the same" +output=$(notmuch search --output=files \*) +test_expect_equal "$output" "$all_files" + +done + +test_done diff --git a/test/T055-path-config.sh b/test/T055-path-config.sh index 6d9fb402..1df240dd 100755 --- a/test/T055-path-config.sh +++ b/test/T055-path-config.sh @@ -277,7 +277,7 @@ EOF built_with.compact=something built_with.field_processor=something built_with.retry_lock=something -built_with.sexpr_query=something +built_with.sexp_queries=something database.autocommit=8000 database.backup_dir database.hook_dir @@ -318,7 +318,14 @@ to=m.header('To') print(to) EOF test_expect_equal_file EXPECTED OUTPUT - ;& # fall through + ;; + *) + backup_database + test_begin_subtest ".notmuch without xapian/ handled gracefully ($config)" + rm -r $XAPIAN_PATH + test_expect_success "notmuch new" + restore_database + ;; esac case $config in diff --git a/test/T060-count.sh b/test/T060-count.sh index 6ad80df9..48146706 100755 --- a/test/T060-count.sh +++ b/test/T060-count.sh @@ -102,22 +102,25 @@ output=$(sed 's/^\(A Xapian exception [^:]*\):.*$/\1/' OUTPUT) test_expect_equal "${output}" "A Xapian exception occurred opening database" restore_database -cat < count-files.gdb -set breakpoint pending on -set logging file count-files-gdb.log -set logging on -break count_files -commands -shell cp /dev/null ${MAIL_DIR}/.notmuch/xapian/postlist.* -continue -end -run +make_shim qsm-shim< + +WRAP_DLFUNC (notmuch_status_t, notmuch_query_search_messages, (notmuch_query_t *query, notmuch_messages_t **messages)) + + /* XXX WARNING THIS CORRUPTS THE DATABASE */ + int fd = open ("target_postlist", O_WRONLY|O_TRUNC); + if (fd < 0) + exit (8); + close (fd); + + return notmuch_query_search_messages_orig(query, messages); +} EOF backup_database test_begin_subtest "error message from query_search_messages" -${TEST_GDB} --batch-silent --return-child-result -x count-files.gdb \ - --args notmuch count --output=files '*' 2>OUTPUT 1>/dev/null +ln -s ${MAIL_DIR}/.notmuch/xapian/postlist.* target_postlist +notmuch_with_shim qsm-shim count --output=files '*' 2>OUTPUT 1>/dev/null cat < EXPECTED notmuch count: A Xapian exception occurred A Xapian exception occurred performing query diff --git a/test/T070-insert.sh b/test/T070-insert.sh index 208deb1c..ec170b30 100755 --- a/test/T070-insert.sh +++ b/test/T070-insert.sh @@ -234,6 +234,18 @@ output=$(notmuch show --format=json id:$gen_msg_id) test_json_nodes <<<"$output" \ 'new_tags:[0][0][0]["tags"] = ["bar", "foo"]' +test_begin_subtest "leading/trailing whitespace in new.tags is ignored" +# avoid complications with leading spaces and "notmuch config" +sed -i 's/^tags=.*$/tags= fu bar ; ; bar /' notmuch-config +gen_insert_msg +notmuch insert < $gen_msg_filename +notmuch dump id:$gen_msg_id | sed 's/ --.*$//' > OUTPUT +cat <EXPECTED +#notmuch-dump batch-tag:3 config,properties,tags ++bar +fu%20bar +EOF +test_expect_equal_file EXPECTED OUTPUT + test_begin_subtest "Tags starting with '-' in new.tags are forbidden" notmuch config set new.tags "-foo;bar" gen_insert_msg diff --git a/test/T081-sexpr-search.sh b/test/T081-sexpr-search.sh index 2a8ad5f1..e2936cd7 100755 --- a/test/T081-sexpr-search.sh +++ b/test/T081-sexpr-search.sh @@ -185,6 +185,50 @@ notmuch search folder:'""' > EXPECTED notmuch search --query=sexp '(folder "")' > OUTPUT test_expect_equal_file EXPECTED OUTPUT +test_begin_subtest "Search by 'folder' with --output=files" +output=$(notmuch search --output=files --query=sexp '(folder bad/news)' | notmuch_search_files_sanitize) +test_expect_equal "$output" "MAIL_DIR/bad/news/msg-XXX +MAIL_DIR/duplicate/bad/news/msg-XXX" + +test_begin_subtest "Search by 'folder' with --output=files (trailing /)" +output=$(notmuch search --output=files --query=sexp '(folder bad/news/)' | notmuch_search_files_sanitize) +test_expect_equal "$output" "MAIL_DIR/bad/news/msg-XXX +MAIL_DIR/duplicate/bad/news/msg-XXX" + +test_begin_subtest "Search by 'folder' (multiple)" +output=$(notmuch search --query=sexp '(folder bad bad/news things/bad)' | notmuch_search_sanitize) +test_expect_equal "$output" "thread:XXX 2001-01-05 [1/1] Notmuch Test Suite; To the bone (inbox unread) +thread:XXX 2001-01-05 [1/1(2)] Notmuch Test Suite; Bears (inbox unread) +thread:XXX 2001-01-05 [1/1] Notmuch Test Suite; Bites, stings, sad feelings (inbox unread)" + +test_begin_subtest "Search by 'folder' (multiple, trailing /)" +output=$(notmuch search --query=sexp '(folder bad bad/news/ things/bad)' | notmuch_search_sanitize) +test_expect_equal "$output" "thread:XXX 2001-01-05 [1/1] Notmuch Test Suite; To the bone (inbox unread) +thread:XXX 2001-01-05 [1/1(2)] Notmuch Test Suite; Bears (inbox unread) +thread:XXX 2001-01-05 [1/1] Notmuch Test Suite; Bites, stings, sad feelings (inbox unread)" + +test_begin_subtest "Search by 'path' with --output=files" +output=$(notmuch search --output=files --query=sexp '(path bad/news)' | notmuch_search_files_sanitize) +test_expect_equal "$output" "MAIL_DIR/bad/news/msg-XXX +MAIL_DIR/duplicate/bad/news/msg-XXX" + +test_begin_subtest "Search by 'path' with --output=files (trailing /)" +output=$(notmuch search --output=files --query=sexp '(path bad/news/)' | notmuch_search_files_sanitize) +test_expect_equal "$output" "MAIL_DIR/bad/news/msg-XXX +MAIL_DIR/duplicate/bad/news/msg-XXX" + +test_begin_subtest "Search by 'path' specification (multiple)" +output=$(notmuch search --query=sexp '(path bad bad/news things/bad)' | notmuch_search_sanitize) +test_expect_equal "$output" "thread:XXX 2001-01-05 [1/1] Notmuch Test Suite; To the bone (inbox unread) +thread:XXX 2001-01-05 [1/1(2)] Notmuch Test Suite; Bears (inbox unread) +thread:XXX 2001-01-05 [1/1] Notmuch Test Suite; Bites, stings, sad feelings (inbox unread)" + +test_begin_subtest "Search by 'path' specification (multiple, trailing /)" +output=$(notmuch search --query=sexp '(path bad bad/news/ things/bad)' | notmuch_search_sanitize) +test_expect_equal "$output" "thread:XXX 2001-01-05 [1/1] Notmuch Test Suite; To the bone (inbox unread) +thread:XXX 2001-01-05 [1/1(2)] Notmuch Test Suite; Bears (inbox unread) +thread:XXX 2001-01-05 [1/1] Notmuch Test Suite; Bites, stings, sad feelings (inbox unread)" + test_begin_subtest "Search by 'id'" 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) @@ -346,7 +390,7 @@ output=$(notmuch search --query=sexp '(attachment (starts-with not))' | notmuch_ test_expect_equal "$output" 'thread:XXX 2009-11-18 [2/2] Lars Kellogg-Stedman; [notmuch] "notmuch help" outputs to stderr? (attachment inbox signed unread)' test_begin_subtest "starts-with, folder" -notmuch search --output=files --query=sexp '(folder (starts-with bad))' | notmuch_dir_sanitize | sed 's/[0-9]*$/XXX/' > OUTPUT +notmuch search --output=files --query=sexp '(folder (starts-with bad))' | notmuch_search_files_sanitize > OUTPUT cat < EXPECTED MAIL_DIR/bad/msg-XXX MAIL_DIR/bad/news/msg-XXX @@ -768,6 +812,144 @@ notmuch search date:2009-11-18..2009-11-18 and tag:unread > EXPECTED notmuch search --query=sexp '(and (infix "date:2009-11-18..2009-11-18") (infix "tag:unread"))' > OUTPUT test_expect_equal_file EXPECTED OUTPUT +test_begin_subtest "date query, empty" +notmuch search from:keithp | notmuch_search_sanitize > EXPECTED +notmuch search --query=sexp '(and (date) (from keithp))'| notmuch_search_sanitize > OUTPUT +test_expect_equal_file EXPECTED OUTPUT + +test_begin_subtest "date query, one argument" +notmuch search date:2009-11-18 and from:keithp | notmuch_search_sanitize > EXPECTED +notmuch search --query=sexp '(and (date 2009-11-18) (from keithp))' | notmuch_search_sanitize > OUTPUT +test_expect_equal_file EXPECTED OUTPUT + +test_begin_subtest "date query, two arguments" +notmuch search date:2009-11-17..2009-11-18 and from:keithp | notmuch_search_sanitize > EXPECTED +notmuch search --query=sexp '(and (date 2009-11-17 2009-11-18) (from keithp))' | notmuch_search_sanitize > OUTPUT +test_expect_equal_file EXPECTED OUTPUT + +test_begin_subtest "date query, illegal nesting 1" +notmuch search --query=sexp '(to (date))' > OUTPUT 2>&1 +cat < EXPECTED +notmuch search: Syntax error in query +nested field: 'date' inside 'to' +EOF +test_expect_equal_file EXPECTED OUTPUT + +test_begin_subtest "date query, illegal nesting 2" +notmuch search --query=sexp '(to (date 2021-11-18))' > OUTPUT 2>&1 +cat < EXPECTED +notmuch search: Syntax error in query +nested field: 'date' inside 'to' +EOF +test_expect_equal_file EXPECTED OUTPUT + +test_begin_subtest "date query, illegal nesting 3" +notmuch search --query=sexp '(date (to))' > OUTPUT 2>&1 +cat < EXPECTED +notmuch search: Syntax error in query +expected atom as first argument of 'date' +EOF +test_expect_equal_file EXPECTED OUTPUT + +test_begin_subtest "date query, illegal nesting 4" +notmuch search --query=sexp '(date today (to))' > OUTPUT 2>&1 +cat < EXPECTED +notmuch search: Syntax error in query +expected atom as second argument of 'date' +EOF +test_expect_equal_file EXPECTED OUTPUT + +test_begin_subtest "date query, too many arguments" +notmuch search --query=sexp '(date yesterday and tommorow)' > OUTPUT 2>&1 +cat < EXPECTED +notmuch search: Syntax error in query +'date' expects maximum of two arguments +EOF +test_expect_equal_file EXPECTED OUTPUT + +test_begin_subtest "date query, bad date" +notmuch search --query=sexp '(date "hawaiian-pizza-day")' > OUTPUT 2>&1 +cat < EXPECTED +notmuch search: Syntax error in query +Didn't understand date specification 'hawaiian-pizza-day' +EOF +test_expect_equal_file EXPECTED OUTPUT + +test_begin_subtest "lastmod query, empty" +notmuch search from:keithp | notmuch_search_sanitize > EXPECTED +notmuch search --query=sexp '(and (lastmod) (from keithp))'| notmuch_search_sanitize > OUTPUT +test_expect_equal_file EXPECTED OUTPUT + +test_begin_subtest "lastmod query, one argument" +notmuch tag +4EFC743A.3060609@april.org id:4EFC743A.3060609@april.org +revision=$(notmuch count --lastmod '*' | cut -f3) +notmuch search lastmod:$revision..$revision | notmuch_search_sanitize > EXPECTED +notmuch search --query=sexp "(and (lastmod $revision))" | notmuch_search_sanitize > OUTPUT +test_expect_equal_file EXPECTED OUTPUT + +test_begin_subtest "lastmod query, two arguments" +notmuch tag +keithp from:keithp +revision2=$(notmuch count --lastmod '*' | cut -f3) +notmuch search lastmod:$revision..$revision2 | notmuch_search_sanitize > EXPECTED +notmuch search --query=sexp "(and (lastmod $revision $revision2))" | notmuch_search_sanitize > OUTPUT +test_expect_equal_file EXPECTED OUTPUT + +test_begin_subtest "lastmod query, illegal nesting 1" +notmuch search --query=sexp '(to (lastmod))' > OUTPUT 2>&1 +cat < EXPECTED +notmuch search: Syntax error in query +nested field: 'lastmod' inside 'to' +EOF +test_expect_equal_file EXPECTED OUTPUT + +test_begin_subtest "lastmod query, bad from revision" +notmuch search --query=sexp '(lastmod apples)' > OUTPUT 2>&1 +cat < EXPECTED +notmuch search: Syntax error in query +bad 'from' revision: 'apples' +EOF +test_expect_equal_file EXPECTED OUTPUT + +test_begin_subtest "lastmod query, bad to revision" +notmuch search --query=sexp '(lastmod 0 apples)' > OUTPUT 2>&1 +cat < EXPECTED +notmuch search: Syntax error in query +bad 'to' revision: 'apples' +EOF +test_expect_equal_file EXPECTED OUTPUT + +test_begin_subtest "lastmod query, illegal nesting 2" +notmuch search --query=sexp '(to (lastmod 2021-11-18))' > OUTPUT 2>&1 +cat < EXPECTED +notmuch search: Syntax error in query +nested field: 'lastmod' inside 'to' +EOF +test_expect_equal_file EXPECTED OUTPUT + +test_begin_subtest "lastmod query, illegal nesting 3" +notmuch search --query=sexp '(lastmod (to))' > OUTPUT 2>&1 +cat < EXPECTED +notmuch search: Syntax error in query +expected atom as first argument of 'lastmod' +EOF +test_expect_equal_file EXPECTED OUTPUT + +test_begin_subtest "lastmod query, illegal nesting 4" +notmuch search --query=sexp '(lastmod today (to))' > OUTPUT 2>&1 +cat < EXPECTED +notmuch search: Syntax error in query +expected atom as second argument of 'lastmod' +EOF +test_expect_equal_file EXPECTED OUTPUT + +test_begin_subtest "lastmod query, too many arguments" +notmuch search --query=sexp '(lastmod yesterday and tommorow)' > OUTPUT 2>&1 +cat < EXPECTED +notmuch search: Syntax error in query +'lastmod' expects maximum of two arguments +EOF +test_expect_equal_file EXPECTED OUTPUT + test_begin_subtest "user header (unknown header)" notmuch search --query=sexp '(FooBar)' >& OUTPUT cat < EXPECTED diff --git a/test/T100-search-by-folder.sh b/test/T100-search-by-folder.sh index 409cfdcc..b4f6294e 100755 --- a/test/T100-search-by-folder.sh +++ b/test/T100-search-by-folder.sh @@ -18,6 +18,12 @@ test_expect_equal "$output" "thread:XXX 2001-01-05 [1/1] Notmuch Test Suite; T thread:XXX 2001-01-05 [1/1(2)] Notmuch Test Suite; Bears (inbox unread) thread:XXX 2001-01-05 [1/1] Notmuch Test Suite; Bites, stings, sad feelings (inbox unread)" +test_begin_subtest "search by path: specification (multiple)" +output=$(notmuch search path:bad path:bad/news path:things/bad | notmuch_search_sanitize) +test_expect_equal "$output" "thread:XXX 2001-01-05 [1/1] Notmuch Test Suite; To the bone (inbox unread) +thread:XXX 2001-01-05 [1/1(2)] Notmuch Test Suite; Bears (inbox unread) +thread:XXX 2001-01-05 [1/1] Notmuch Test Suite; Bites, stings, sad feelings (inbox unread)" + test_begin_subtest "Top level folder" output=$(notmuch search folder:'""' | notmuch_search_sanitize) test_expect_equal "$output" "thread:XXX 2001-01-05 [1/1] Notmuch Test Suite; Top level (inbox unread)" @@ -28,8 +34,13 @@ test_expect_equal "$output" "thread:XXX 2001-01-05 [1/1(2)] Notmuch Test Suite test_begin_subtest "Folder search with --output=files" output=$(notmuch search --output=files folder:bad/news | notmuch_search_files_sanitize) -test_expect_equal "$output" "MAIL_DIR/bad/news/msg-003 -MAIL_DIR/duplicate/bad/news/msg-003" +test_expect_equal "$output" "MAIL_DIR/bad/news/msg-XXX +MAIL_DIR/duplicate/bad/news/msg-XXX" + +test_begin_subtest "Folder search with --output=files (trailing /)" +output=$(notmuch search --output=files folder:bad/news/ | notmuch_search_files_sanitize) +test_expect_equal "$output" "MAIL_DIR/bad/news/msg-XXX +MAIL_DIR/duplicate/bad/news/msg-XXX" test_begin_subtest "After removing duplicate instance of matching path" rm -r "${MAIL_DIR}/bad/news" @@ -39,7 +50,7 @@ test_expect_equal "$output" "" test_begin_subtest "Folder search with --output=files part #2" output=$(notmuch search --output=files folder:duplicate/bad/news | notmuch_search_files_sanitize) -test_expect_equal "$output" "MAIL_DIR/duplicate/bad/news/msg-003" +test_expect_equal "$output" "MAIL_DIR/duplicate/bad/news/msg-XXX" test_begin_subtest "After removing duplicate instance of matching path part #2" output=$(notmuch search folder:duplicate/bad/news | notmuch_search_sanitize) @@ -120,6 +131,13 @@ test_expect_equal "$output" "MAIL_DIR/bar/17:2, MAIL_DIR/bar/18:2, MAIL_DIR/cur/51:2," +test_begin_subtest "path: search (trailing /)" +output=$(notmuch search --output=files path:"bar/" | notmuch_search_files_sanitize | sort) +# cur/51:2, is a duplicate of bar/18:2, +test_expect_equal "$output" "MAIL_DIR/bar/17:2, +MAIL_DIR/bar/18:2, +MAIL_DIR/cur/51:2," + test_begin_subtest "top level path: search" output=$(notmuch search --output=files path:'""' | notmuch_search_files_sanitize | sort) test_expect_equal "$output" "MAIL_DIR/01:2, diff --git a/test/T160-json.sh b/test/T160-json.sh index 6a3e5812..ec7b1461 100755 --- a/test/T160-json.sh +++ b/test/T160-json.sh @@ -156,4 +156,46 @@ EOF output=$(notmuch show --format=json --body=false --format-version=2 id:message-id@example.com) test_expect_equal_json "$output" "$(cat EXPECTED)" +test_begin_subtest "show extra headers" +add_message "[subject]=\"extra-headers\"" "[date]=\"Sat, 01 Jan 2000 12:00:00 -0000\"" "[in-reply-to]=\"\"" "[body]=\"extra-headers test\""\ + "[header]=\"Received: from mail.example.com (mail.example.com [1.1.1.1]) + by mail.notmuchmail.org (some MTA) with ESMTP id 12345678 + for ; Sat, 10 Apr 2010 07:54:51 -0400 (EDT)\"" \ + +notmuch config set show.extra_headers "in-reply-to;received" +output=$(notmuch show --format=json --body=false id:${gen_msg_id} | notmuch_json_show_sanitize) +cat < EXPECTED +[ + [ + [ + { + "crypto": {}, + "date_relative": "2000-01-01", + "excluded": false, + "filename": [ + "YYYYY" + ], + "headers": { + "Date": "Sat, 01 Jan 2000 12:00:00 +0000", + "From": "Notmuch Test Suite ", + "In-Reply-To": "", + "Received": "from mail.example.com (mail.example.com [1.1.1.1])\tby mail.notmuchmail.org (some MTA) with ESMTP id 12345678\tfor ; Sat, 10 Apr 2010 07:54:51 -0400 (EDT)", + "Subject": "extra-headers", + "To": "Notmuch Test Suite " + }, + "id": "XXXXX", + "match": true, + "tags": [ + "inbox", + "unread" + ], + "timestamp": 946728000 + }, + [] + ] + ] +] +EOF +test_expect_equal_json "${output}" "$(cat EXPECTED)" + test_done diff --git a/test/T170-sexp.sh b/test/T170-sexp.sh index 18084273..0d32560c 100755 --- a/test/T170-sexp.sh +++ b/test/T170-sexp.sh @@ -47,4 +47,18 @@ filename=$(notmuch search --output=files "id:$id") attachment_length=$(( $(base64 $NOTMUCH_SRCDIR/test/README | wc -c) - 1 )) test_expect_equal "$output" "((((:id \"$id\" :match t :excluded nil :filename (\"$filename\") :timestamp 946728000 :date_relative \"2000-01-01\" :tags (\"inbox\") :body ((:id 1 :content-type \"multipart/mixed\" :content ((:id 2 :content-type \"text/plain\" :content \"This is a test message with inline attachment with a filename\") (:id 3 :content-type \"application/octet-stream\" :content-disposition \"inline\" :filename \"README\" :content-transfer-encoding \"base64\" :content-length $attachment_length)))) :crypto () :headers (:Subject \"sexp-show-inline-attachment-filename\" :From \"Notmuch Test Suite \" :To \"test_suite@notmuchmail.org\" :Date \"Sat, 01 Jan 2000 12:00:00 +0000\")) ())))" +test_begin_subtest "show extra headers" +add_message "[subject]=\"extra-headers\"" "[date]=\"Sat, 01 Jan 2000 12:00:00 -0000\"" "[in-reply-to]=\"\"" "[body]=\"extra-headers test\""\ + "[header]=\"Received: from mail.example.com (mail.example.com [1.1.1.1]) + by mail.notmuchmail.org (some MTA) with ESMTP id 12345678 + for ; Sat, 10 Apr 2010 07:54:51 -0400 (EDT)\"" \ + +notmuch config set show.extra_headers "in-reply-to;received" +notmuch show --format=sexp --body=false id:${gen_msg_id} | \ + notmuch_dir_sanitize | sed 's/msg-[0-9]*/MSG/g'> OUTPUT +cat < EXPECTED +((((:id "MSG@notmuch-test-suite" :match t :excluded nil :filename ("MAIL_DIR/MSG") :timestamp 946728000 :date_relative "2000-01-01" :tags ("inbox" "unread") :crypto () :headers (:Subject "extra-headers" :From "Notmuch Test Suite " :To "Notmuch Test Suite " :Date "Sat, 01 Jan 2000 12:00:00 +0000" :In-Reply-To "" :Received "from mail.example.com (mail.example.com [1.1.1.1])\011by mail.notmuchmail.org (some MTA) with ESMTP id 12345678\011for ; Sat, 10 Apr 2010 07:54:51 -0400 (EDT)")) ()))) +EOF +test_expect_equal_file EXPECTED OUTPUT + test_done diff --git a/test/T310-emacs.sh b/test/T310-emacs.sh index 1b6660f0..a05b828a 100755 --- a/test/T310-emacs.sh +++ b/test/T310-emacs.sh @@ -485,6 +485,31 @@ Sender writes: EOF test_expect_equal_file EXPECTED OUTPUT +test_begin_subtest "Reply with show.extra_headers set" +notmuch config set show.extra_headers Received +add_message '[from]="Sender "' \ + [to]=test_suite_other@notmuchmail.org + +test_emacs "(let ((message-hidden-headers '())) + (notmuch-search \"id:\\\"${gen_msg_id}\\\"\") + (notmuch-test-wait) + (notmuch-search-reply-to-thread) + (test-output))" +cat <EXPECTED +From: Notmuch Test Suite +To: Sender +Subject: Re: ${test_subtest_name} +In-Reply-To: <${gen_msg_id}> +Fcc: ${MAIL_DIR}/sent +References: <${gen_msg_id}> +--text follows this line-- +Sender writes: + +> This is just a test message (#${gen_msg_cnt}) +EOF +notmuch config set show.extra_headers +test_expect_equal_file EXPECTED OUTPUT + test_begin_subtest "Reply from address in named group list within emacs" add_message '[from]="Sender "' \ '[to]=group:test_suite@notmuchmail.org,someone@example.com\;' \ @@ -680,7 +705,7 @@ References: --text follows this line-- test_suite@notmuchmail.org writes: -> This is just a test message (#7) +> This is just a test message (#${gen_msg_cnt}) EOF test_expect_equal_file EXPECTED OUTPUT diff --git a/test/T350-crypto.sh b/test/T350-crypto.sh index 8dbf8935..3c6626b4 100755 --- a/test/T350-crypto.sh +++ b/test/T350-crypto.sh @@ -13,16 +13,29 @@ test_description='PGP/MIME signature verification and decryption' test_require_emacs add_gnupg_home -test_begin_subtest "emacs delivery of signed message" +test_begin_subtest "emacs delivery of signed message via fcc" test_expect_success \ 'emacs_fcc_message \ "test signed message 001" \ "This is a test signed message." \ "(mml-secure-message-sign)"' +test_begin_subtest "emacs delivery of signed message via fcc and smtp" +emacs_deliver_message \ + 'signed message sent via SMTP' \ + 'This is a test that messages are sent via SMTP' \ + "(add-hook 'message-send-mail-hook (lambda () (sleep-for 1))) + (mml-secure-message-sign)" +msg_file=$(notmuch search --output=files subject:signed-message-sent-via-SMTP) +test_expect_equal_message_body sent_message "$msg_file" + test_begin_subtest "signed part content-type indexing" -output=$(notmuch search mimetype:multipart/signed and mimetype:application/pgp-signature | notmuch_search_sanitize) -test_expect_equal "$output" "thread:XXX 2000-01-01 [1/1] Notmuch Test Suite; test signed message 001 (inbox signed)" +notmuch search mimetype:multipart/signed and mimetype:application/pgp-signature | notmuch_search_sanitize > OUTPUT +cat <EXPECTED +thread:XXX 2000-01-01 [1/1] Notmuch Test Suite; test signed message 001 (inbox signed) +thread:XXX 2000-01-01 [1/1] Notmuch Test Suite; signed message sent via SMTP (inbox signed) +EOF +test_expect_equal_file EXPECTED OUTPUT test_begin_subtest "signature verification" output=$(notmuch show --format=json --verify subject:"test signed message 001" \ diff --git a/test/T370-search-folder-coherence.sh b/test/T370-search-folder-coherence.sh index 0a2727e7..cf202bb3 100755 --- a/test/T370-search-folder-coherence.sh +++ b/test/T370-search-folder-coherence.sh @@ -24,8 +24,8 @@ test_expect_equal "$output" "No new mail." test_begin_subtest "Multiple files for same message" cat <EXPECTED -MAIL_DIR/msg-001 -MAIL_DIR/spam/msg-001 +MAIL_DIR/msg-XXX +MAIL_DIR/spam/msg-XXX EOF notmuch search --output=files id:$id_x | notmuch_search_files_sanitize >OUTPUT test_expect_equal_file EXPECTED OUTPUT diff --git a/test/T392-python-cffi-notmuch.sh b/test/T392-python-cffi-notmuch.sh new file mode 100755 index 00000000..15c8fc6b --- /dev/null +++ b/test/T392-python-cffi-notmuch.sh @@ -0,0 +1,48 @@ +#!/usr/bin/env bash +test_description="python bindings (notmuch test suite)" +. $(dirname "$0")/test-lib.sh || exit 1 + +if [ $NOTMUCH_HAVE_PYTHON3_CFFI -eq 0 -o $NOTMUCH_HAVE_PYTHON3_PYTEST -eq 0 ]; then + test_done +fi + +add_email_corpus + +cat < recurse.py +from notmuch2 import Database +def show_msgs(msgs, level): + print('{:s} {:s}'.format(' ' * level*4, type(msgs).__name__)) + for msg in msgs: + print('{:s} {:s}'.format(' ' * (level*4+2), type(msg).__name__)) + replies=msg.replies() + show_msgs(replies, level+1) +db = Database(config=Database.CONFIG.SEARCH) +msg=db.find("87ocn0qh6d.fsf@yoom.home.cworth.org") +threads = db.threads(query="thread:"+msg.threadid) +thread = next (threads) +show_msgs(thread, 0) +EOF + +test_begin_subtest "recursive traversal of replies (no crash)" +test_python < recurse.py +error=$? +test_expect_equal "${error}" 0 + +test_begin_subtest "recursive traversal of replies (output)" +test_python < recurse.py +tail -n 10 < OUTPUT > OUTPUT.sample +cat < EXPECTED + OwnedMessage + MessageIter + OwnedMessage + MessageIter + OwnedMessage + MessageIter + OwnedMessage + MessageIter + OwnedMessage + MessageIter +EOF +test_expect_equal_file EXPECTED OUTPUT.sample + +test_done diff --git a/test/T440-emacs-hello.sh b/test/T440-emacs-hello.sh index a1ed1c2b..842781a4 100755 --- a/test/T440-emacs-hello.sh +++ b/test/T440-emacs-hello.sh @@ -68,6 +68,16 @@ test_emacs '(notmuch-hello) notmuch tag -$tag '*' test_expect_equal_file $EXPECTED/notmuch-hello-long-names OUTPUT +test_begin_subtest "All tags show up" +tag=exclude_me +notmuch tag +$tag '*' +notmuch config set search.exclude_tags $tag +test_emacs '(notmuch-hello) + (test-output)' +notmuch tag -$tag '*' +test_expect_equal_file $EXPECTED/notmuch-hello-all-tags OUTPUT + +test_done test_begin_subtest "notmuch-hello with nonexistent CWD" test_emacs ' (notmuch-hello) diff --git a/test/T450-emacs-show.sh b/test/T450-emacs-show.sh index 4b5f5fde..057ad37e 100755 --- a/test/T450-emacs-show.sh +++ b/test/T450-emacs-show.sh @@ -220,7 +220,9 @@ test_emacs '(notmuch-show "id:basic-encrypted@crypto.notmuchmail.org") test_expect_equal_file $EXPECTED/notmuch-show-decrypted-message OUTPUT test_begin_subtest "show encrypted rfc822 message" -test_subtest_known_broken +if ${TEST_EMACS} --quick --batch --eval '(kill-emacs (if (version< emacs-version "28") 0 1))'; then + test_subtest_known_broken +fi test_emacs '(notmuch-show "id:encrypted-rfc822-attachment@crypto.notmuchmail.org") (test-visible-output)' test_expect_code 1 'fgrep "!!!" OUTPUT' diff --git a/test/T562-lib-database.sh b/test/T562-lib-database.sh index 769fe86e..2314efd2 100755 --- a/test/T562-lib-database.sh +++ b/test/T562-lib-database.sh @@ -9,10 +9,8 @@ test_begin_subtest "building database" test_expect_success "NOTMUCH_NEW" cat < c_head -#include -#include #include -#include + int main (int argc, char** argv) { notmuch_database_t *db; @@ -82,7 +80,7 @@ cat < EXPECTED == stdout == 0 == stderr == -A Xapian exception occurred at lib/database.cc:XXX: Database has been closed +A Xapian exception occurred at database.cc:XXX: Database has been closed EOF test_expect_equal_file EXPECTED OUTPUT @@ -147,7 +145,7 @@ cat < EXPECTED == stdout == 1 == stderr == -A Xapian exception occurred at lib/database.cc:XXX: Database has been closed +A Xapian exception occurred at database.cc:XXX: Database has been closed EOF test_expect_equal_file EXPECTED OUTPUT diff --git a/test/T563-lib-directory.sh b/test/T563-lib-directory.sh index 28325ff2..ebd7fcb2 100755 --- a/test/T563-lib-directory.sh +++ b/test/T563-lib-directory.sh @@ -9,10 +9,8 @@ test_begin_subtest "building database" test_expect_success "NOTMUCH_NEW" cat < c_head -#include -#include #include -#include + int main (int argc, char** argv) { notmuch_database_t *db; @@ -53,7 +51,7 @@ cat < EXPECTED == stdout == 1 == stderr == -A Xapian exception occurred at lib/directory.cc:XXX: Database has been closed +A Xapian exception occurred at directory.cc:XXX: Database has been closed EOF test_expect_equal_file EXPECTED OUTPUT @@ -70,7 +68,7 @@ cat < EXPECTED == stdout == 1 == stderr == -A Xapian exception occurred at lib/directory.cc:XXX: Database has been closed +A Xapian exception occurred at directory.cc:XXX: Database has been closed EOF test_expect_equal_file EXPECTED OUTPUT diff --git a/test/T564-lib-query.sh b/test/T564-lib-query.sh index 50b0a88e..ff1d4984 100755 --- a/test/T564-lib-query.sh +++ b/test/T564-lib-query.sh @@ -9,10 +9,8 @@ test_begin_subtest "building database" test_expect_success "NOTMUCH_NEW" cat < c_head -#include -#include #include -#include + int main (int argc, char** argv) { notmuch_database_t *db; diff --git a/test/T566-lib-message.sh b/test/T566-lib-message.sh index ee55ef29..8b61d182 100755 --- a/test/T566-lib-message.sh +++ b/test/T566-lib-message.sh @@ -19,9 +19,8 @@ cat <<'EOF' > c_tail EOF cat < c_head0 -#include -#include #include + int main (int argc, char** argv) { notmuch_database_t *db; diff --git a/test/T568-lib-thread.sh b/test/T568-lib-thread.sh index 088e66dd..b45836cd 100755 --- a/test/T568-lib-thread.sh +++ b/test/T568-lib-thread.sh @@ -24,9 +24,8 @@ cat <<'EOF' > c_tail EOF cat < c_head -#include -#include #include + int main (int argc, char** argv) { notmuch_database_t *db; diff --git a/test/T590-libconfig.sh b/test/T590-libconfig.sh index 9fa51fc0..26a1f033 100755 --- a/test/T590-libconfig.sh +++ b/test/T590-libconfig.sh @@ -23,8 +23,6 @@ EOF } cat < c_head -#include -#include #include int main (int argc, char** argv) @@ -272,6 +270,29 @@ EOF test_expect_equal_file EXPECTED OUTPUT restore_database +test_begin_subtest "notmuch_config_get_values (ignore leading/trailing whitespace)" +cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR} ${NOTMUCH_CONFIG} %NULL% +{ + notmuch_config_values_t *values; + EXPECT0(notmuch_config_set (db, NOTMUCH_CONFIG_NEW_TAGS, " a ; b c ; d ")); + for (values = notmuch_config_get_values (db, NOTMUCH_CONFIG_NEW_TAGS); + notmuch_config_values_valid (values); + notmuch_config_values_move_to_next (values)) + { + puts (notmuch_config_values_get (values)); + } +} +EOF +cat <<'EOF' >EXPECTED +== stdout == +a +b c +d +== stderr == +EOF +test_expect_equal_file EXPECTED OUTPUT +restore_database + test_begin_subtest "notmuch_config_get_values_string" cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR} ${NOTMUCH_CONFIG} %NULL% { @@ -418,6 +439,7 @@ cat <<'EOF' >EXPECTED 09: 'NULL' 10: 'USER_FULL_NAME' 11: '8000' +12: 'NULL' == stderr == EOF unset MAILDIR @@ -616,8 +638,6 @@ cp notmuch-config.bak notmuch-config test_expect_equal_file EXPECTED OUTPUT cat < c_head2 -#include -#include #include int main (int argc, char** argv) @@ -730,6 +750,7 @@ cat <<'EOF' >EXPECTED 09: 'test_suite_other@notmuchmail.org;test_suite@otherdomain.org' 10: 'Notmuch Test Suite' 11: '8000' +12: 'NULL' == stderr == EOF test_expect_equal_file EXPECTED OUTPUT @@ -763,6 +784,7 @@ cat <<'EOF' >EXPECTED 09: 'NULL' 10: 'USER_FULL_NAME' 11: '8000' +12: 'NULL' == stderr == EOF test_expect_equal_file EXPECTED OUTPUT.clean @@ -839,6 +861,7 @@ maildir.synchronize_flags true new.ignore sekrit_junk new.tags unread;inbox search.exclude_tags foo;bar;fub +show.extra_headers (null) test.key1 testvalue1 test.key2 testvalue2 user.name Notmuch Test Suite @@ -953,6 +976,7 @@ EOF test_expect_equal_file EXPECTED OUTPUT test_begin_subtest "open: database parameter overrides implicit config" +cp $NOTMUCH_CONFIG ${NOTMUCH_CONFIG}.bak notmuch config set database.path ${MAIL_DIR}/nonexistent cat c_head3 - c_tail3 <<'EOF' | test_C ${MAIL_DIR} const char *path = NULL; @@ -963,6 +987,7 @@ cat c_head3 - c_tail3 <<'EOF' | test_C ${MAIL_DIR} path = notmuch_database_get_path (db); printf ("path: %s\n", path ? path : "(null)"); EOF +cp ${NOTMUCH_CONFIG}.bak ${NOTMUCH_CONFIG} cat < EXPECTED == stdout == status: 0 @@ -973,4 +998,43 @@ EOF notmuch_dir_sanitize < OUTPUT > OUTPUT.clean test_expect_equal_file EXPECTED OUTPUT.clean +cat < c_body + notmuch_status_t st = notmuch_database_open_with_config(NULL, + NOTMUCH_DATABASE_MODE_READ_ONLY, + "", NULL, &db, NULL); + printf ("status == SUCCESS: %d\n", st == NOTMUCH_STATUS_SUCCESS); + if (db) { + const char *mail_root = NULL; + mail_root = notmuch_config_get (db, NOTMUCH_CONFIG_MAIL_ROOT); + printf ("mail_root: %s\n", mail_root ? mail_root : "(null)"); + } +EOF + +cat < EXPECTED.common +== stdout == +status == SUCCESS: 0 +db == NULL: 1 +== stderr == +EOF + +test_begin_subtest "open/error: config=empty with no mail root in db " +old_NOTMUCH_CONFIG=${NOTMUCH_CONFIG} +unset NOTMUCH_CONFIG +cat c_head3 c_body c_tail3 | test_C +export NOTMUCH_CONFIG=${old_NOTMUCH_CONFIG} +notmuch_dir_sanitize < OUTPUT > OUTPUT.clean +test_expect_equal_file EXPECTED.common OUTPUT.clean + +test_begin_subtest "open/error: config=empty with no mail root in db (xdg)" +old_NOTMUCH_CONFIG=${NOTMUCH_CONFIG} +unset NOTMUCH_CONFIG +backup_database +mkdir -p home/.local/share/notmuch +mv mail/.notmuch home/.local/share/notmuch/default +cat c_head3 c_body c_tail3 | test_C +restore_database +export NOTMUCH_CONFIG=${old_NOTMUCH_CONFIG} +notmuch_dir_sanitize < OUTPUT > OUTPUT.clean +test_expect_equal_file EXPECTED.common OUTPUT.clean + test_done diff --git a/test/T595-reopen.sh b/test/T595-reopen.sh index 7375e2ac..1a517423 100755 --- a/test/T595-reopen.sh +++ b/test/T595-reopen.sh @@ -6,8 +6,6 @@ test_description="library reopen API" add_email_corpus cat < c_head -#include -#include #include int main (int argc, char** argv) diff --git a/test/T610-message-property.sh b/test/T610-message-property.sh index d0e52f4a..4ec85474 100755 --- a/test/T610-message-property.sh +++ b/test/T610-message-property.sh @@ -6,10 +6,6 @@ test_description="message property API" add_email_corpus cat < c_head -#include -#include -#include -#include #include void print_properties (notmuch_message_t *message, const char *prefix, notmuch_bool_t exact) { diff --git a/test/T620-lock.sh b/test/T620-lock.sh index 7aaaff2a..8f4c380f 100755 --- a/test/T620-lock.sh +++ b/test/T620-lock.sh @@ -9,9 +9,6 @@ if [ $NOTMUCH_HAVE_XAPIAN_DB_RETRY_LOCK -ne 1 ]; then test_subtest_known_broken fi test_C ${MAIL_DIR} <<'EOF' -#include -#include -#include #include void diff --git a/test/T640-database-modified.sh b/test/T640-database-modified.sh index 274105c7..636b20c7 100755 --- a/test/T640-database-modified.sh +++ b/test/T640-database-modified.sh @@ -10,11 +10,8 @@ test_begin_subtest "catching DatabaseModifiedError in _notmuch_message_ensure_me first_id=$(notmuch search --output=messages '*'| head -1 | sed s/^id://) test_C ${MAIL_DIR} < -#include #include -#include -#include + int main (int argc, char **argv) { diff --git a/test/T750-gzip.sh b/test/T750-gzip.sh index 4408d085..5648896f 100755 --- a/test/T750-gzip.sh +++ b/test/T750-gzip.sh @@ -58,13 +58,13 @@ test_expect_equal_file EXPECTED OUTPUT test_begin_subtest "notmuch search --output=files with partially gzipped mail store" notmuch search --output=files '*' | notmuch_search_files_sanitize > OUTPUT cat < EXPECTED -MAIL_DIR/msg-001.gz -MAIL_DIR/msg-002.gz -MAIL_DIR/msg-003.gz -MAIL_DIR/msg-004 -MAIL_DIR/msg-005.gz -MAIL_DIR/msg-006 -MAIL_DIR/msg-007.gz +MAIL_DIR/msg-XXX.gz +MAIL_DIR/msg-XXX.gz +MAIL_DIR/msg-XXX.gz +MAIL_DIR/msg-XXX +MAIL_DIR/msg-XXX.gz +MAIL_DIR/msg-XXX +MAIL_DIR/msg-XXX.gz EOF test_expect_equal_file EXPECTED OUTPUT diff --git a/test/T800-asan.sh b/test/T800-asan.sh new file mode 100755 index 00000000..8607732e --- /dev/null +++ b/test/T800-asan.sh @@ -0,0 +1,39 @@ +#!/usr/bin/env bash +test_description='run code with ASAN enabled against the library' +. $(dirname "$0")/test-lib.sh || exit 1 + +if [ $NOTMUCH_HAVE_ASAN -ne 1 ]; then + printf "Skipping due to missing ASAN support\n" + test_done +fi + +add_email_corpus + +TEST_CFLAGS="-fsanitize=address" + +test_begin_subtest "open and destroy" +test_C ${MAIL_DIR} ${NOTMUCH_CONFIG} < +#include + +int main(int argc, char **argv) { + notmuch_database_t *db = NULL; + + notmuch_status_t st = notmuch_database_open_with_config(argv[1], + NOTMUCH_DATABASE_MODE_READ_ONLY, + argv[2], NULL, &db, NULL); + + printf("db != NULL: %d\n", db != NULL); + if (db != NULL) + notmuch_database_destroy(db); + return 0; +} +EOF +cat < EXPECTED +== stdout == +db != NULL: 1 +== stderr == +EOF +test_expect_equal_file EXPECTED OUTPUT + +test_done diff --git a/test/emacs.expected-output/notmuch-hello-all-tags b/test/emacs.expected-output/notmuch-hello-all-tags new file mode 100644 index 00000000..65e479fa --- /dev/null +++ b/test/emacs.expected-output/notmuch-hello-all-tags @@ -0,0 +1,11 @@ + Welcome to notmuch. You have 52 messages. + +Search: . + +All tags: [hide] + + 4 attachment 52 inbox 52 unread + 52 exclude_me 7 signed + + Hit `?' for context-sensitive help in any Notmuch screen. + Customize Notmuch or this page. diff --git a/test/notmuch-test.h b/test/notmuch-test.h index 34dbb8e0..ed713099 100644 --- a/test/notmuch-test.h +++ b/test/notmuch-test.h @@ -1,7 +1,21 @@ #ifndef _NOTMUCH_TEST_H #define _NOTMUCH_TEST_H +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif + +#include +#include +#include #include #include +#include +#include +#include +#include +#include +#include + #include inline static void @@ -14,4 +28,23 @@ expect0 (int line, notmuch_status_t ret) } #define EXPECT0(v) expect0 (__LINE__, v); + +inline static void * +dlsym_next (const char *symbol) +{ + void *sym = dlsym (RTLD_NEXT, symbol); + char *str = dlerror (); + + if (str != NULL) { + fprintf (stderr, "finding symbol '%s' failed: %s", symbol, str); + exit (77); + } + return sym; +} + +#define WRAP_DLFUNC(_rtype, _func, _args) \ + _rtype _func _args; \ + _rtype _func _args { \ + static _rtype (*_func##_orig) _args = NULL; \ + if (! _func##_orig ) *(void **) (&_func##_orig) = dlsym_next (#_func); #endif diff --git a/test/setup.expected-output/config-with-comments b/test/setup.expected-output/config-with-comments index 56c628e5..d8397714 100644 --- a/test/setup.expected-output/config-with-comments +++ b/test/setup.expected-output/config-with-comments @@ -31,7 +31,7 @@ path=/path/to/maildir [user] name=Test Suite primary_email=test.suite@example.com -other_email=another.suite@example.com; +other_email=another.suite@example.com # Configuration for "notmuch new" # @@ -60,7 +60,7 @@ tags=foo;bar; # query will override that exclusion. # [search] -exclude_tags=baz; +exclude_tags=baz # Maildir compatibility configuration # diff --git a/test/symbol-test.cc b/test/symbol-test.cc index 9d73a571..9e956ddf 100644 --- a/test/symbol-test.cc +++ b/test/symbol-test.cc @@ -12,8 +12,10 @@ main (int argc, char **argv) if (argc != 3) return 1; - if (notmuch_database_open_verbose (argv[1], NOTMUCH_DATABASE_MODE_READ_ONLY, - ¬much, &message)) { + if (notmuch_database_open_with_config (argv[1], NOTMUCH_DATABASE_MODE_READ_ONLY, + "", + NULL, + ¬much, &message)) { if (message) { fputs (message, stderr); free (message); diff --git a/test/test-lib-emacs.sh b/test/test-lib-emacs.sh index dde32177..a298526d 100644 --- a/test/test-lib-emacs.sh +++ b/test/test-lib-emacs.sh @@ -54,8 +54,9 @@ emacs_deliver_message () { (message-goto-body) (insert \"${body}\") $* - (notmuch-mua-send-and-exit))" - + (let ((mml-secure-smime-sign-with-sender t) + (mml-secure-openpgp-sign-with-sender t)) + (notmuch-mua-send-and-exit)))" # In case message was sent properly, client waits for confirmation # before exiting and resuming control here; therefore making sure # that server exits by sending (KILL) signal to it is safe. diff --git a/test/test-lib.sh b/test/test-lib.sh index e476a69b..833bf5fe 100644 --- a/test/test-lib.sh +++ b/test/test-lib.sh @@ -145,7 +145,7 @@ add_gpgsm_home () { mkdir -p -m 0700 "$GNUPGHOME" 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') + fpr=$(gpgsm --batch --with-colons --list-key test_suite@notmuchmail.org | awk -F: '/^fpr/ {print $10}') echo "$fpr S relax" >> "$GNUPGHOME/trustlist.txt" gpgsm --quiet --batch --no-tty --no-common-certs-import --disable-dirmngr --import < $NOTMUCH_SRCDIR/test/smime/ca.crt echo "4D:E0:FF:63:C0:E9:EC:01:29:11:C8:7A:EE:DA:3A:9A:7F:6E:C1:0D S" >> "$GNUPGHOME/trustlist.txt" @@ -432,6 +432,20 @@ test_expect_equal_file () { test_diff_file_ "$1" "$2" } +# Like test_expect_equal_file, but compare the part of the two files after the first blank line +test_expect_equal_message_body () { + exec 1>&6 2>&7 # Restore stdout and stderr + if [ -z "$inside_subtest" ]; then + error "bug in the test script: test_expect_equal_file without test_begin_subtest" + fi + test "$#" = 2 || + error "bug in the test script: not 2 parameters to test_expect_equal_file" + + expected=$(sed '1,/^$/d' "$1") + output=$(sed '1,/^$/d' "$2") + test_expect_equal "$expected" "$output" +} + # Like test_expect_equal, but takes two filenames. Fails if either is empty test_expect_equal_file_nonempty () { exec 1>&6 2>&7 # Restore stdout and stderr @@ -529,7 +543,7 @@ notmuch_debug_sanitize () { } notmuch_exception_sanitize () { - perl -pe 's/(A Xapian exception occurred at .*[.]cc?):([0-9]*)/\1:XXX/' + perl -pe 's,(A Xapian exception occurred at) .*?([^/]*[.]cc?):([0-9]*),\1 \2:XXX,' } notmuch_search_sanitize () { @@ -537,7 +551,7 @@ notmuch_search_sanitize () { } notmuch_search_files_sanitize () { - notmuch_dir_sanitize + notmuch_dir_sanitize | sed 's/msg-[0-9][0-9][0-9]/msg-XXX/' } notmuch_dir_sanitize () { diff --git a/util/hex-escape.h b/util/hex-escape.h index 8703334c..83a4c6f1 100644 --- a/util/hex-escape.h +++ b/util/hex-escape.h @@ -5,7 +5,7 @@ extern "C" { #endif -typedef enum hex_status { +typedef enum { HEX_SUCCESS = 0, HEX_SYNTAX_ERROR, HEX_OUT_OF_MEMORY diff --git a/util/string-util.c b/util/string-util.c index 9c46a81a..03d7648d 100644 --- a/util/string-util.c +++ b/util/string-util.c @@ -42,13 +42,15 @@ const char * strsplit_len (const char *s, char delim, size_t *len) { bool escaping = false; - size_t count = 0; + size_t count = 0, last_nonspace = 0; - /* Skip initial unescaped delimiters */ - while (*s && *s == delim) + /* Skip initial unescaped delimiters and whitespace */ + while (*s && (*s == delim || isspace (*s))) s++; while (s[count] && (escaping || s[count] != delim)) { + if (! isspace (s[count])) + last_nonspace = count; escaping = (s[count] == '\\'); count++; } @@ -56,7 +58,7 @@ strsplit_len (const char *s, char delim, size_t *len) if (count == 0) return NULL; - *len = count; + *len = last_nonspace + 1; return s; } diff --git a/version.txt b/version.txt index 3f8003cd..c74e8a04 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -0.34.2 +0.35