aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDavid Bremner <david@tethera.net>2015-12-13 08:39:11 -0400
committerDavid Bremner <david@tethera.net>2015-12-13 08:39:11 -0400
commite3cd357fdddd2d8d29e8eea0b40a1043c46f1791 (patch)
treeb0e4f0549fc1216439e4593b73206c7923995ad4
parentad5b7434978ed98b1eb832b77684869f5d02594a (diff)
parent1432a4f946e0f236179b53ac71d03764da725f33 (diff)
Merge tag 'debian/0.21-3' into jessie-backports
uploaded to unstable
-rw-r--r--INSTALL6
-rw-r--r--Makefile.local5
-rw-r--r--NEWS147
-rw-r--r--bindings/Makefile7
-rw-r--r--bindings/Makefile.local21
-rw-r--r--bindings/python/notmuch/query.py22
-rw-r--r--bindings/python/notmuch/version.py2
-rw-r--r--bindings/ruby/README7
-rw-r--r--bindings/ruby/extconf.rb16
-rw-r--r--bindings/ruby/query.c38
-rw-r--r--completion/notmuch-completion.bash42
-rwxr-xr-xconfigure244
-rw-r--r--crypto.c25
-rw-r--r--debian/NEWS10
-rw-r--r--debian/changelog64
-rw-r--r--debian/control4
-rw-r--r--debian/libnotmuch4.symbols5
-rwxr-xr-xdebian/notmuch-emacs.emacsen-install4
-rwxr-xr-xdebian/rules1
-rw-r--r--devel/STYLE10
-rwxr-xr-xdevel/gen-testdb.sh2
-rwxr-xr-xdevel/nmbug/nmbug-status13
-rwxr-xr-xdevel/release-checks.sh45
-rw-r--r--doc/Makefile.local2
-rw-r--r--doc/conf.py2
-rw-r--r--doc/doxygen.cfg2
-rw-r--r--doc/man1/notmuch-address.rst26
-rw-r--r--doc/man1/notmuch-count.rst5
-rw-r--r--doc/man1/notmuch-emacs-mua.rst22
-rw-r--r--doc/man1/notmuch.rst23
-rw-r--r--doc/man7/notmuch-search-terms.rst20
-rw-r--r--emacs/notmuch-hello.el27
-rw-r--r--emacs/notmuch-jump.el4
-rw-r--r--emacs/notmuch-lib.el8
-rw-r--r--emacs/notmuch-maildir-fcc.el23
-rw-r--r--emacs/notmuch-mua.el23
-rw-r--r--emacs/notmuch-show.el32
-rw-r--r--emacs/notmuch-tag.el4
-rw-r--r--emacs/notmuch-tree.el13
-rw-r--r--emacs/notmuch.el46
-rw-r--r--lib/Makefile.local23
-rw-r--r--lib/database-private.h18
-rw-r--r--lib/database.cc107
-rw-r--r--lib/directory.cc25
-rw-r--r--lib/gen-version-script.sh1
-rw-r--r--lib/message-file.c21
-rw-r--r--lib/message.cc34
-rw-r--r--lib/notmuch-private.h11
-rw-r--r--lib/notmuch.h144
-rw-r--r--lib/parse-time-vrp.cc5
-rw-r--r--lib/query.cc53
-rw-r--r--lib/thread.cc7
-rw-r--r--mime-node.c83
-rw-r--r--notmuch-client.h36
-rw-r--r--notmuch-compact.c9
-rw-r--r--notmuch-config.c13
-rw-r--r--notmuch-count.c70
-rw-r--r--notmuch-dump.c12
-rwxr-xr-xnotmuch-emacs-mua31
-rw-r--r--notmuch-insert.c7
-rw-r--r--notmuch-new.c42
-rw-r--r--notmuch-reply.c32
-rw-r--r--notmuch-restore.c4
-rw-r--r--notmuch-search.c212
-rw-r--r--notmuch-setup.c7
-rw-r--r--notmuch-show.c109
-rw-r--r--notmuch-tag.c15
-rw-r--r--notmuch.c134
-rwxr-xr-xperformance-test/M00-new.sh2
-rwxr-xr-xperformance-test/M01-dump-restore.sh2
-rwxr-xr-xperformance-test/T00-new.sh2
-rwxr-xr-xperformance-test/T01-dump-restore.sh2
-rwxr-xr-xperformance-test/T02-tag.sh2
-rw-r--r--performance-test/perf-test-lib.sh6
-rw-r--r--status.c21
-rw-r--r--test/Makefile.local10
-rw-r--r--test/README9
-rwxr-xr-xtest/T000-basic.sh2
-rwxr-xr-xtest/T010-help-test.sh6
-rwxr-xr-xtest/T020-compact.sh2
-rwxr-xr-xtest/T030-config.sh2
-rwxr-xr-xtest/T040-setup.sh2
-rwxr-xr-xtest/T050-new.sh72
-rwxr-xr-xtest/T060-count.sh32
-rwxr-xr-xtest/T070-insert.sh4
-rwxr-xr-xtest/T080-search.sh2
-rwxr-xr-xtest/T090-search-output.sh2
-rwxr-xr-xtest/T095-address.sh146
-rwxr-xr-xtest/T100-search-by-folder.sh2
-rwxr-xr-xtest/T110-search-position-overlap-bug.sh2
-rwxr-xr-xtest/T120-search-insufficient-from-quoting.sh2
-rwxr-xr-xtest/T130-search-limiting.sh2
-rwxr-xr-xtest/T140-excludes.sh2
-rwxr-xr-xtest/T150-tagging.sh2
-rwxr-xr-xtest/T160-json.sh2
-rwxr-xr-xtest/T170-sexp.sh2
-rwxr-xr-xtest/T180-text.sh2
-rwxr-xr-xtest/T190-multipart.sh2
-rwxr-xr-xtest/T200-thread-naming.sh2
-rwxr-xr-xtest/T205-author-naming.sh2
-rwxr-xr-xtest/T210-raw.sh2
-rwxr-xr-xtest/T220-reply.sh2
-rwxr-xr-xtest/T230-reply-to-sender.sh2
-rwxr-xr-xtest/T240-dump-restore.sh2
-rwxr-xr-xtest/T250-uuencode.sh2
-rwxr-xr-xtest/T260-thread-order.sh2
-rwxr-xr-xtest/T270-author-order.sh2
-rwxr-xr-xtest/T280-from-guessing.sh2
-rwxr-xr-xtest/T290-long-id.sh2
-rwxr-xr-xtest/T300-encoding.sh2
-rwxr-xr-xtest/T310-emacs.sh2
-rwxr-xr-xtest/T320-emacs-large-search-buffer.sh2
-rwxr-xr-xtest/T330-emacs-subject-to-filename.sh2
-rwxr-xr-xtest/T340-maildir-sync.sh2
-rwxr-xr-xtest/T350-crypto.sh2
-rwxr-xr-xtest/T360-symbol-hiding.sh21
-rwxr-xr-xtest/T370-search-folder-coherence.sh2
-rwxr-xr-xtest/T380-atomicity.sh2
-rwxr-xr-xtest/T390-python.sh10
-rwxr-xr-xtest/T395-ruby.sh86
-rwxr-xr-xtest/T400-hooks.sh2
-rwxr-xr-xtest/T410-argument-parsing.sh2
-rwxr-xr-xtest/T420-emacs-test-functions.sh2
-rwxr-xr-xtest/T430-emacs-address-cleaning.sh2
-rwxr-xr-xtest/T440-emacs-hello.sh2
-rwxr-xr-xtest/T450-emacs-show.sh2
-rwxr-xr-xtest/T455-emacs-charsets.sh2
-rwxr-xr-xtest/T460-emacs-tree.sh2
-rwxr-xr-xtest/T470-missing-headers.sh2
-rwxr-xr-xtest/T480-hex-escaping.sh2
-rwxr-xr-xtest/T490-parse-time-string.sh2
-rwxr-xr-xtest/T500-search-date.sh6
-rwxr-xr-xtest/T510-thread-replies.sh2
-rwxr-xr-xtest/T520-show.sh2
-rwxr-xr-xtest/T530-upgrade.sh2
-rwxr-xr-xtest/T550-db-features.sh2
-rwxr-xr-xtest/T560-lib-error.sh60
-rwxr-xr-xtest/T570-revision-tracking.sh93
-rwxr-xr-xtest/aggregate-results.sh5
-rw-r--r--test/random-corpus.c19
-rw-r--r--test/symbol-test.cc35
-rw-r--r--test/test-lib-common.sh14
-rw-r--r--test/test-lib.sh36
-rwxr-xr-xtest/test-verbose2
-rw-r--r--util/string-util.c34
-rw-r--r--util/string-util.h11
-rw-r--r--version2
147 files changed, 2319 insertions, 818 deletions
diff --git a/INSTALL b/INSTALL
index eaccd93d..b1b9cd55 100644
--- a/INSTALL
+++ b/INSTALL
@@ -20,7 +20,7 @@ configure stage.
Dependencies
------------
-Notmuch depends on four libraries: Xapian, GMime 2.4 or 2.6,
+Notmuch depends on four libraries: Xapian, GMime 2.6,
Talloc, and zlib which are each described below:
Xapian
@@ -39,8 +39,8 @@ Talloc, and zlib which are each described below:
reading mail while notmuch would wait for Xapian when removing
the "inbox" and "unread" tags from messages in a thread.
- GMime 2.4 or 2.6
- ----------------
+ GMime 2.6
+ ----------
GMime provides decoding of MIME email messages for Notmuch.
Without GMime, Notmuch would not be able to extract and index
diff --git a/Makefile.local b/Makefile.local
index 6d547423..066ecf23 100644
--- a/Makefile.local
+++ b/Makefile.local
@@ -59,7 +59,7 @@ endif
FINAL_LIBNOTMUCH_LDFLAGS = $(LDFLAGS) $(AS_NEEDED_LDFLAGS) $(CONFIGURE_LDFLAGS)
.PHONY: all
-all: notmuch notmuch-shared build-man
+all: notmuch notmuch-shared build-man ruby-bindings
ifeq ($(MAKECMDGOALS),)
ifeq ($(shell cat .first-build-message 2>/dev/null),)
@NOTMUCH_FIRST_BUILD=1 $(MAKE) --no-print-directory all
@@ -224,7 +224,7 @@ release-checks:
.PHONY: verify-newer
verify-newer:
@echo -n "Checking that no $(VERSION) release already exists..."
- @wget -q -O /dev/null $(RELEASE_URL)/$(TAR_FILE) ; \
+ @wget -q --no-check-certificate -O /dev/null $(RELEASE_URL)/$(TAR_FILE) ; \
case $$? in \
8) echo "Good." ;; \
0) echo "Ouch."; \
@@ -271,6 +271,7 @@ dataclean: distclean
notmuch_client_srcs = \
command-line-arguments.c\
debugger.c \
+ status.c \
gmime-filter-reply.c \
hooks.c \
notmuch.c \
diff --git a/NEWS b/NEWS
index cc8d7edf..6681699d 100644
--- a/NEWS
+++ b/NEWS
@@ -1,3 +1,146 @@
+Notmuch 0.21 (2015-10-29)
+=========================
+
+General
+-------
+
+Notmuch now requires gmime >= 2.6.7. The gmime 2.4 series is no longer
+supported.
+
+Database revision tracking: `lastmod:` queries
+
+ Each message now has a metadata revision number that increases with
+ every tagging operation. See the discussion of `lastmod:` in
+ `notmuch-search-terms(7)` for more information.
+
+Date queries now support `date:<expr>..!` shorthand for
+`date:<expr>..<expr>`
+
+ You can use, for example, `date:yesterday..!` to match from the
+ beginning of yesterday to the end of yesterday. For further details,
+ please refer to the `notmuch-search-terms` manual page.
+
+Notmuch database upgrade to support `lastmod:` queries
+
+ The above mentioned `lastmod:` prefix. This will be done
+ automatically, without prompting on the next time `notmuch new` is
+ run after the upgrade. The upgrade is not reversible, and the
+ upgraded database will not be readable by older versions of
+ Notmuch. As a safeguard, a database dump will be created in the
+ `.notmuch` directory before upgrading.
+
+Build System
+------------
+
+The ruby bindings are now built as part of the main notmuch build
+process. This can be disabled with the `--without-ruby` option to
+configure.
+
+Building the documentation can be disabled with the `--without-docs`
+option to configure.
+
+Skipped individual tests are no longer considered as failures.
+
+Command Line Interface
+----------------------
+
+Database revision tracking
+
+ Two new options were added to support revision tracking. A global
+ option "--uuid" (`notmuch(1)`) was added for to detect counter
+ rollover and reinitialization, and `notmuch-count(1)` gained a
+ `--lastmod` option to query database revision tracking data.
+
+The `notmuch address` command supports new deduplication schemes
+
+ `notmuch address` has gained a new `--deduplicate` option to specify
+ how the results should be deduplicated, if at all. The alternatives
+ are `no` (do not deduplicate, useful for processing the results with
+ external tools), `mailbox` (deduplicate based on the full, case
+ sensitive name and email address), and `address` (deduplicate based
+ on the case insensitive address part). See the `notmuch-address`
+ manual page for further information.
+
+Emacs Interface
+---------------
+
+`notmuch-emacs-version` is used in `User-Agent` header
+
+ The value of recently introduced variable `notmuch-emacs-version` is
+ now used as a part of `User-Agent` header when sending emails.
+
+Removed `notmuch-version` function by renaming it to `notmuch-cli-version`
+
+ With existing variable `notmuch-emacs-version` the accompanied
+ function which retrieves the version of `notmuch-command` is
+ better named as `notmuch-cli-version`.
+
+Query input now supports completion for "is:<tag>"
+
+New message composition mode: `notmuch-compose-mode`
+
+ This is mainly to fix fcc handling, but may be useful for user
+ customization as well.
+
+Allow filtering of search results in `notmuch-show`
+
+Add function to rerun current tree-view search in search mode
+
+Bug fix for replying to encrypted messages in `notmuch-tree` mode
+
+Allow saved searched to specify tree view rather than search view
+
+ Applies to saved searches run from `notmuch-hello`, or by a keyboard
+ shortcut (`notmuch-jump`). Can be set in the customize interface, or
+ by adding :search-type tree to the appropriate saved search plist in
+ `notmuch-saved-searches`.
+
+Increase maximum size of rendered text parts
+
+ The variable `notmuch-show-max-text-part-size` controls the maximum
+ size (in bytes) which is automatically rendered. This may make
+ rendering large threads slower. To get the previous behaviour set
+ this variable to 10000.
+
+Library
+-------
+
+The use of absolute paths is now enforced when calling
+`notmuch_database_{open, create}`
+
+New function `notmuch_directory_delete` to delete directory documents
+
+ Previously there was no way to delete directory documents from the
+ database, leading to confusing results when the "ghost" directory
+ document of a renamed or deleted filesystem directory was
+ encountered every time the parent directory was being scanned by
+ `notmuch new`. The mtime of the old directory document was also used
+ if a directory by the same name was added again in the filesystem,
+ potentially bypassing the scan for the directory. The issues are
+ fixed by providing a library call to delete directory documents, and
+ deleting the old documents in `notmuch new` on filesystem directory
+ removal or rename.
+
+Database revision tracking
+
+ Revision tracking is supported via a new prefix "lastmod:" in the
+ query parser and the new function
+ `notmuch_database_get_revision`. For the latter, see `notmuch(3)`.
+
+New status code returning API for n_query_count_{messages,threads}
+
+Deprecated functions
+
+ `notmuch_query_search_threads`, `notmuch_query_search_messages`,
+ `notmuch_query_count_messages`, and `notmuch_query_count_threads`
+ are all deprecated as of this release. Clients are encouraged to
+ transition to the `_st` variants supporting better error reporting.
+
+nmbug-status
+------------
+
+`nmbug-status` now supports specifying the sort order for each view.
+
Notmuch 0.20.2 (2015-06-27)
===========================
@@ -12,7 +155,7 @@ Notmuch 0.20.1 (2015-06-01)
Test Suite
----------
-Work around apparent gdb bug on arm64
+Work around apparent gdb bug on arm64.
Notmuch 0.20 (2015-05-31)
=========================
@@ -2767,7 +2910,7 @@ New 'G' key binding to trigger mail refresh (G == "Get new mail")
The 'G' key works wherever '=' works. Before refreshing the screen
it calls an external program that can be used to poll email servers,
- run notmuch new and setup specific tags for the new emails. The
+ run notmuch new and set up specific tags for the new emails. The
script to be called should be configured with the "Notmuch Poll
Script" setting in the customize interface. This script will
typically invoke "notmuch new" and then perhaps several "notmuch
diff --git a/bindings/Makefile b/bindings/Makefile
new file mode 100644
index 00000000..de492a7c
--- /dev/null
+++ b/bindings/Makefile
@@ -0,0 +1,7 @@
+# See Makefile.local for the list of files to be compiled in this
+# directory.
+all:
+ $(MAKE) -C .. all
+
+.DEFAULT:
+ $(MAKE) -C .. $@
diff --git a/bindings/Makefile.local b/bindings/Makefile.local
new file mode 100644
index 00000000..4ecf839d
--- /dev/null
+++ b/bindings/Makefile.local
@@ -0,0 +1,21 @@
+# -*- makefile -*-
+
+dir := bindings
+
+# force the shared library to be built
+ruby-bindings: lib/$(LINKER_NAME)
+ifeq ($(HAVE_RUBY_DEV),1)
+ cd $(dir)/ruby && \
+ EXTRA_LDFLAGS="$(NO_UNDEFINED_LDFLAGS)" \
+ LIBNOTMUCH="../../lib/$(LINKER_NAME)" \
+ ruby extconf.rb --vendor
+ $(MAKE) -C $(dir)/ruby
+else
+ @echo Missing dependency, skipping ruby bindings
+endif
+
+CLEAN += $(patsubst %,$(dir)/ruby/%, \
+ .RUBYARCHDIR.time \
+ Makefile database.o directory.o filenames.o\
+ init.o message.o messages.o mkmf.log notmuch.so query.o \
+ status.o tags.o thread.o threads.o)
diff --git a/bindings/python/notmuch/query.py b/bindings/python/notmuch/query.py
index 94773ac5..378aa47d 100644
--- a/bindings/python/notmuch/query.py
+++ b/bindings/python/notmuch/query.py
@@ -17,7 +17,7 @@ along with notmuch. If not, see <http://www.gnu.org/licenses/>.
Copyright 2010 Sebastian Spaeth <Sebastian@SSpaeth.de>
"""
-from ctypes import c_char_p, c_uint
+from ctypes import c_char_p, c_uint, POINTER, byref
from .globals import (
nmlib,
Enum,
@@ -178,8 +178,8 @@ class Query(object):
raise NullPointerError
return Messages(msgs_p, self)
- _count_messages = nmlib.notmuch_query_count_messages
- _count_messages.argtypes = [NotmuchQueryP]
+ _count_messages = nmlib.notmuch_query_count_messages_st
+ _count_messages.argtypes = [NotmuchQueryP, POINTER(c_uint)]
_count_messages.restype = c_uint
def count_messages(self):
@@ -191,10 +191,14 @@ class Query(object):
:rtype: int
'''
self._assert_query_is_initialized()
- return Query._count_messages(self._query)
+ count = c_uint(0)
+ status = Query._count_messages(self._query, byref(count))
+ if status != 0:
+ raise NotmuchError(status)
+ return count.value
- _count_threads = nmlib.notmuch_query_count_threads
- _count_threads.argtypes = [NotmuchQueryP]
+ _count_threads = nmlib.notmuch_query_count_threads_st
+ _count_threads.argtypes = [NotmuchQueryP, POINTER(c_uint)]
_count_threads.restype = c_uint
def count_threads(self):
@@ -210,7 +214,11 @@ class Query(object):
:rtype: int
'''
self._assert_query_is_initialized()
- return Query._count_threads(self._query)
+ count = c_uint(0)
+ status = Query._count_threads(self._query, byref(count))
+ if status != 0:
+ raise NotmuchError(status)
+ return count.value
_destroy = nmlib.notmuch_query_destroy
_destroy.argtypes = [NotmuchQueryP]
diff --git a/bindings/python/notmuch/version.py b/bindings/python/notmuch/version.py
index 1295ac5a..db9d0764 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.20.2'
+__VERSION__ = '0.21'
SOVERSION = '4'
diff --git a/bindings/ruby/README b/bindings/ruby/README
new file mode 100644
index 00000000..a2946b66
--- /dev/null
+++ b/bindings/ruby/README
@@ -0,0 +1,7 @@
+To build the the notmuch ruby extension, run the following commands
+from the *top level* notmuch source directory:
+
+% ./configure
+% make ruby-bindings
+
+The generic documentation about building notmuch also applies.
diff --git a/bindings/ruby/extconf.rb b/bindings/ruby/extconf.rb
index 6160db26..ddaa6841 100644
--- a/bindings/ruby/extconf.rb
+++ b/bindings/ruby/extconf.rb
@@ -10,22 +10,16 @@ dir = File.join('..', '..', 'lib')
# includes
$INCFLAGS = "-I#{dir} #{$INCFLAGS}"
-# make sure there are no undefined symbols
-$LDFLAGS += ' -Wl,--no-undefined'
-
-def have_local_library(lib, path, func, headers = nil)
- checking_for checking_message(func, lib) do
- lib = File.join(path, lib)
- if try_func(func, lib, headers)
- $LOCAL_LIBS += lib
- end
- end
+if ENV['EXTRA_LDFLAGS']
+ $LDFLAGS += " " + ENV['EXTRA_LDFLAGS']
end
-if not have_local_library('libnotmuch.so', dir, 'notmuch_database_create', 'notmuch.h')
+if not ENV['LIBNOTMUCH']
exit 1
end
+$LOCAL_LIBS += ENV['LIBNOTMUCH']
+
# Create Makefile
dir_config('notmuch')
create_makefile('notmuch')
diff --git a/bindings/ruby/query.c b/bindings/ruby/query.c
index a7dacba3..8cbc73f2 100644
--- a/bindings/ruby/query.c
+++ b/bindings/ruby/query.c
@@ -134,12 +134,13 @@ notmuch_rb_query_search_threads (VALUE self)
{
notmuch_query_t *query;
notmuch_threads_t *threads;
+ notmuch_status_t status;
Data_Get_Notmuch_Query (self, query);
- threads = notmuch_query_search_threads (query);
- if (!threads)
- rb_raise (notmuch_rb_eMemoryError, "Out of memory");
+ status = notmuch_query_search_threads_st (query, &threads);
+ if (status)
+ notmuch_rb_status_raise (status);
return Data_Wrap_Struct (notmuch_rb_cThreads, NULL, NULL, threads);
}
@@ -154,12 +155,13 @@ notmuch_rb_query_search_messages (VALUE self)
{
notmuch_query_t *query;
notmuch_messages_t *messages;
+ notmuch_status_t status;
Data_Get_Notmuch_Query (self, query);
- messages = notmuch_query_search_messages (query);
- if (!messages)
- rb_raise (notmuch_rb_eMemoryError, "Out of memory");
+ status = notmuch_query_search_messages_st (query, &messages);
+ if (status)
+ notmuch_rb_status_raise (status);
return Data_Wrap_Struct (notmuch_rb_cMessages, NULL, NULL, messages);
}
@@ -173,14 +175,16 @@ VALUE
notmuch_rb_query_count_messages (VALUE self)
{
notmuch_query_t *query;
+ notmuch_status_t status;
+ unsigned int count;
Data_Get_Notmuch_Query (self, query);
- /* Xapian exceptions are not handled properly.
- * (function may return 0 after printing a message)
- * Thus there is nothing we can do here...
- */
- return UINT2NUM(notmuch_query_count_messages(query));
+ status = notmuch_query_count_messages_st (query, &count);
+ if (status)
+ notmuch_rb_status_raise (status);
+
+ return UINT2NUM(count);
}
/*
@@ -192,12 +196,14 @@ VALUE
notmuch_rb_query_count_threads (VALUE self)
{
notmuch_query_t *query;
+ notmuch_status_t status;
+ unsigned int count;
Data_Get_Notmuch_Query (self, query);
- /* Xapian exceptions are not handled properly.
- * (function may return 0 after printing a message)
- * Thus there is nothing we can do here...
- */
- return UINT2NUM(notmuch_query_count_threads(query));
+ status = notmuch_query_count_threads_st (query, &count);
+ if (status)
+ notmuch_rb_status_raise (status);
+
+ return UINT2NUM(count);
}
diff --git a/completion/notmuch-completion.bash b/completion/notmuch-completion.bash
index 960275d1..cc583924 100644
--- a/completion/notmuch-completion.bash
+++ b/completion/notmuch-completion.bash
@@ -27,6 +27,8 @@
# on completion.
#
+_notmuch_shared_options="--help --uuid= --version"
+
# $1: current input of the form prefix:partialinput, where prefix is
# to or from.
_notmuch_email()
@@ -84,7 +86,7 @@ _notmuch_search_terms()
sed "s|^$path/||" | grep -v "\(^\|/\)\(cur\|new\|tmp\)$" ) )
;;
*)
- local search_terms="from: to: subject: attachment: mimetype: tag: id: thread: folder: path: date:"
+ local search_terms="from: to: subject: attachment: mimetype: tag: id: thread: folder: path: date: lastmod:"
compopt -o nospace
COMPREPLY=( $(compgen -W "${search_terms}" -- ${cur}) )
;;
@@ -109,7 +111,7 @@ _notmuch_compact()
! $split &&
case "${cur}" in
-*)
- local options="--backup= --quiet"
+ local options="--backup= --quiet ${_notmuch_shared_options}"
compopt -o nospace
COMPREPLY=( $(compgen -W "$options" -- ${cur}) )
;;
@@ -162,7 +164,7 @@ _notmuch_count()
! $split &&
case "${cur}" in
-*)
- local options="--output= --exclude= --batch --input="
+ local options="--output= --exclude= --batch --input= --lastmod ${_notmuch_shared_options}"
compopt -o nospace
COMPREPLY=( $(compgen -W "$options" -- ${cur}) )
;;
@@ -192,7 +194,7 @@ _notmuch_dump()
! $split &&
case "${cur}" in
-*)
- local options="--gzip --format= --output="
+ local options="--gzip --format= --output= ${_notmuch_shared_options}"
compopt -o nospace
COMPREPLY=( $(compgen -W "$options" -- ${cur}) )
;;
@@ -222,7 +224,7 @@ _notmuch_insert()
! $split &&
case "${cur}" in
--*)
- local options="--create-folder --folder= --keep --no-hooks"
+ local options="--create-folder --folder= --keep --no-hooks ${_notmuch_shared_options}"
compopt -o nospace
COMPREPLY=( $(compgen -W "$options" -- ${cur}) )
return
@@ -245,7 +247,8 @@ _notmuch_new()
case "${cur}" in
-*)
- local options="--no-hooks --quiet"
+ local options="--no-hooks --quiet ${_notmuch_shared_options}"
+ compopt -o nospace
COMPREPLY=( $(compgen -W "${options}" -- ${cur}) )
;;
esac
@@ -271,7 +274,7 @@ _notmuch_reply()
! $split &&
case "${cur}" in
-*)
- local options="--format= --format-version= --reply-to= --decrypt"
+ local options="--format= --format-version= --reply-to= --decrypt ${_notmuch_shared_options}"
compopt -o nospace
COMPREPLY=( $(compgen -W "$options" -- ${cur}) )
;;
@@ -301,7 +304,7 @@ _notmuch_restore()
! $split &&
case "${cur}" in
-*)
- local options="--format= --accumulate --input="
+ local options="--format= --accumulate --input= ${_notmuch_shared_options}"
compopt -o nospace
COMPREPLY=( $(compgen -W "$options" -- ${cur}) )
;;
@@ -336,7 +339,7 @@ _notmuch_search()
! $split &&
case "${cur}" in
-*)
- local options="--format= --output= --sort= --offset= --limit= --exclude= --duplicate="
+ local options="--format= --output= --sort= --offset= --limit= --exclude= --duplicate= ${_notmuch_shared_options}"
compopt -o nospace
COMPREPLY=( $(compgen -W "$options" -- ${cur}) )
;;
@@ -369,12 +372,16 @@ _notmuch_address()
COMPREPLY=( $( compgen -W "true false flag all" -- "${cur}" ) )
return
;;
+ --deduplicate)
+ COMPREPLY=( $( compgen -W "no mailbox address" -- "${cur}" ) )
+ return
+ ;;
esac
! $split &&
case "${cur}" in
-*)
- local options="--format= --output= --sort= --exclude="
+ local options="--format= --output= --sort= --exclude= --deduplicate= ${_notmuch_shared_options}"
compopt -o nospace
COMPREPLY=( $(compgen -W "$options" -- ${cur}) )
;;
@@ -408,7 +415,7 @@ _notmuch_show()
! $split &&
case "${cur}" in
-*)
- local options="--entire-thread= --format= --exclude= --body= --format-version= --part= --verify --decrypt --include-html"
+ local options="--entire-thread= --format= --exclude= --body= --format-version= --part= --verify --decrypt --include-html ${_notmuch_shared_options}"
compopt -o nospace
COMPREPLY=( $(compgen -W "$options" -- ${cur}) )
;;
@@ -435,7 +442,7 @@ _notmuch_tag()
! $split &&
case "${cur}" in
--*)
- local options="--batch --input= --remove-all"
+ local options="--batch --input= --remove-all ${_notmuch_shared_options}"
compopt -o nospace
COMPREPLY=( $(compgen -W "$options" -- ${cur}) )
return
@@ -477,10 +484,15 @@ _notmuch()
if [ -z "${arg}" ]; then
# top level completion
- local top_options="--help --version"
case "${cur}" in
- -*) COMPREPLY=( $(compgen -W "${top_options}" -- ${cur}) ) ;;
- *) COMPREPLY=( $(compgen -W "${_notmuch_commands}" -- ${cur}) ) ;;
+ -*)
+ # XXX: handle ${_notmuch_shared_options} and --config=
+ local options="--help --version"
+ COMPREPLY=( $(compgen -W "${options}" -- ${cur}) )
+ ;;
+ *)
+ COMPREPLY=( $(compgen -W "${_notmuch_commands}" -- ${cur}) )
+ ;;
esac
elif [ "${arg}" = "help" ]; then
# handle help command specially due to _notmuch_commands usage
diff --git a/configure b/configure
index 4af7ba94..440d678c 100755
--- a/configure
+++ b/configure
@@ -21,6 +21,7 @@ srcdir=$(dirname "$0")
subdirs="util compat lib parse-time-string completion doc emacs"
subdirs="${subdirs} performance-test test test/test-databases"
+subdirs="${subdirs} bindings"
# For a non-srcdir configure invocation (such as ../configure), create
# the directory structure and copy Makefiles.
@@ -37,7 +38,7 @@ if [ "$srcdir" != "." ]; then
cp -a "$srcdir"/test/* test
# Emacs only likes to generate compiled files next to the .el files
- # by default so copy these as well (which is not ideal0.
+ # by default so copy these as well (which is not ideal).
cp -a "$srcdir"/emacs/*.el emacs
fi
@@ -47,9 +48,11 @@ CC=${CC:-cc}
CXX=${CXX:-c++}
CFLAGS=${CFLAGS:--g -O2}
CPPFLAGS=${CPPFLAGS:-}
+CXXFLAGS_for_sh=${CXXFLAGS:-${CFLAGS}}
CXXFLAGS=${CXXFLAGS:-\$(CFLAGS)}
LDFLAGS=${LDFLAGS:-}
XAPIAN_CONFIG=${XAPIAN_CONFIG:-xapian-config}
+PYTHON=${PYTHON:-}
# We don't allow the EMACS or GZIP Makefile variables inherit values
# from the environment as we do with CC and CXX above. The reason is
@@ -62,20 +65,12 @@ XAPIAN_CONFIG=${XAPIAN_CONFIG:-xapian-config}
# options.
PREFIX=/usr/local
LIBDIR=
+WITH_DOCS=1
WITH_EMACS=1
WITH_BASH=1
+WITH_RUBY=1
WITH_ZSH=1
-# Compatible GMime versions (with constraints).
-# If using GMime 2.6, we need to have a version >= 2.6.5 to avoid a
-# crypto bug. We need 2.6.7 for permissive "From " header handling.
-GMIME_24_VERSION_CTR=''
-GMIME_24_VERSION="gmime-2.4 $GMIME_24_VERSION_CTR"
-GMIME_26_VERSION_CTR='>= 2.6.7'
-GMIME_26_VERSION="gmime-2.6 $GMIME_26_VERSION_CTR"
-
-WITH_GMIME_VERSIONS="$GMIME_26_VERSION;$GMIME_24_VERSION"
-
usage ()
{
cat <<EOF
@@ -95,7 +90,7 @@ First, some common variables can specified via environment variables:
CC The C compiler to use
CFLAGS Flags to pass to the C compiler
- CPPFLAGS Flags to pass to the C preprocessor
+ CPPFLAGS Flags to pass to the C preprocessor
CXX The C++ compiler to use
CXXFLAGS Flags to pass to the C compiler
LDFLAGS Flags to pass when linking
@@ -109,6 +104,8 @@ Other environment variables can be used to control configure itself,
XAPIAN_CONFIG The program to use to determine flags for
compiling and linking against the Xapian
library. [$XAPIAN_CONFIG]
+ PYTHON Name of python command to use in
+ configure and the test suite.
Additionally, various options can be specified on the configure
command line.
@@ -133,15 +130,13 @@ Fine tuning of some installation directories is available:
--bashcompletiondir=DIR Bash completions files [SYSCONFDIR/bash_completion.d]
--zshcompletiondir=DIR Zsh completions files [PREFIX/share/zsh/functions/Completion/Unix]
-Some specific library versions can be specified (auto-detected otherwise):
-
- --with-gmime-version=VERS Specify GMIME version (2.4 or 2.6)
-
Some features can be disabled (--with-feature=no is equivalent to
--without-feature) :
- --without-emacs Do not install lisp file
--without-bash-completion Do not install bash completions files
+ --without-docs Do not install documentation and man pages
+ --without-emacs Do not install lisp file
+ --without-ruby Do not install ruby bindings
--without-zsh-completion Do not install zsh completions files
Additional options are accepted for compatibility with other
@@ -182,6 +177,14 @@ for option; do
BASHCOMPLETIONDIR="${option#*=}"
elif [ "${option%%=*}" = '--zshcompletiondir' ] ; then
ZSHCOMLETIONDIR="${option#*=}"
+ elif [ "${option%%=*}" = '--with-docs' ]; then
+ if [ "${option#*=}" = 'no' ]; then
+ WITH_DOCS=0
+ else
+ WITH_DOCS=1
+ fi
+ elif [ "${option}" = '--without-docs' ] ; then
+ WITH_DOCS=0
elif [ "${option%%=*}" = '--with-emacs' ]; then
if [ "${option#*=}" = 'no' ]; then
WITH_EMACS=0
@@ -198,6 +201,14 @@ for option; do
fi
elif [ "${option}" = '--without-bash-completion' ] ; then
WITH_BASH=0
+ elif [ "${option%%=*}" = '--with-ruby' ]; then
+ if [ "${option#*=}" = 'no' ]; then
+ WITH_RUBY=0
+ else
+ WITH_RUBY=1
+ fi
+ elif [ "${option}" = '--without-ruby' ] ; then
+ WITH_RUBY=0
elif [ "${option%%=*}" = '--with-zsh-completion' ]; then
if [ "${option#*=}" = 'no' ]; then
WITH_ZSH=0
@@ -206,12 +217,6 @@ for option; do
fi
elif [ "${option}" = '--without-zsh-completion' ] ; then
WITH_ZSH=0
- elif [ "${option%%=*}" = '--with-gmime-version' ] ; then
- if [ "${option#*=}" = '2.4' ]; then
- WITH_GMIME_VERSIONS=$GMIME_24_VERSION
- elif [ "${option#*=}" = '2.6' ]; then
- WITH_GMIME_VERSIONS=$GMIME_26_VERSION
- fi
elif [ "${option%%=*}" = '--build' ] ; then
true
elif [ "${option%%=*}" = '--host' ] ; then
@@ -269,6 +274,64 @@ dependencies are available:
EOF
errors=0
+printf "int main(void){return 0;}\n" > minimal.c
+
+printf "Sanity checking C compilation environment... "
+if ${CC} ${CFLAGS} ${CPPFLAGS} minimal.c ${LDFLAGS} -o minimal > /dev/null 2>&1
+then
+ printf "OK.\n"
+else
+ printf "Fail.\n"
+ errors=$((errors + 1))
+fi
+
+printf "Sanity checking C++ compilation environment... "
+if ${CXX} ${CXXFLAGS_for_sh} ${CPPFLAGS} minimal.c ${LDFLAGS} -o minimal > /dev/null 2>&1
+then
+ printf "OK.\n"
+else
+ printf "Fail.\n"
+ errors=$((errors + 1))
+fi
+
+if [ $errors -gt 0 ]; then
+ cat <<EOF
+*** Error: Initial sanity checking of environment failed. Please try
+running configure in a clean environment, and if the problem persists,
+report a bug.
+EOF
+ rm -f minimal minimal.c
+ exit 1
+fi
+
+printf "Reading libnotmuch version from source... "
+cat > _libversion.c <<EOF
+#include <stdio.h>
+#include "lib/notmuch.h"
+int main(void) {
+ printf("libnotmuch_version_major=%d\n",
+ LIBNOTMUCH_MAJOR_VERSION);
+ printf("libnotmuch_version_minor=%d\n",
+ LIBNOTMUCH_MINOR_VERSION);
+ printf("libnotmuch_version_release=%d\n",
+ LIBNOTMUCH_MICRO_VERSION);
+ return 0;
+}
+EOF
+if ${CC} ${CFLAGS} _libversion.c -o _libversion > /dev/null 2>&1 && \
+ ./_libversion > _libversion.sh && . ./_libversion.sh
+then
+ printf "OK.\n"
+else
+ cat <<EOF
+
+*** Error: Reading lib/notmuch.h failed.
+Please try running configure again in a clean environment, and if the
+problem persists, report a bug.
+EOF
+ rm -f _libversion _libversion.c _libversion.sh
+ exit 1
+fi
if pkg-config --version > /dev/null 2>&1; then
have_pkg_config=1
@@ -298,30 +361,29 @@ have_xapian_compact=0
if [ ${have_xapian} = "1" ]; then
printf "Checking for Xapian compaction support... "
case "${xapian_version}" in
- 0.*|1.[01].*|1.2.[0-5])
- printf "No (only available with Xapian > 1.2.6).\n" ;;
- [1-9]*.[0-9]*.[0-9]*)
- have_xapian_compact=1
- printf "Yes.\n" ;;
- *)
- printf "Unknown version.\n" ;;
+ 0.*|1.[01].*|1.2.[0-5])
+ printf "No (only available with Xapian > 1.2.6).\n" ;;
+ [1-9]*.[0-9]*.[0-9]*)
+ have_xapian_compact=1
+ printf "Yes.\n" ;;
+ *)
+ printf "Unknown version.\n" ;;
esac
fi
+
+# we need to have a version >= 2.6.5 to avoid a crypto bug. We need
+# 2.6.7 for permissive "From " header handling.
+GMIME_MINVER=2.6.7
+
printf "Checking for GMime development files... "
-have_gmime=0
-IFS=';'
-for gmimepc in $WITH_GMIME_VERSIONS; do
- if pkg-config --exists $gmimepc; then
- printf "Yes ($gmimepc).\n"
- have_gmime=1
- gmime_cflags=$(pkg-config --cflags $gmimepc)
- gmime_ldflags=$(pkg-config --libs $gmimepc)
- break
- fi
-done
-IFS=$DEFAULT_IFS
-if [ "$have_gmime" = "0" ]; then
+if pkg-config --exists "gmime-2.6 >= $GMIME_MINVER"; then
+ printf "Yes.\n"
+ have_gmime=1
+ gmime_cflags=$(pkg-config --cflags gmime-2.6)
+ gmime_ldflags=$(pkg-config --libs gmime-2.6)
+else
+ have_gmime=0
printf "No.\n"
errors=$((errors + 1))
fi
@@ -377,7 +439,7 @@ fi
printf "Checking for python... "
have_python=0
-for name in python python2 python3; do
+for name in ${PYTHON} python python2 python3; do
if command -v $name > /dev/null; then
have_python=1
python=$name
@@ -434,22 +496,37 @@ else
have_emacs=0
fi
-printf "Checking if doxygen is available... "
-if command -v doxygen > /dev/null; then
- printf "Yes.\n"
- have_doxygen=1
-else
- printf "No (so will not install api docs)\n"
- have_doxygen=0
+have_doxygen=0
+if [ $WITH_DOCS = "1" ] ; then
+ printf "Checking if doxygen is available... "
+ if command -v doxygen > /dev/null; then
+ printf "Yes.\n"
+ have_doxygen=1
+ else
+ printf "No (so will not install api docs)\n"
+ fi
fi
-printf "Checking if sphinx is available and supports nroff output... "
-if command -v sphinx-build > /dev/null && ${python} -m sphinx.writers.manpage > /dev/null 2>&1 ; then
- printf "Yes.\n"
- have_sphinx=1
-else
- printf "No (so will not install man pages).\n"
- have_sphinx=0
+have_ruby_dev=0
+if [ $WITH_RUBY = "1" ] ; then
+ printf "Checking for ruby development files... "
+ if ruby -e "require 'mkmf'"> /dev/null 2>&1; then
+ printf "Yes.\n"
+ have_ruby_dev=1
+ else
+ printf "No (skipping ruby bindings)\n"
+ fi
+fi
+
+have_sphinx=0
+if [ $WITH_DOCS = "1" ] ; then
+ printf "Checking if sphinx is available and supports nroff output... "
+ if command -v sphinx-build > /dev/null && ${python} -m sphinx.writers.manpage > /dev/null 2>&1 ; then
+ printf "Yes.\n"
+ have_sphinx=1
+ else
+ printf "No (so will not install man pages).\n"
+ fi
fi
libdir_in_ldconfig=0
@@ -542,7 +619,7 @@ EOF
echo
fi
if [ $have_gmime -eq 0 ]; then
- echo " Either GMime 2.4 library" $GMIME_24_VERSION_CTR "or GMime 2.6 library" $GMIME_26_VERSION_CTR
+ echo " GMime 2.6 library >= $GMIME_MINVER"
echo " (including development files such as headers)"
echo " http://spruce.sourceforge.net/gmime/"
echo
@@ -690,8 +767,6 @@ else
fi
rm -f compat/check_asctime
-printf "int main(void){return 0;}\n" > minimal.c
-
printf "Checking for rpath support... "
if ${CC} -Wl,--enable-new-dtags -Wl,-rpath,/tmp/ -o minimal minimal.c >/dev/null 2>&1
then
@@ -712,6 +787,16 @@ else
as_needed_ldflags=""
fi
+printf "Checking for -Wl,--no-undefined... "
+if ${CC} -Wl,--no-undefined -o minimal minimal.c >/dev/null 2>&1
+then
+ printf "Yes.\n"
+ no_undefined_ldflags="-Wl,--no-undefined"
+else
+ printf "No (nothing to worry about).\n"
+ no_undefined_ldflags=""
+fi
+
WARN_CXXFLAGS=""
printf "Checking for available C++ compiler warning flags... "
for flag in -Wall -Wextra -Wwrite-strings; do
@@ -732,7 +817,7 @@ for flag in -Wmissing-declarations; do
done
printf "\n\t${WARN_CFLAGS}\n"
-rm -f minimal minimal.c
+rm -f minimal minimal.c _libversion.c _libversion _libversion.sh
# construct the Makefile.config
cat > Makefile.config <<EOF
@@ -770,6 +855,28 @@ vpath Makefile.% \$(srcdir)
vpath %.py \$(srcdir)
vpath %.rst \$(srcdir)
+# Library versions (used to make SONAME)
+# The major version of the library interface. This will control the soname.
+# As such, this number must be incremented for any incompatible change to
+# the library interface, (such as the deletion of an API or a major
+# semantic change that breaks formerly functioning code).
+#
+LIBNOTMUCH_VERSION_MAJOR = ${libnotmuch_version_major}
+
+# The minor version of the library interface. This should be incremented at
+# the time of release for any additions to the library interface,
+# (and when it is incremented, the release version of the library should
+# be reset to 0).
+LIBNOTMUCH_VERSION_MINOR = ${libnotmuch_version_minor}
+
+# The release version the library interface. This should be incremented at
+# the time of release if there have been no changes to the interface, (but
+# simply compatible changes to the implementation).
+LIBNOTMUCH_VERSION_RELEASE = ${libnotmuch_version_release}
+
+# These are derived from the VERSION macros in lib/notmuch.h so
+# if you have to change them, something is wrong.
+
# The C compiler to use
CC = ${CC}
@@ -858,6 +965,10 @@ HAVE_CANONICALIZE_FILE_NAME = ${have_canonicalize_file_name}
# build its own version)
HAVE_GETLINE = ${have_getline}
+# Are the ruby development files (and ruby) available? If not skip
+# building/testing ruby bindings.
+HAVE_RUBY_DEV = ${have_ruby_dev}
+
# Whether the strcasestr function is available (if not, then notmuch will
# build its own version)
HAVE_STRCASESTR = ${have_strcasestr}
@@ -894,7 +1005,7 @@ LINKER_RESOLVES_LIBRARY_DEPENDENCIES = ${linker_resolves_library_dependencies}
XAPIAN_CXXFLAGS = ${xapian_cxxflags}
XAPIAN_LDFLAGS = ${xapian_ldflags}
-# Flags needed to compile and link against GMime-2.4
+# Flags needed to compile and link against GMime
GMIME_CFLAGS = ${gmime_cflags}
GMIME_LDFLAGS = ${gmime_ldflags}
@@ -912,6 +1023,9 @@ RPATH_LDFLAGS = ${rpath_ldflags}
# Flags needed to have linker link only to necessary libraries
AS_NEEDED_LDFLAGS = ${as_needed_ldflags}
+# Flags to have the linker flag undefined symbols in object files
+NO_UNDEFINED_LDFLAGS = ${no_undefined_ldflags}
+
# Whether valgrind header files are available
HAVE_VALGRIND = ${have_valgrind}
@@ -970,6 +1084,10 @@ NOTMUCH_HAVE_MAN=$((have_sphinx))
# Name of python interpreter
NOTMUCH_PYTHON=${python}
+
+# Are the ruby development files (and ruby) available? If not skip
+# building/testing ruby bindings.
+NOTMUCH_HAVE_RUBY_DEV=${have_ruby_dev}
EOF
# Finally, after everything configured, inform the user how to continue.
diff --git a/crypto.c b/crypto.c
index 026640fe..a6eb27db 100644
--- a/crypto.c
+++ b/crypto.c
@@ -20,8 +20,6 @@
#include "notmuch-client.h"
-#ifdef GMIME_ATLEAST_26
-
/* Create a GPG context (GMime 2.6) */
static notmuch_crypto_context_t *
create_gpg_context (const char *gpgpath)
@@ -39,29 +37,6 @@ create_gpg_context (const char *gpgpath)
return gpgctx;
}
-#else /* GMIME_ATLEAST_26 */
-
-/* Create a GPG context (GMime 2.4) */
-static notmuch_crypto_context_t *
-create_gpg_context (const char* gpgpath)
-{
- GMimeSession *session;
- notmuch_crypto_context_t *gpgctx;
-
- session = g_object_new (g_mime_session_get_type (), NULL);
- gpgctx = g_mime_gpg_context_new (session, gpgpath ? gpgpath : "gpg");
- g_object_unref (session);
-
- if (! gpgctx)
- return NULL;
-
- g_mime_gpg_context_set_always_trust ((GMimeGpgContext *) gpgctx, FALSE);
-
- return gpgctx;
-}
-
-#endif /* GMIME_ATLEAST_26 */
-
/* for the specified protocol return the context pointer (initializing
* if needed) */
notmuch_crypto_context_t *
diff --git a/debian/NEWS b/debian/NEWS
index 81b6b0ad..174c1303 100644
--- a/debian/NEWS
+++ b/debian/NEWS
@@ -1,3 +1,13 @@
+notmuch (0.21~rc1-1) experimental; urgency=medium
+
+ * This release of notmuch requires a non-reversible database upgrade
+ to support database revision tracking. This upgrade will happen on
+ the first run of 'notmuch-new' after updating. Notmuch will backup
+ your tags for your before doing the upgrade, but it never hurts to
+ make your own backup with notmuch dump.
+
+ -- David Bremner <bremner@debian.org> Thu, 15 Oct 2015 08:13:04 -0300
+
notmuch (0.19-1) experimental; urgency=medium
* This release of notmuch again requires a non-reversable database
diff --git a/debian/changelog b/debian/changelog
index 582d44d5..cd01e0e9 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,67 @@
+notmuch (0.21-3) unstable; urgency=medium
+
+ * Add mips and mips64el to gdb build-dep blacklist
+
+ -- David Bremner <bremner@debian.org> Sat, 14 Nov 2015 19:07:06 -0400
+
+notmuch (0.21-2) unstable; urgency=medium
+
+ * Build-conflict with gdb on ppc64el and mipsel. Workaround gdb breakage on those
+ architectures (Closes: #804792).
+
+ -- David Bremner <bremner@debian.org> Thu, 12 Nov 2015 08:54:23 -0400
+
+notmuch (0.21-1) unstable; urgency=medium
+
+ * New upstream release. Highlights include
+ - revision tracking for metadata
+ - new features and bug fixes for emacs interface
+ See /usr/share/doc/notmuch/NEWS for more details.
+
+ -- David Bremner <bremner@debian.org> Thu, 29 Oct 2015 20:04:42 -0300
+
+notmuch (0.21~rc3-3) experimental; urgency=medium
+
+ * Build-conflict with gdb-minimal. gdb python scripts are needed for
+ the test suite
+
+ -- David Bremner <bremner@debian.org> Sun, 25 Oct 2015 22:08:56 -0300
+
+notmuch (0.21~rc3-2) experimental; urgency=medium
+
+ * Bug fix: "reply-to encrypted messages in tree view fails to quote
+ and defaults to unencrypted message", thanks to Vagrant Cascadian
+ (Closes: #795243).
+ * Bug fix: "install/notmuch-emacs may interact with console, fail
+ emacs24 upgrade", thanks to Hilko Bengen (Closes: #802952).
+
+ -- David Bremner <bremner@debian.org> Sun, 25 Oct 2015 13:42:57 -0300
+
+notmuch (0.21~rc3-1) experimental; urgency=medium
+
+ * New upstream release candidate
+
+ -- David Bremner <bremner@debian.org> Thu, 22 Oct 2015 09:19:02 -0300
+
+notmuch (0.21~rc2-1) experimental; urgency=medium
+
+ * New upstream release candidate
+
+ -- David Bremner <bremner@debian.org> Mon, 19 Oct 2015 07:25:10 -0300
+
+notmuch (0.21~rc1-1) experimental; urgency=medium
+
+ * New upstream release candidate
+
+ -- David Bremner <bremner@debian.org> Thu, 15 Oct 2015 08:08:17 -0300
+
+notmuch (0.20.2-2) unstable; urgency=medium
+
+ * Fix linking in emacsen-install script. The previous version can
+ break an emacs upgrade.
+
+ -- David Bremner <bremner@debian.org> Sat, 26 Sep 2015 09:26:41 -0300
+
notmuch (0.20.2-1~bpo8+1) jessie-backports; urgency=medium
* Rebuild for jessie-backports.
diff --git a/debian/control b/debian/control
index 4bc4cd9f..7e6a548c 100644
--- a/debian/control
+++ b/debian/control
@@ -5,7 +5,7 @@ Maintainer: Carl Worth <cworth@debian.org>
Uploaders:
Jameson Graef Rollins <jrollins@finestructure.net>,
David Bremner <bremner@debian.org>
-Build-Conflicts: ruby1.8
+Build-Conflicts: ruby1.8, gdb-minimal, gdb [s390x ia64 armel ppc64el mips mipsel mips64el]
Build-Depends:
debhelper (>= 9),
pkg-config,
@@ -20,7 +20,7 @@ Build-Depends:
ruby, ruby-dev (>>1:1.9.3~),
emacs24-nox | emacs24 (>=24~) | emacs24-lucid (>=24~) |
emacs23-nox | emacs23 (>=23~) | emacs23-lucid (>=23~),
- gdb [!s390x !ia64 !armel],
+ gdb [!s390x !ia64 !armel !ppc64el !mips !mipsel !mips64el],
dtach (>= 0.8),
bash-completion (>=1.9.0~)
Standards-Version: 3.9.6
diff --git a/debian/libnotmuch4.symbols b/debian/libnotmuch4.symbols
index ec46aa9b..7dd54237 100644
--- a/debian/libnotmuch4.symbols
+++ b/debian/libnotmuch4.symbols
@@ -12,6 +12,7 @@ libnotmuch.so.4 libnotmuch4 #MINVER#
notmuch_database_get_all_tags@Base 0.3
notmuch_database_get_directory@Base 0.3
notmuch_database_get_path@Base 0.3
+ notmuch_database_get_revision@Base 0.21~rc1
notmuch_database_get_version@Base 0.3
notmuch_database_needs_upgrade@Base 0.3
notmuch_database_open@Base 0.3
@@ -19,6 +20,7 @@ libnotmuch.so.4 libnotmuch4 #MINVER#
notmuch_database_remove_message@Base 0.3
notmuch_database_status_string@Base 0.20~rc1
notmuch_database_upgrade@Base 0.3
+ notmuch_directory_delete@Base 0.21~rc1
notmuch_directory_destroy@Base 0.3
notmuch_directory_get_child_directories@Base 0.3
notmuch_directory_get_child_files@Base 0.3
@@ -53,9 +55,12 @@ libnotmuch.so.4 libnotmuch4 #MINVER#
notmuch_messages_valid@Base 0.3
notmuch_query_add_tag_exclude@Base 0.12~rc1
notmuch_query_count_messages@Base 0.3
+ notmuch_query_count_messages_st@Base 0.21~rc1
notmuch_query_count_threads@Base 0.10~rc1
+ notmuch_query_count_threads_st@Base 0.21~rc1
notmuch_query_create@Base 0.3
notmuch_query_destroy@Base 0.3
+ notmuch_query_get_database@Base 0.21~rc1
notmuch_query_get_query_string@Base 0.4
notmuch_query_get_sort@Base 0.4
notmuch_query_search_messages@Base 0.3
diff --git a/debian/notmuch-emacs.emacsen-install b/debian/notmuch-emacs.emacsen-install
index f35ef48e..cce95c34 100755
--- a/debian/notmuch-emacs.emacsen-install
+++ b/debian/notmuch-emacs.emacsen-install
@@ -31,7 +31,7 @@ echo install/${PACKAGE}: byte-compiling for ${FLAVOR}
# Create symlinks to the .el files (see section 6E in debian-emacs
# polcy). This makes complation easy, and also allows find-function
# and find-library to work properly.
-(cd ${elc_dir} && cp -s ${el_dir}/*.el .)
+(cd ${elc_dir} && ln -sf ${el_dir}/*.el .)
# Byte compile them
(cd ${elc_dir}
@@ -43,6 +43,6 @@ echo install/${PACKAGE}: byte-compiling for ${FLAVOR}
exit 1
fi
set -e
- gzip -9 Install.log)
+ gzip -9f Install.log)
exit 0;
diff --git a/debian/rules b/debian/rules
index 56378ecd..04f0062a 100755
--- a/debian/rules
+++ b/debian/rules
@@ -19,7 +19,6 @@ override_dh_auto_build:
dh_auto_build -- V=1
dh_auto_build --sourcedirectory bindings/python
cd bindings/python && $(python3_all) setup.py build
- cd bindings/ruby && ruby extconf.rb --vendor && make
$(MAKE) -C contrib/notmuch-mutt
override_dh_auto_clean:
diff --git a/devel/STYLE b/devel/STYLE
index 92de42cc..24bd5482 100644
--- a/devel/STYLE
+++ b/devel/STYLE
@@ -93,3 +93,13 @@ libnotmuch conventions
* Code which needs to be accessed from both the CLI and from
libnotmuch should be factored out into libutil (under util/).
+
+* Deprecated functions should be marked with the NOTMUCH_DEPRECATED
+ macro which generates run time warnings with gcc and clang. In order
+ not to confuse doxygen this should go at the beginning of the
+ declaration like:
+
+ NOTMUCH_DEPRECATED(major,minor) notmuch_status_t notmuch_dwim(void *arg);
+
+ The @deprecated doxygen command can be used to generate markup in
+ the API docs.
diff --git a/devel/gen-testdb.sh b/devel/gen-testdb.sh
index 621b31e6..61ae48a3 100755
--- a/devel/gen-testdb.sh
+++ b/devel/gen-testdb.sh
@@ -79,7 +79,7 @@ while getopts v:c:s: opt; do
done
shift `expr $OPTIND - 1`
-. ./test-lib.sh
+. ./test-lib.sh || exit 1
SHORT_CORPUS=$(basename ${CORPUS:-database})
DBNAME=${SHORT_CORPUS}${SUFFIX}
diff --git a/devel/nmbug/nmbug-status b/devel/nmbug/nmbug-status
index e845c2a5..b36b6ad3 100755
--- a/devel/nmbug/nmbug-status
+++ b/devel/nmbug/nmbug-status
@@ -156,11 +156,20 @@ class Page (object):
stream.write(self.footer)
def _write_view(self, database, view, stream):
+ # sort order, default to oldest-first
+ sort_key = view.get('sort', 'oldest-first')
+ # dynamically accept all values in Query.SORT
+ sort_attribute = sort_key.upper().replace('-', '_')
+ try:
+ sort = getattr(notmuch.Query.SORT, sort_attribute)
+ except AttributeError:
+ raise ConfigError('Invalid sort setting for {}: {!r}'.format(
+ view['title'], sort_key))
if 'query-string' not in view:
query = view['query']
view['query-string'] = ' and '.join(query)
q = notmuch.Query(database, view['query-string'])
- q.set_sort(notmuch.Query.SORT.OLDEST_FIRST)
+ q.set_sort(sort)
threads = self._get_threads(messages=q.search_messages())
self._write_view_header(view=view, stream=stream)
self._write_threads(threads=threads, stream=stream)
@@ -309,7 +318,7 @@ args = parser.parse_args()
try:
config = read_config(path=args.config)
except ConfigError as e:
- print(e)
+ print(e, file=sys.stderr)
sys.exit(1)
header_template = config['meta'].get('header', '''<!DOCTYPE html>
diff --git a/devel/release-checks.sh b/devel/release-checks.sh
index ae02f557..8604a9f7 100755
--- a/devel/release-checks.sh
+++ b/devel/release-checks.sh
@@ -59,6 +59,17 @@ readonly VERSION
# In the rest of this file, tests collect list of errors to be fixed
+echo -n "Checking that git working directory is clean... "
+git_status=`git status --porcelain`
+if [ "$git_status" = '' ]
+then
+ echo Yes.
+else
+ echo No.
+ append_emsg "Git working directory is not clean (git status --porcelain)."
+fi
+unset git_status
+
verfail ()
{
echo No.
@@ -77,38 +88,6 @@ case $VERSION in
*) verfail "'$VERSION' is a single number" ;;
esac
-echo -n "Checking that LIBNOTMUCH version macros & variables match ... "
-# lib/notmuch.h
-LIBNOTMUCH_MAJOR_VERSION=broken
-LIBNOTMUCH_MINOR_VERSION=broken
-LIBNOTMUCH_MICRO_VERSION=broken
-# lib/Makefile.local
-LIBNOTMUCH_VERSION_MAJOR=borken
-LIBNOTMUCH_VERSION_MINOR=borken
-LIBNOTMUCH_VERSION_RELEASE=borken
-
-eval `awk 'NF == 3 && $1 == "#define" && $2 ~ /^LIBNOTMUCH_[A-Z]+_VERSION$/ \
- && $3 ~ /^[0-9]+$/ { print $2 "=" $3 }' lib/notmuch.h`
-
-eval `awk 'NF == 3 && $1 ~ /^LIBNOTMUCH_VERSION_[A-Z]+$/ && $2 == "=" \
- && $3 ~ /^[0-9]+$/ { print $1 "=" $3 }' lib/Makefile.local`
-
-
-check_version_component ()
-{
- eval local v1=\$LIBNOTMUCH_$1_VERSION
- eval local v2=\$LIBNOTMUCH_VERSION_$2
- if [ $v1 != $v2 ]
- then append_emsg "LIBNOTMUCH_$1_VERSION ($v1) does not equal LIBNOTMUCH_VERSION_$2 ($v2)"
- fi
-}
-
-old_emsg_count=$emsg_count
-check_version_component MAJOR MAJOR
-check_version_component MINOR MINOR
-check_version_component MICRO RELEASE
-[ $old_emsg_count = $emsg_count ] && echo Yes. || echo No.
-
echo -n "Checking that this is Debian package for notmuch... "
read deb_notmuch deb_version rest < debian/changelog
if [ "$deb_notmuch" = 'notmuch' ]
@@ -130,7 +109,7 @@ else
fi
echo -n "Checking that python bindings version is $VERSION... "
-py_version=`python -c "with open('$PV_FILE') as vf: exec(vf.read()); print __VERSION__"`
+py_version=`python -c "with open('$PV_FILE') as vf: exec(vf.read()); print(__VERSION__)"`
if [ "$py_version" = "$VERSION" ]
then
echo Yes.
diff --git a/doc/Makefile.local b/doc/Makefile.local
index e2097491..8633cfcd 100644
--- a/doc/Makefile.local
+++ b/doc/Makefile.local
@@ -7,7 +7,7 @@ SPHINXOPTS := -q
SPHINXBUILD = sphinx-build
DOCBUILDDIR := $(dir)/_build
-mkdocdeps := python $(srcdir)/$(dir)/mkdocdeps.py
+mkdocdeps := $(PYTHON) $(srcdir)/$(dir)/mkdocdeps.py
# Internal variables.
ALLSPHINXOPTS := -d $(DOCBUILDDIR)/doctrees $(SPHINXOPTS) $(srcdir)/$(dir)
diff --git a/doc/conf.py b/doc/conf.py
index 8fbc8544..65adafeb 100644
--- a/doc/conf.py
+++ b/doc/conf.py
@@ -12,7 +12,7 @@ master_doc = 'index'
# General information about the project.
project = u'notmuch'
-copyright = u'2014, Carl Worth and many others'
+copyright = u'2009-2015, Carl Worth and many others'
location = os.path.dirname(__file__)
diff --git a/doc/doxygen.cfg b/doc/doxygen.cfg
index 42b63394..c033f344 100644
--- a/doc/doxygen.cfg
+++ b/doc/doxygen.cfg
@@ -74,7 +74,7 @@ STRICT_PROTO_MATCHING = NO
GENERATE_TODOLIST = NO
GENERATE_TESTLIST = NO
GENERATE_BUGLIST = NO
-GENERATE_DEPRECATEDLIST= NO
+GENERATE_DEPRECATEDLIST= YES
ENABLED_SECTIONS =
MAX_INITIALIZER_LINES = 30
SHOW_USED_FILES = NO
diff --git a/doc/man1/notmuch-address.rst b/doc/man1/notmuch-address.rst
index 9570095b..7f7214b3 100644
--- a/doc/man1/notmuch-address.rst
+++ b/doc/man1/notmuch-address.rst
@@ -55,6 +55,28 @@ Supported options for **address** include
Note: With this option, addresses are printed only after
the whole search is finished. This may take long time.
+ ``--deduplicate=(no|mailbox|address)``
+
+ Control the deduplication of results.
+
+ **no**
+ Output all occurences of addresses in the matching
+ messages. This is not applicable with --output=count.
+
+ **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**
+ 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 matching messages. If --output=count
+ is specified, include all variants in the count.
+
``--sort=``\ (**newest-first**\ \|\ **oldest-first**)
This option can be used to present results in either
chronological order (**oldest-first**) or reverse chronological
@@ -63,7 +85,9 @@ Supported options for **address** include
By default, results will be displayed in reverse chronological
order, (that is, the newest results will be displayed first).
- This option is not supported with --output=count.
+ However, if either --output=count or --deduplicate=address is
+ specified, this option is ignored and the order of the results
+ is unspecified.
``--exclude=(true|false)``
A message is called "excluded" if it matches at least one tag in
diff --git a/doc/man1/notmuch-count.rst b/doc/man1/notmuch-count.rst
index ca78c18b..99de13a9 100644
--- a/doc/man1/notmuch-count.rst
+++ b/doc/man1/notmuch-count.rst
@@ -47,6 +47,11 @@ Supported options for **count** include
(or threads) in the database will be output. This option is not
compatible with specifying search terms on the command line.
+ ``--lastmod``
+ Append lastmod (counter for number of database updates) and UUID
+ to the output. lastmod values are only comparable between databases
+ with the same UUID.
+
``--input=``\ <filename>
Read input from given file, instead of from stdin. Implies
``--batch``.
diff --git a/doc/man1/notmuch-emacs-mua.rst b/doc/man1/notmuch-emacs-mua.rst
index 36b51cdc..7c572904 100644
--- a/doc/man1/notmuch-emacs-mua.rst
+++ b/doc/man1/notmuch-emacs-mua.rst
@@ -18,10 +18,6 @@ Supported options for **notmuch-emacs-mua** include
``-h, --help``
Display help.
- ``--client``
- Use emacsclient, rather than emacs. This will start
- an emacs daemon process if necessary.
-
``-s, --subject=``\ <subject>
Specify the subject of the message.
@@ -38,7 +34,23 @@ Supported options for **notmuch-emacs-mua** include
Specify a file to include into the body of the message.
``--no-window-system``
- Even if a window system is available, use the current terminal
+ Even if a window system is available, use the current terminal.
+
+ ``--client``
+ Use **emacsclient**, rather than **emacs**. For
+ **emacsclient** to work, you need an already running Emacs
+ with a server, or use ``--auto-daemon``.
+
+ ``--auto-daemon``
+ Automatically start Emacs in daemon mode, if the Emacs server
+ is not running. Applicable with ``--client``. Implies
+ ``--create-frame``.
+
+ ``--create-frame``
+ Create a new frame instead of trying to use the current Emacs
+ frame. Applicable with ``--client``. This will be required
+ when Emacs is running (or automatically started with
+ ``--auto-daemon``) in daemon mode.
``--print``
Output the resulting elisp to stdout instead of evaluating it.
diff --git a/doc/man1/notmuch.rst b/doc/man1/notmuch.rst
index b33738ed..3acfbdb4 100644
--- a/doc/man1/notmuch.rst
+++ b/doc/man1/notmuch.rst
@@ -39,15 +39,28 @@ OPTIONS
Supported global options for ``notmuch`` include
- ``--help``
- Print a synopsis of available commands and exit.
+ ``--help`` [command-name]
+ Print a synopsis of available commands and exit.
+ With an optional command name, show the man page
+ for that subcommand.
``--version``
- Print the installed version of notmuch, and exit.
+ Print the installed version of notmuch, and exit.
``--config=FILE``
- Specify the configuration file to use. This overrides any
- configuration file specified by ${NOTMUCH\_CONFIG}.
+ Specify the configuration file to use. This overrides any
+ configuration file specified by ${NOTMUCH\_CONFIG}.
+
+ ``--uuid=HEX``
+ Enforce that the database UUID (a unique identifier which
+ persists until e.g. the database is compacted)
+ is HEX; exit with an error if it is not. This is useful to
+ detect rollover in modification counts on messages. You can
+ find this UUID using e.g. ``notmuch count --lastmod``
+
+All global options except ``--config`` can also be specified after the
+command. For example, ``notmuch subcommand --uuid=HEX`` is
+equivalent to ``notmuch --uuid=HEX subcommand``.
COMMANDS
========
diff --git a/doc/man7/notmuch-search-terms.rst b/doc/man7/notmuch-search-terms.rst
index 1d27ac1e..2fbc16d1 100644
--- a/doc/man7/notmuch-search-terms.rst
+++ b/doc/man7/notmuch-search-terms.rst
@@ -54,6 +54,8 @@ indicate user-supplied values):
- date:<since>..<until>
+- lastmod:<initial-revision>..<final-revision>
+
The **from:** prefix is used to match the name or address of the sender
of an email message.
@@ -124,6 +126,12 @@ The time range can also be specified using timestamps with a syntax of:
Each timestamp is a number representing the number of seconds since
1970-01-01 00:00:00 UTC.
+The **lastmod:** prefix can be used to restrict the result by the
+database revision number of when messages were last modified (tags
+were added/removed or filenames changed). This is usually used in
+conjunction with the **--uuid** argument to **notmuch search**
+to find messages that have changed since an earlier query.
+
Operators
---------
@@ -270,6 +278,13 @@ In this case, <since> is taken as the earliest time it could describe
could describe (the end of yesterday). Similarly, date:january..february
matches from the beginning of January to the end of February.
+date:<expr>..! can be used as a shorthand for date:<expr>..<expr>. The
+expansion takes place before interpretation, and thus, for example,
+date:monday..! matches from the beginning of Monday until the end of
+Monday. (Note that entering date:<expr> without "..", for example
+date:yesterday, won't work, as it's not interpreted as a range
+expression at all. Again, use date:yesterday..!)
+
Currently, we do not support spaces in range expressions. You can
replace the spaces with '\_', or (in most cases) '-', or (in some cases)
leave the spaces out altogether. Examples in this man page use spaces
@@ -280,11 +295,6 @@ to specify date:..<until> or date:<since>.. to not limit the start or
end time, respectively. Pre-1.2.1 Xapian does not report an error on
open ended ranges, but it does not work as expected either.
-Entering date:expr without ".." (for example date:yesterday) won't work,
-as it's not interpreted as a range expression at all. You can achieve
-the expected result by duplicating the expr both sides of ".." (for
-example date:yesterday..yesterday).
-
Relative date and time
----------------------
diff --git a/emacs/notmuch-hello.el b/emacs/notmuch-hello.el
index 65d06276..8bde808f 100644
--- a/emacs/notmuch-hello.el
+++ b/emacs/notmuch-hello.el
@@ -28,6 +28,8 @@
(declare-function notmuch-search "notmuch" (&optional query oldest-first target-thread target-line continuation))
(declare-function notmuch-poll "notmuch" ())
+(declare-function notmuch-tree "notmuch-tree"
+ (&optional query query-context target buffer-name open-target))
(defun notmuch-saved-search-get (saved-search field)
"Get FIELD from SAVED-SEARCH.
@@ -91,7 +93,11 @@ searches so they still work in customize."
(choice :tag " Sort Order"
(const :tag "Default" nil)
(const :tag "Oldest-first" oldest-first)
- (const :tag "Newest-first" newest-first))))))
+ (const :tag "Newest-first" newest-first)))
+ (group :format "%v" :inline t (const :format "" :search-type)
+ (choice :tag " Search Type"
+ (const :tag "Search mode" nil)
+ (const :tag "Tree mode" tree))))))
(defcustom notmuch-saved-searches
`((:name "inbox" :query "tag:inbox" :key ,(kbd "i"))
@@ -114,6 +120,10 @@ a plist. Supported properties are
:sort-order Specify the sort order to be used for the search.
Possible values are 'oldest-first 'newest-first or
nil. Nil means use the default sort order.
+ :search-type Specify whether to run the search in search-mode
+ or tree mode. Set to 'tree to specify tree
+ mode, set to nil (or anything except tree) to
+ specify search mode.
Other accepted forms are a cons cell of the form (NAME . QUERY)
or a list of the form (NAME QUERY COUNT-QUERY)."
@@ -425,10 +435,13 @@ diagonal."
append (notmuch-hello-reflect-generate-row ncols nrows row list))))
(defun notmuch-hello-widget-search (widget &rest ignore)
- (notmuch-search (widget-get widget
- :notmuch-search-terms)
- (widget-get widget
- :notmuch-search-oldest-first)))
+ (if (widget-get widget :notmuch-search-type)
+ (notmuch-tree (widget-get widget
+ :notmuch-search-terms))
+ (notmuch-search (widget-get widget
+ :notmuch-search-terms)
+ (widget-get widget
+ :notmuch-search-oldest-first))))
(defun notmuch-saved-search-count (search)
(car (process-lines notmuch-command "count" search)))
@@ -564,6 +577,7 @@ with `notmuch-hello-query-counts'."
(newest-first nil)
(oldest-first t)
(otherwise notmuch-search-oldest-first)))
+ (search-type (eq (plist-get elem :search-type) 'tree))
(msg-count (plist-get elem :count)))
(widget-insert (format "%8s "
(notmuch-hello-nice-number msg-count)))
@@ -571,6 +585,7 @@ with `notmuch-hello-query-counts'."
:notify #'notmuch-hello-widget-search
:notmuch-search-terms query
:notmuch-search-oldest-first oldest-first
+ :notmuch-search-type search-type
name)
(setq column-indent
(1+ (max 0 (- column-width (length name)))))))
@@ -628,7 +643,7 @@ with `notmuch-hello-query-counts'."
(defun notmuch-hello-versions ()
"Display the notmuch version(s)"
(interactive)
- (let ((notmuch-cli-version (notmuch-version)))
+ (let ((notmuch-cli-version (notmuch-cli-version)))
(message "notmuch version %s"
(if (string= notmuch-emacs-version notmuch-cli-version)
notmuch-cli-version
diff --git a/emacs/notmuch-jump.el b/emacs/notmuch-jump.el
index 20e24b25..506ae2c8 100644
--- a/emacs/notmuch-jump.el
+++ b/emacs/notmuch-jump.el
@@ -54,7 +54,9 @@ fast way to jump to a saved search from anywhere in Notmuch."
(oldest-first t)
(otherwise (default-value 'notmuch-search-oldest-first)))))
(push (list key name
- `(lambda () (notmuch-search ',query ',oldest-first)))
+ (if (eq (plist-get saved-search :search-type) 'tree)
+ `(lambda () (notmuch-tree ',query))
+ `(lambda () (notmuch-search ',query ',oldest-first))))
action-map)))))
(setq action-map (nreverse action-map))
diff --git a/emacs/notmuch-lib.el b/emacs/notmuch-lib.el
index f8e51658..201d7ec8 100644
--- a/emacs/notmuch-lib.el
+++ b/emacs/notmuch-lib.el
@@ -25,6 +25,10 @@
(require 'mm-decode)
(require 'cl)
+(unless (require 'notmuch-version nil t)
+ (defconst notmuch-emacs-version "unknown"
+ "Placeholder variable when notmuch-version.el[c] is not available."))
+
(autoload 'notmuch-jump-search "notmuch-jump"
"Jump to a saved search by shortcut key." t)
@@ -192,8 +196,8 @@ Otherwise the output will be returned"
"Perhaps you haven't run \"notmuch setup\" yet? Try running this
on the command line, and then retry your notmuch command")))
-(defun notmuch-version ()
- "Return a string with the notmuch version number."
+(defun notmuch-cli-version ()
+ "Return a string with the notmuch cli command version number."
(let ((long-string
;; Trim off the trailing newline.
(substring (notmuch-command-to-string "--version") 0 -1)))
diff --git a/emacs/notmuch-maildir-fcc.el b/emacs/notmuch-maildir-fcc.el
index 07eedba2..c2f2f4cb 100644
--- a/emacs/notmuch-maildir-fcc.el
+++ b/emacs/notmuch-maildir-fcc.el
@@ -59,23 +59,19 @@ yet when sending a mail."
:require 'notmuch-fcc-initialization
:group 'notmuch-send)
-(defun notmuch-fcc-initialization ()
- "If notmuch-fcc-directories is set,
- hook them into the message-fcc-handler-function"
- ;; Set up the message-fcc-handler to move mails to the maildir in Fcc
- ;; The parameter is set to mark messages as "seen"
- (setq message-fcc-handler-function
- (lambda (destdir)
- (notmuch-maildir-fcc-write-buffer-to-maildir destdir t)))
- ;; add a hook to actually insert the Fcc header when sending
- (add-hook 'message-header-setup-hook 'notmuch-fcc-header-setup))
+(defun notmuch-fcc-handler (destdir)
+ "Write buffer to `destdir', marking it as sent
+
+Intended to be dynamically bound to `message-fcc-handler-function'"
+ (notmuch-maildir-fcc-write-buffer-to-maildir destdir t))
(defun notmuch-fcc-header-setup ()
"Add an Fcc header to the current message buffer.
-Can be added to `message-send-hook' and will set the Fcc header
-based on the values of `notmuch-fcc-dirs'. An existing Fcc header
-will NOT be removed or replaced."
+Sets the Fcc header based on the values of `notmuch-fcc-dirs'.
+
+Originally intended to be use a hook function, but now called directly
+by notmuch-mua-mail"
(let ((subdir
(cond
@@ -213,6 +209,5 @@ return t if successful, and nil otherwise."
(delete-file (concat destdir "/tmp/" msg-id))))
t)))
-(notmuch-fcc-initialization)
(provide 'notmuch-maildir-fcc)
diff --git a/emacs/notmuch-mua.el b/emacs/notmuch-mua.el
index 33f13998..57465b20 100644
--- a/emacs/notmuch-mua.el
+++ b/emacs/notmuch-mua.el
@@ -50,7 +50,7 @@ window/frame that will be destroyed when the buffer is killed.
You may want to customize `message-kill-buffer-on-exit'
accordingly."
(when (< emacs-major-version 24)
- " Due to a known bug in Emacs 23, you should not set
+ " Due to a known bug in Emacs 23, you should not set
this to `new-window' if `message-kill-buffer-on-exit' is
disabled: this would result in an incorrect behavior."))
:group 'notmuch-send
@@ -118,7 +118,10 @@ Note that these functions use `mail-citation-hook' if that is non-nil."
(defun notmuch-mua-user-agent-notmuch ()
"Generate a `User-Agent:' string suitable for notmuch."
- (concat "Notmuch/" (notmuch-version) " (http://notmuchmail.org)"))
+ (let ((notmuch-version (if (string= notmuch-emacs-version "unknown")
+ (notmuch-cli-version)
+ notmuch-emacs-version)))
+ (concat "Notmuch/" notmuch-version " (http://notmuchmail.org)")))
(defun notmuch-mua-user-agent-emacs ()
"Generate a `User-Agent:' string suitable for notmuch."
@@ -265,6 +268,12 @@ Note that these functions use `mail-citation-hook' if that is non-nil."
(message-goto-body)
(set-buffer-modified-p nil))
+(define-derived-mode notmuch-message-mode message-mode "Message[Notmuch]"
+ "Notmuch message composition mode. Mostly like `message-mode'")
+
+(define-key notmuch-message-mode-map (kbd "C-c C-c") #'notmuch-mua-send-and-exit)
+(define-key notmuch-message-mode-map (kbd "C-c C-s") #'notmuch-mua-send)
+
(defun notmuch-mua-mail (&optional to subject other-headers &rest other-args)
"Invoke the notmuch mail composition window.
@@ -281,6 +290,8 @@ OTHER-ARGS are passed through to `message-mail'."
(notmuch-user-name) " <" (notmuch-user-primary-email) ">")) other-headers))
(apply #'message-mail to subject other-headers other-args)
+ (notmuch-message-mode)
+ (notmuch-fcc-header-setup)
(message-sort-headers)
(message-hide-headers)
(set-buffer-modified-p nil)
@@ -394,7 +405,13 @@ will be addressed to all recipients of the source message."
(defun notmuch-mua-send-and-exit (&optional arg)
(interactive "P")
- (message-send-and-exit arg))
+ (let ((message-fcc-handler-function #'notmuch-fcc-handler))
+ (message-send-and-exit arg)))
+
+(defun notmuch-mua-send (&optional arg)
+ (interactive "P")
+ (let ((message-fcc-handler-function #'notmuch-fcc-handler))
+ (message-send arg)))
(defun notmuch-mua-kill-buffer ()
(interactive)
diff --git a/emacs/notmuch-show.el b/emacs/notmuch-show.el
index 2a53461e..49fd198a 100644
--- a/emacs/notmuch-show.el
+++ b/emacs/notmuch-show.el
@@ -47,6 +47,7 @@
(declare-function notmuch-tree "notmuch-tree"
(&optional query query-context target buffer-name open-target))
(declare-function notmuch-tree-get-message-properties "notmuch-tree" nil)
+(declare-function notmuch-read-query "notmuch" (prompt))
(defcustom notmuch-message-headers '("Subject" "To" "Cc" "Date")
"Headers that should be shown in a message, in this order.
@@ -99,7 +100,7 @@ visible for any given message."
:group 'notmuch-show
:group 'notmuch-hooks)
-(defcustom notmuch-show-max-text-part-size 10000
+(defcustom notmuch-show-max-text-part-size 100000
"Maximum size of a text part to be shown by default in characters.
Set to 0 to show the part regardless of size."
@@ -1281,6 +1282,16 @@ This includes:
")")
notmuch-show-thread-id))
+(defun notmuch-show-goto-message (msg-id)
+ "Go to message with msg-id."
+ (goto-char (point-min))
+ (unless (loop if (string= msg-id (notmuch-show-get-message-id))
+ return t
+ until (not (notmuch-show-goto-message-next)))
+ (goto-char (point-min))
+ (message "Message-id not found."))
+ (notmuch-show-message-adjust))
+
(defun notmuch-show-apply-state (state)
"Apply STATE to the current buffer.
@@ -1298,13 +1309,7 @@ This includes:
until (not (notmuch-show-goto-message-next)))
;; Go to the previously open message.
- (goto-char (point-min))
- (unless (loop if (string= current (notmuch-show-get-message-id))
- return t
- until (not (notmuch-show-goto-message-next)))
- (goto-char (point-min))
- (message "Previously current message not found."))
- (notmuch-show-message-adjust)))
+ (notmuch-show-goto-message current)))
(defun notmuch-show-refresh-view (&optional reset-state)
"Refresh the current view.
@@ -1368,6 +1373,7 @@ reset based on the original query."
(define-key map (kbd "<backtab>") 'notmuch-show-previous-button)
(define-key map (kbd "TAB") 'notmuch-show-next-button)
(define-key map "f" 'notmuch-show-forward-message)
+ (define-key map "l" 'notmuch-show-filter-thread)
(define-key map "r" 'notmuch-show-reply-sender)
(define-key map "R" 'notmuch-show-reply)
(define-key map "|" 'notmuch-show-pipe-message)
@@ -1656,6 +1662,16 @@ user decision and we should not override it."
(save-excursion
(funcall notmuch-show-mark-read-function (window-start) (window-end)))))
+(defun notmuch-show-filter-thread (query)
+ "Filter or LIMIT the current thread based on a new query string.
+
+Reshows the current thread with matches defined by the new query-string."
+ (interactive (list (notmuch-read-query "Filter thread: ")))
+ (let ((msg-id (notmuch-show-get-message-id)))
+ (setq notmuch-show-query-context (if (string= query "") nil query))
+ (notmuch-show-refresh-view t)
+ (notmuch-show-goto-message msg-id)))
+
;; Functions for getting attributes of several messages in the current
;; thread.
diff --git a/emacs/notmuch-tag.el b/emacs/notmuch-tag.el
index f54aa9d6..c7f62c90 100644
--- a/emacs/notmuch-tag.el
+++ b/emacs/notmuch-tag.el
@@ -265,7 +265,7 @@ changed (the normal case) are shown using formats from
(defcustom notmuch-before-tag-hook nil
"Hooks that are run before tags of a message are modified.
-'tags' will contain the tags that are about to be added or removed as
+'tag-changes' will contain the tags that are about to be added or removed as
a list of strings of the form \"+TAG\" or \"-TAG\".
'query' will be a string containing the search query that determines
the messages that are about to be tagged"
@@ -277,7 +277,7 @@ the messages that are about to be tagged"
(defcustom notmuch-after-tag-hook nil
"Hooks that are run after tags of a message are modified.
-'tags' will contain the tags that were added or removed as
+'tag-changes' will contain the tags that were added or removed as
a list of strings of the form \"+TAG\" or \"-TAG\".
'query' will be a string containing the search query that determines
the messages that were tagged"
diff --git a/emacs/notmuch-tree.el b/emacs/notmuch-tree.el
index 182235e7..384cb76b 100644
--- a/emacs/notmuch-tree.el
+++ b/emacs/notmuch-tree.el
@@ -240,6 +240,8 @@ FUNC."
;; Override because we want to close message pane first.
(define-key map [remap notmuch-mua-new-mail] (notmuch-tree-close-message-pane-and #'notmuch-mua-new-mail))
+ (define-key map "S" 'notmuch-search-from-tree-current-query)
+
;; these use notmuch-show functions directly
(define-key map "|" 'notmuch-show-pipe-message)
(define-key map "w" 'notmuch-show-save-attachments)
@@ -402,6 +404,12 @@ Does NOT change the database."
(notmuch-tree-close-message-window)
(notmuch-tree query)))
+(defun notmuch-search-from-tree-current-query ()
+ "Call notmuch search with the current query"
+ (interactive)
+ (notmuch-tree-close-message-window)
+ (notmuch-search (notmuch-tree-get-query)))
+
(defun notmuch-tree-message-window-kill-hook ()
"Close the message pane when exiting the show buffer."
(let ((buffer (current-buffer)))
@@ -867,6 +875,11 @@ the same as for the function notmuch-tree."
(setq notmuch-tree-query-context query-context)
(setq notmuch-tree-target-msg target)
(setq notmuch-tree-open-target open-target)
+ ;; Set the default value for `notmuch-show-process-crypto' in this
+ ;; buffer. Although we don't use this some of the functions we call
+ ;; (such as reply) do. It is a buffer local variable so setting it
+ ;; will not affect genuine show buffers.
+ (setq notmuch-show-process-crypto notmuch-crypto-process-mime)
(erase-buffer)
(goto-char (point-min))
diff --git a/emacs/notmuch.el b/emacs/notmuch.el
index ab004543..463b9262 100644
--- a/emacs/notmuch.el
+++ b/emacs/notmuch.el
@@ -61,10 +61,6 @@
(require 'notmuch-message)
(require 'notmuch-parser)
-(unless (require 'notmuch-version nil t)
- (defconst notmuch-emacs-version "unknown"
- "Placeholder variable when notmuch-version.el[c] is not available."))
-
(defcustom notmuch-search-result-format
`(("date" . "%12s ")
("count" . "%-7s ")
@@ -181,6 +177,7 @@ there will be called at other points of notmuch execution."
(defvar notmuch-search-stash-map
(let ((map (make-sparse-keymap)))
(define-key map "i" 'notmuch-search-stash-thread-id)
+ (define-key map "q" 'notmuch-stash-query)
(define-key map "?" 'notmuch-subkeymap-help)
map)
"Submap for stash commands")
@@ -191,6 +188,11 @@ there will be called at other points of notmuch execution."
(interactive)
(notmuch-common-do-stash (notmuch-search-find-thread-id)))
+(defun notmuch-stash-query ()
+ "Copy current query to kill-ring."
+ (interactive)
+ (notmuch-common-do-stash (notmuch-search-get-query)))
+
(defvar notmuch-search-query-string)
(defvar notmuch-search-target-thread)
(defvar notmuch-search-target-line)
@@ -855,13 +857,15 @@ See `notmuch-tag' for information on the format of TAG-CHANGES."
"Read a notmuch-query from the minibuffer with completion.
PROMPT is the string to prompt with."
- (lexical-let
- ((completions
- (append (list "folder:" "path:" "thread:" "id:" "date:" "from:" "to:"
- "subject:" "attachment:" "mimetype:")
- (mapcar (lambda (tag)
- (concat "tag:" (notmuch-escape-boolean-term tag)))
- (process-lines notmuch-command "search" "--output=tags" "*")))))
+ (lexical-let*
+ ((all-tags
+ (mapcar (lambda (tag) (notmuch-escape-boolean-term tag))
+ (process-lines notmuch-command "search" "--output=tags" "*")))
+ (completions
+ (append (list "folder:" "path:" "thread:" "id:" "date:" "from:" "to:"
+ "subject:" "attachment:" "mimetype:")
+ (mapcar (lambda (tag) (concat "tag:" tag)) all-tags)
+ (mapcar (lambda (tag) (concat "is:" tag)) all-tags))))
(let ((keymap (copy-keymap minibuffer-local-map))
(current-query (case major-mode
(notmuch-search-mode (notmuch-search-get-query))
@@ -974,18 +978,28 @@ default sort order is defined by `notmuch-search-oldest-first'."
(set 'notmuch-search-oldest-first (not notmuch-search-oldest-first))
(notmuch-search-refresh-view))
+(defun notmuch-group-disjunctive-query-string (query-string)
+ "Group query if it contains a complex expression.
+
+Enclose QUERY-STRING in parentheses if it matches
+`notmuch-search-disjunctive-regexp'."
+ (if (string-match-p notmuch-search-disjunctive-regexp query-string)
+ (concat "( " query-string " )")
+ query-string))
+
(defun notmuch-search-filter (query)
"Filter the current search results based on an additional query string.
Runs a new search matching only messages that match both the
current search results AND the additional query string provided."
(interactive (list (notmuch-read-query "Filter search: ")))
- (let ((grouped-query (if (string-match-p notmuch-search-disjunctive-regexp query)
- (concat "( " query " )")
- query)))
- (notmuch-search (if (string= notmuch-search-query-string "*")
+ (let ((grouped-query (notmuch-group-disjunctive-query-string query))
+ (grouped-original-query (notmuch-group-disjunctive-query-string
+ notmuch-search-query-string)))
+ (notmuch-search (if (string= grouped-original-query "*")
grouped-query
- (concat notmuch-search-query-string " and " grouped-query)) notmuch-search-oldest-first)))
+ (concat grouped-original-query " and " grouped-query))
+ notmuch-search-oldest-first)))
(defun notmuch-search-filter-by-tag (tag)
"Filter the current search results based on a single tag.
diff --git a/lib/Makefile.local b/lib/Makefile.local
index f9ecd50e..3a070907 100644
--- a/lib/Makefile.local
+++ b/lib/Makefile.local
@@ -1,26 +1,5 @@
# -*- makefile -*-
-# The major version of the library interface. This will control the soname.
-# As such, this number must be incremented for any incompatible change to
-# the library interface, (such as the deletion of an API or a major
-# semantic change that breaks formerly functioning code).
-#
-LIBNOTMUCH_VERSION_MAJOR = 4
-
-# The minor version of the library interface. This should be incremented at
-# the time of release for any additions to the library interface,
-# (and when it is incremented, the release version of the library should
-# be reset to 0).
-LIBNOTMUCH_VERSION_MINOR = 2
-
-# The release version the library interface. This should be incremented at
-# the time of release if there have been no changes to the interface, (but
-# simply compatible changes to the implementation).
-LIBNOTMUCH_VERSION_RELEASE = 0
-
-# Note: Don't forget to change the VERSION macros in notmuch.h when
-# any of the above change.
-
ifeq ($(PLATFORM),MACOSX)
LIBRARY_SUFFIX = dylib
# On OS X, library version numbers go before suffix.
@@ -33,7 +12,7 @@ LIBRARY_SUFFIX = so
LINKER_NAME = libnotmuch.$(LIBRARY_SUFFIX)
SONAME = $(LINKER_NAME).$(LIBNOTMUCH_VERSION_MAJOR)
LIBNAME = $(SONAME).$(LIBNOTMUCH_VERSION_MINOR).$(LIBNOTMUCH_VERSION_RELEASE)
-LIBRARY_LINK_FLAG = -shared -Wl,--version-script=notmuch.sym,-soname=$(SONAME) -Wl,--no-undefined
+LIBRARY_LINK_FLAG = -shared -Wl,--version-script=notmuch.sym,-soname=$(SONAME) $(NO_UNDEFINED_LDFLAGS)
ifeq ($(PLATFORM),OPENBSD)
LIBRARY_LINK_FLAG += -lc
endif
diff --git a/lib/database-private.h b/lib/database-private.h
index 24243db2..3fb10f7a 100644
--- a/lib/database-private.h
+++ b/lib/database-private.h
@@ -100,6 +100,12 @@ enum _notmuch_features {
*
* Introduced: version 3. */
NOTMUCH_FEATURE_INDEXED_MIMETYPES = 1 << 5,
+
+ /* If set, messages store the revision number of the last
+ * modification in NOTMUCH_VALUE_LAST_MOD.
+ *
+ * Introduced: version 3. */
+ NOTMUCH_FEATURE_LAST_MOD = 1 << 6,
};
/* In C++, a named enum is its own type, so define bitwise operators
@@ -145,6 +151,8 @@ struct _notmuch_database {
notmuch_database_mode_t mode;
int atomic_nesting;
+ /* TRUE if changes have been made in this atomic section */
+ notmuch_bool_t atomic_dirty;
Xapian::Database *xapian_db;
/* Bit mask of features used by this database. This is a
@@ -158,10 +166,17 @@ struct _notmuch_database {
* next library call. May be NULL */
char *status_string;
+ /* Highest committed revision number. Modifications are recorded
+ * under a higher revision number, which can be generated with
+ * notmuch_database_new_revision. */
+ unsigned long revision;
+ const char *uuid;
+
Xapian::QueryParser *query_parser;
Xapian::TermGenerator *term_gen;
Xapian::ValueRangeProcessor *value_range_processor;
Xapian::ValueRangeProcessor *date_range_processor;
+ Xapian::ValueRangeProcessor *last_mod_range_processor;
};
/* Prior to database version 3, features were implied by the database
@@ -179,7 +194,8 @@ struct _notmuch_database {
* will have it). */
#define NOTMUCH_FEATURES_CURRENT \
(NOTMUCH_FEATURE_FILE_TERMS | NOTMUCH_FEATURE_DIRECTORY_DOCS | \
- NOTMUCH_FEATURE_BOOL_FOLDER | NOTMUCH_FEATURE_GHOSTS)
+ NOTMUCH_FEATURE_BOOL_FOLDER | NOTMUCH_FEATURE_GHOSTS | \
+ NOTMUCH_FEATURE_LAST_MOD)
/* Return the list of terms from the given iterator matching a prefix.
* The prefix will be stripped from the strings in the returned list.
diff --git a/lib/database.cc b/lib/database.cc
index cffab62c..5e86955d 100644
--- a/lib/database.cc
+++ b/lib/database.cc
@@ -101,6 +101,9 @@ typedef struct {
*
* SUBJECT: The value of the "Subject" header
*
+ * LAST_MOD: The revision number as of the last tag or
+ * filename change.
+ *
* In addition, terms from the content of the message are added with
* "from", "to", "attachment", and "subject" prefixes for use by the
* user in searching. Similarly, terms from the path of the mail
@@ -310,6 +313,8 @@ static const struct {
* them. */
{ NOTMUCH_FEATURE_INDEXED_MIMETYPES,
"indexed MIME types", "w"},
+ { NOTMUCH_FEATURE_LAST_MOD,
+ "modification tracking", "w"},
};
const char *
@@ -342,6 +347,8 @@ notmuch_status_to_string (notmuch_status_t status)
return "Unsupported operation";
case NOTMUCH_STATUS_UPGRADE_REQUIRED:
return "Operation requires a database upgrade";
+ case NOTMUCH_STATUS_PATH_ERROR:
+ return "Path supplied is illegal for this function";
default:
case NOTMUCH_STATUS_LAST_STATUS:
return "Unknown error status value";
@@ -657,6 +664,12 @@ notmuch_database_create_verbose (const char *path,
goto DONE;
}
+ if (path[0] != '/') {
+ message = strdup ("Error: Database path must be absolute.\n");
+ status = NOTMUCH_STATUS_PATH_ERROR;
+ goto DONE;
+ }
+
err = stat (path, &st);
if (err) {
IGNORE_RESULT (asprintf (&message, "Error: Cannot create database at %s: %s.\n",
@@ -729,6 +742,23 @@ _notmuch_database_ensure_writable (notmuch_database_t *notmuch)
return NOTMUCH_STATUS_SUCCESS;
}
+/* Allocate a revision number for the next change. */
+unsigned long
+_notmuch_database_new_revision (notmuch_database_t *notmuch)
+{
+ unsigned long new_revision = notmuch->revision + 1;
+
+ /* If we're in an atomic section, hold off on updating the
+ * committed revision number until we commit the atomic section.
+ */
+ if (notmuch->atomic_nesting)
+ notmuch->atomic_dirty = TRUE;
+ else
+ notmuch->revision = new_revision;
+
+ return new_revision;
+}
+
/* Parse a database features string from the given database version.
* Returns the feature bit set.
*
@@ -847,6 +877,12 @@ notmuch_database_open_verbose (const char *path,
goto DONE;
}
+ if (path[0] != '/') {
+ message = strdup ("Error: Database path must be absolute.\n");
+ status = NOTMUCH_STATUS_PATH_ERROR;
+ goto DONE;
+ }
+
if (! (notmuch_path = talloc_asprintf (local, "%s/%s", path, ".notmuch"))) {
message = strdup ("Out of memory\n");
status = NOTMUCH_STATUS_OUT_OF_MEMORY;
@@ -890,6 +926,7 @@ notmuch_database_open_verbose (const char *path,
notmuch->atomic_nesting = 0;
try {
string last_thread_id;
+ string last_mod;
if (mode == NOTMUCH_DATABASE_MODE_READ_WRITE) {
notmuch->xapian_db = new Xapian::WritableDatabase (xapian_path,
@@ -948,11 +985,22 @@ notmuch_database_open_verbose (const char *path,
INTERNAL_ERROR ("Malformed database last_thread_id: %s", str);
}
+ /* Get current highest revision number. */
+ last_mod = notmuch->xapian_db->get_value_upper_bound (
+ NOTMUCH_VALUE_LAST_MOD);
+ if (last_mod.empty ())
+ notmuch->revision = 0;
+ else
+ notmuch->revision = Xapian::sortable_unserialise (last_mod);
+ notmuch->uuid = talloc_strdup (
+ notmuch, notmuch->xapian_db->get_uuid ().c_str ());
+
notmuch->query_parser = new Xapian::QueryParser;
notmuch->term_gen = new Xapian::TermGenerator;
notmuch->term_gen->set_stemmer (Xapian::Stem ("english"));
notmuch->value_range_processor = new Xapian::NumberValueRangeProcessor (NOTMUCH_VALUE_TIMESTAMP);
notmuch->date_range_processor = new ParseTimeValueRangeProcessor (NOTMUCH_VALUE_TIMESTAMP);
+ notmuch->last_mod_range_processor = new Xapian::NumberValueRangeProcessor (NOTMUCH_VALUE_LAST_MOD, "lastmod:");
notmuch->query_parser->set_default_op (Xapian::Query::OP_AND);
notmuch->query_parser->set_database (*notmuch->xapian_db);
@@ -960,6 +1008,7 @@ notmuch_database_open_verbose (const char *path,
notmuch->query_parser->set_stemming_strategy (Xapian::QueryParser::STEM_SOME);
notmuch->query_parser->add_valuerangeprocessor (notmuch->value_range_processor);
notmuch->query_parser->add_valuerangeprocessor (notmuch->date_range_processor);
+ notmuch->query_parser->add_valuerangeprocessor (notmuch->last_mod_range_processor);
for (i = 0; i < ARRAY_SIZE (BOOLEAN_PREFIX_EXTERNAL); i++) {
prefix_t *prefix = &BOOLEAN_PREFIX_EXTERNAL[i];
@@ -1038,6 +1087,8 @@ notmuch_database_close (notmuch_database_t *notmuch)
notmuch->value_range_processor = NULL;
delete notmuch->date_range_processor;
notmuch->date_range_processor = NULL;
+ delete notmuch->last_mod_range_processor;
+ notmuch->last_mod_range_processor = NULL;
return status;
}
@@ -1321,6 +1372,7 @@ notmuch_database_upgrade (notmuch_database_t *notmuch,
enum _notmuch_features target_features, new_features;
notmuch_status_t status;
notmuch_private_status_t private_status;
+ notmuch_query_t *query = NULL;
unsigned int count = 0, total = 0;
status = _notmuch_database_ensure_writable (notmuch);
@@ -1336,7 +1388,7 @@ notmuch_database_upgrade (notmuch_database_t *notmuch,
return NOTMUCH_STATUS_SUCCESS;
if (progress_notify) {
- /* Setup our handler for SIGALRM */
+ /* Set up our handler for SIGALRM */
memset (&action, 0, sizeof (struct sigaction));
action.sa_handler = handle_sigalrm;
sigemptyset (&action.sa_mask);
@@ -1355,10 +1407,18 @@ notmuch_database_upgrade (notmuch_database_t *notmuch,
/* Figure out how much total work we need to do. */
if (new_features &
- (NOTMUCH_FEATURE_FILE_TERMS | NOTMUCH_FEATURE_BOOL_FOLDER)) {
- notmuch_query_t *query = notmuch_query_create (notmuch, "");
- total += notmuch_query_count_messages (query);
+ (NOTMUCH_FEATURE_FILE_TERMS | NOTMUCH_FEATURE_BOOL_FOLDER |
+ NOTMUCH_FEATURE_LAST_MOD)) {
+ query = notmuch_query_create (notmuch, "");
+ unsigned msg_count;
+
+ status = notmuch_query_count_messages_st (query, &msg_count);
+ if (status)
+ goto DONE;
+
+ total += msg_count;
notmuch_query_destroy (query);
+ query = NULL;
}
if (new_features & NOTMUCH_FEATURE_DIRECTORY_DOCS) {
t_end = db->allterms_end ("XTIMESTAMP");
@@ -1382,13 +1442,18 @@ notmuch_database_upgrade (notmuch_database_t *notmuch,
/* Perform per-message upgrades. */
if (new_features &
- (NOTMUCH_FEATURE_FILE_TERMS | NOTMUCH_FEATURE_BOOL_FOLDER)) {
- notmuch_query_t *query = notmuch_query_create (notmuch, "");
+ (NOTMUCH_FEATURE_FILE_TERMS | NOTMUCH_FEATURE_BOOL_FOLDER |
+ NOTMUCH_FEATURE_LAST_MOD)) {
notmuch_messages_t *messages;
notmuch_message_t *message;
char *filename;
- for (messages = notmuch_query_search_messages (query);
+ query = notmuch_query_create (notmuch, "");
+
+ status = notmuch_query_search_messages_st (query, &messages);
+ if (status)
+ goto DONE;
+ for (;
notmuch_messages_valid (messages);
notmuch_messages_move_to_next (messages))
{
@@ -1419,6 +1484,14 @@ notmuch_database_upgrade (notmuch_database_t *notmuch,
if (new_features & NOTMUCH_FEATURE_BOOL_FOLDER)
_notmuch_message_upgrade_folder (message);
+ /* Prior to NOTMUCH_FEATURE_LAST_MOD, messages did not
+ * track modification revisions. Give all messages the
+ * next available revision; since we just started tracking
+ * revisions for this database, that will be 1.
+ */
+ if (new_features & NOTMUCH_FEATURE_LAST_MOD)
+ _notmuch_message_upgrade_last_mod (message);
+
_notmuch_message_sync (message);
notmuch_message_destroy (message);
@@ -1427,6 +1500,7 @@ notmuch_database_upgrade (notmuch_database_t *notmuch,
}
notmuch_query_destroy (query);
+ query = NULL;
}
/* Perform per-directory upgrades. */
@@ -1547,6 +1621,9 @@ notmuch_database_upgrade (notmuch_database_t *notmuch,
sigaction (SIGALRM, &action, NULL);
}
+ if (query)
+ notmuch_query_destroy (query);
+
talloc_free (local);
return status;
}
@@ -1601,11 +1678,25 @@ notmuch_database_end_atomic (notmuch_database_t *notmuch)
return NOTMUCH_STATUS_XAPIAN_EXCEPTION;
}
+ if (notmuch->atomic_dirty) {
+ ++notmuch->revision;
+ notmuch->atomic_dirty = FALSE;
+ }
+
DONE:
notmuch->atomic_nesting--;
return NOTMUCH_STATUS_SUCCESS;
}
+unsigned long
+notmuch_database_get_revision (notmuch_database_t *notmuch,
+ const char **uuid)
+{
+ if (uuid)
+ *uuid = notmuch->uuid;
+ return notmuch->revision;
+}
+
/* We allow the user to use arbitrarily long paths for directories. But
* we have a term-length limit. So if we exceed that, we'll use the
* SHA-1 of the path for the database term.
@@ -2576,7 +2667,7 @@ notmuch_database_get_all_tags (notmuch_database_t *db)
}
const char *
-notmuch_database_status_string (notmuch_database_t *notmuch)
+notmuch_database_status_string (const notmuch_database_t *notmuch)
{
return notmuch->status_string;
}
diff --git a/lib/directory.cc b/lib/directory.cc
index b836ea28..78637b3a 100644
--- a/lib/directory.cc
+++ b/lib/directory.cc
@@ -281,6 +281,31 @@ notmuch_directory_get_child_directories (notmuch_directory_t *directory)
return child_directories;
}
+notmuch_status_t
+notmuch_directory_delete (notmuch_directory_t *directory)
+{
+ notmuch_status_t status;
+ Xapian::WritableDatabase *db;
+
+ status = _notmuch_database_ensure_writable (directory->notmuch);
+ if (status)
+ return status;
+
+ try {
+ db = static_cast <Xapian::WritableDatabase *> (directory->notmuch->xapian_db);
+ db->delete_document (directory->document_id);
+ } catch (const Xapian::Error &error) {
+ _notmuch_database_log (directory->notmuch,
+ "A Xapian exception occurred deleting directory entry: %s.\n",
+ error.get_msg().c_str());
+ directory->notmuch->exception_reported = TRUE;
+ status = NOTMUCH_STATUS_XAPIAN_EXCEPTION;
+ }
+ notmuch_directory_destroy (directory);
+
+ return NOTMUCH_STATUS_SUCCESS;
+}
+
void
notmuch_directory_destroy (notmuch_directory_t *directory)
{
diff --git a/lib/gen-version-script.sh b/lib/gen-version-script.sh
index 64a73749..84770011 100644
--- a/lib/gen-version-script.sh
+++ b/lib/gen-version-script.sh
@@ -1,3 +1,4 @@
+set -eu
# we go through a bit of work to get the unmangled names of the
# typeinfo symbols because of
diff --git a/lib/message-file.c b/lib/message-file.c
index 8ac96e8e..ee305202 100644
--- a/lib/message-file.c
+++ b/lib/message-file.c
@@ -38,27 +38,6 @@ struct _notmuch_message_file {
};
static int
-strcase_equal (const void *a, const void *b)
-{
- return strcasecmp (a, b) == 0;
-}
-
-static unsigned int
-strcase_hash (const void *ptr)
-{
- const char *s = ptr;
-
- /* This is the djb2 hash. */
- unsigned int hash = 5381;
- while (s && *s) {
- hash = ((hash << 5) + hash) + tolower (*s);
- s++;
- }
-
- return hash;
-}
-
-static int
_notmuch_message_file_destructor (notmuch_message_file_t *message)
{
if (message->headers)
diff --git a/lib/message.cc b/lib/message.cc
index 5bc7aff1..26b5e76e 100644
--- a/lib/message.cc
+++ b/lib/message.cc
@@ -43,6 +43,9 @@ struct visible _notmuch_message {
* if each flag has been initialized. */
unsigned long lazy_flags;
+ /* Message document modified since last sync */
+ notmuch_bool_t modified;
+
Xapian::Document doc;
Xapian::termcount termpos;
};
@@ -539,6 +542,7 @@ _notmuch_message_remove_terms (notmuch_message_t *message, const char *prefix)
try {
message->doc.remove_term ((*i));
+ message->modified = TRUE;
} catch (const Xapian::InvalidArgumentError) {
/* Ignore failure to remove non-existent term. */
}
@@ -793,6 +797,7 @@ void
_notmuch_message_clear_data (notmuch_message_t *message)
{
message->doc.set_data ("");
+ message->modified = TRUE;
}
static void
@@ -990,6 +995,17 @@ _notmuch_message_set_header_values (notmuch_message_t *message,
Xapian::sortable_serialise (time_value));
message->doc.add_value (NOTMUCH_VALUE_FROM, from);
message->doc.add_value (NOTMUCH_VALUE_SUBJECT, subject);
+ message->modified = TRUE;
+}
+
+/* Upgrade a message to support NOTMUCH_FEATURE_LAST_MOD. The caller
+ * must call _notmuch_message_sync. */
+void
+_notmuch_message_upgrade_last_mod (notmuch_message_t *message)
+{
+ /* _notmuch_message_sync will update the last modification
+ * revision; we just have to ask it to. */
+ message->modified = TRUE;
}
/* Synchronize changes made to message->doc out into the database. */
@@ -1001,8 +1017,24 @@ _notmuch_message_sync (notmuch_message_t *message)
if (message->notmuch->mode == NOTMUCH_DATABASE_MODE_READ_ONLY)
return;
+ if (! message->modified)
+ return;
+
+ /* Update the last modification of this message. */
+ if (message->notmuch->features & NOTMUCH_FEATURE_LAST_MOD)
+ /* sortable_serialise gives a reasonably compact encoding,
+ * which directly translates to reduced IO when scanning the
+ * value stream. Since it's built for doubles, we only get 53
+ * effective bits, but that's still enough for the database to
+ * last a few centuries at 1 million revisions per second. */
+ message->doc.add_value (NOTMUCH_VALUE_LAST_MOD,
+ Xapian::sortable_serialise (
+ _notmuch_database_new_revision (
+ message->notmuch)));
+
db = static_cast <Xapian::WritableDatabase *> (message->notmuch->xapian_db);
db->replace_document (message->doc_id, message->doc);
+ message->modified = FALSE;
}
/* Delete a message document from the database. */
@@ -1077,6 +1109,7 @@ _notmuch_message_add_term (notmuch_message_t *message,
return NOTMUCH_PRIVATE_STATUS_TERM_TOO_LONG;
message->doc.add_term (term, 0);
+ message->modified = TRUE;
talloc_free (term);
@@ -1145,6 +1178,7 @@ _notmuch_message_remove_term (notmuch_message_t *message,
try {
message->doc.remove_term (term);
+ message->modified = TRUE;
} catch (const Xapian::InvalidArgumentError) {
/* We'll let the philosopher's try to wrestle with the
* question of whether failing to remove that which was not
diff --git a/lib/notmuch-private.h b/lib/notmuch-private.h
index cc9ce12c..5dd4770e 100644
--- a/lib/notmuch-private.h
+++ b/lib/notmuch-private.h
@@ -50,6 +50,7 @@ NOTMUCH_BEGIN_DECLS
#include "xutil.h"
#include "error_util.h"
+#include "string-util.h"
#pragma GCC visibility push(hidden)
@@ -107,7 +108,8 @@ typedef enum {
NOTMUCH_VALUE_TIMESTAMP = 0,
NOTMUCH_VALUE_MESSAGE_ID,
NOTMUCH_VALUE_FROM,
- NOTMUCH_VALUE_SUBJECT
+ NOTMUCH_VALUE_SUBJECT,
+ NOTMUCH_VALUE_LAST_MOD,
} notmuch_value_t;
/* Xapian (with flint backend) complains if we provide a term longer
@@ -194,6 +196,9 @@ void
_notmuch_database_log (notmuch_database_t *notmuch,
const char *format, ...);
+unsigned long
+_notmuch_database_new_revision (notmuch_database_t *notmuch);
+
const char *
_notmuch_database_relative_path (notmuch_database_t *notmuch,
const char *path);
@@ -305,6 +310,10 @@ _notmuch_message_set_header_values (notmuch_message_t *message,
const char *date,
const char *from,
const char *subject);
+
+void
+_notmuch_message_upgrade_last_mod (notmuch_message_t *message);
+
void
_notmuch_message_sync (notmuch_message_t *message);
diff --git a/lib/notmuch.h b/lib/notmuch.h
index 20c4e019..85b56bf1 100644
--- a/lib/notmuch.h
+++ b/lib/notmuch.h
@@ -56,9 +56,11 @@ NOTMUCH_BEGIN_DECLS
* version in Makefile.local.
*/
#define LIBNOTMUCH_MAJOR_VERSION 4
-#define LIBNOTMUCH_MINOR_VERSION 2
+#define LIBNOTMUCH_MINOR_VERSION 3
#define LIBNOTMUCH_MICRO_VERSION 0
+#define NOTMUCH_DEPRECATED(major,minor) \
+ __attribute__ ((deprecated ("function deprecated as of libnotmuch " #major "." #minor)))
#endif /* __DOXYGEN__ */
/**
@@ -164,6 +166,11 @@ typedef enum _notmuch_status {
*/
NOTMUCH_STATUS_UPGRADE_REQUIRED,
/**
+ * There is a problem with the proposed path, e.g. a relative path
+ * passed to a function expecting an absolute path.
+ */
+ NOTMUCH_STATUS_PATH_ERROR,
+ /**
* Not an actual status value. Just a way to find out how many
* valid status values there are.
*/
@@ -306,7 +313,7 @@ notmuch_database_open_verbose (const char *path,
*
*/
const char *
-notmuch_database_status_string (notmuch_database_t *notmuch);
+notmuch_database_status_string (const notmuch_database_t *notmuch);
/**
* Commit changes and close the given notmuch database.
@@ -461,6 +468,24 @@ notmuch_status_t
notmuch_database_end_atomic (notmuch_database_t *notmuch);
/**
+ * Return the committed database revision and UUID.
+ *
+ * The database revision number increases monotonically with each
+ * commit to the database. Hence, all messages and message changes
+ * committed to the database (that is, visible to readers) have a last
+ * modification revision <= the committed database revision. Any
+ * messages committed in the future will be assigned a modification
+ * revision > the committed database revision.
+ *
+ * The UUID is a NUL-terminated opaque string that uniquely identifies
+ * this database. Two revision numbers are only comparable if they
+ * have the same database UUID.
+ */
+unsigned long
+notmuch_database_get_revision (notmuch_database_t *notmuch,
+ const char **uuid);
+
+/**
* Retrieve a directory object from the database for 'path'.
*
* Here, 'path' should be a path relative to the path of 'database'
@@ -703,7 +728,13 @@ typedef enum {
* Return the query_string of this query. See notmuch_query_create.
*/
const char *
-notmuch_query_get_query_string (notmuch_query_t *query);
+notmuch_query_get_query_string (const notmuch_query_t *query);
+
+/**
+ * Return the notmuch database of this query. See notmuch_query_create.
+ */
+notmuch_database_t *
+notmuch_query_get_database (const notmuch_query_t *query);
/**
* Exclude values for notmuch_query_set_omit_excluded. The strange
@@ -760,7 +791,7 @@ notmuch_query_set_sort (notmuch_query_t *query, notmuch_sort_t sort);
* notmuch_query_set_sort.
*/
notmuch_sort_t
-notmuch_query_get_sort (notmuch_query_t *query);
+notmuch_query_get_sort (const notmuch_query_t *query);
/**
* Add a tag that will be excluded from the query results by default.
@@ -807,20 +838,26 @@ notmuch_query_add_tag_exclude (notmuch_query_t *query, const char *tag);
* notmuch_threads_destroy function, but there's no good reason
* to call it if the query is about to be destroyed).
*
- * If a Xapian exception occurs this function will return NULL.
- * For better error reporting, use the _st variant.
- */
-notmuch_threads_t *
-notmuch_query_search_threads (notmuch_query_t *query);
-
-/**
- * Like notmuch_query_search_threads, but with a status return.
+ * @since libnotmuch 4.2 (notmuch 0.20)
*/
notmuch_status_t
notmuch_query_search_threads_st (notmuch_query_t *query,
notmuch_threads_t **out);
/**
+ * Like notmuch_query_search_threads_st, but without a status return.
+ *
+ * If a Xapian exception occurs this function will return NULL.
+ *
+ * @deprecated Deprecated as of libnotmuch 4.3 (notmuch 0.21). Please
+ * use notmuch_query_search_threads_st instead.
+ *
+ */
+NOTMUCH_DEPRECATED(4,3)
+notmuch_threads_t *
+notmuch_query_search_threads (notmuch_query_t *query);
+
+/**
* Execute a query for messages, returning a notmuch_messages_t object
* which can be used to iterate over the results. The returned
* messages object is owned by the query and as such, will only be
@@ -858,17 +895,24 @@ notmuch_query_search_threads_st (notmuch_query_t *query,
* reason to call it if the query is about to be destroyed).
*
* If a Xapian exception occurs this function will return NULL.
- * For better error reporting, use the _st variant.
- */
-notmuch_messages_t *
-notmuch_query_search_messages (notmuch_query_t *query);
-
-/**
- * Like notmuch_query_search_messages, but with a status return.
+ *
+ * @since libnotmuch 4.2 (notmuch 0.20)
*/
notmuch_status_t
notmuch_query_search_messages_st (notmuch_query_t *query,
notmuch_messages_t **out);
+/**
+ * Like notmuch_query_search_messages, but without a status return.
+ *
+ * If a Xapian exception occurs this function will return NULL.
+ *
+ * @deprecated Deprecated as of libnotmuch 4.3 (notmuch 0.21). Please use
+ * notmuch_query_search_messages_st instead.
+ *
+ */
+NOTMUCH_DEPRECATED(4,3)
+notmuch_messages_t *
+notmuch_query_search_messages (notmuch_query_t *query);
/**
* Destroy a notmuch_query_t along with any associated resources.
@@ -942,10 +986,28 @@ notmuch_threads_destroy (notmuch_threads_t *threads);
* This function performs a search and returns the number of matching
* messages.
*
- * If a Xapian exception occurs, this function may return 0 (after
- * printing a message).
+ * @returns
+ *
+ * NOTMUCH_STATUS_SUCCESS: query completed successfully.
+ *
+ * NOTMUCH_STATUS_XAPIAN_EXCEPTION: a Xapian exception occured. The
+ * value of *count is not defined.
+ *
+ * @since libnotmuch 4.3 (notmuch 0.21)
+ */
+notmuch_status_t
+notmuch_query_count_messages_st (notmuch_query_t *query, unsigned int *count);
+
+/**
+ * like notmuch_query_count_messages_st, but without a status return.
+ *
+ * May return 0 in the case of errors.
+ *
+ * @deprecated Deprecated since libnotmuch 4.3 (notmuch 0.21). Please
+ * use notmuch_query_count_messages_st instead.
*/
-unsigned
+NOTMUCH_DEPRECATED(4,3)
+unsigned int
notmuch_query_count_messages (notmuch_query_t *query);
/**
@@ -956,11 +1018,33 @@ notmuch_query_count_messages (notmuch_query_t *query);
* search.
*
* Note that this is a significantly heavier operation than
- * notmuch_query_count_messages().
+ * notmuch_query_count_messages{_st}().
*
- * If an error occurs, this function may return 0.
+ * @returns
+ *
+ * NOTMUCH_STATUS_OUT_OF_MEMORY: Memory allocation failed. The value
+ * of *count is not defined
+
+ * NOTMUCH_STATUS_SUCCESS: query completed successfully.
+ *
+ * NOTMUCH_STATUS_XAPIAN_EXCEPTION: a Xapian exception occured. The
+ * value of *count is not defined.
+ *
+ * @since libnotmuch 4.3 (notmuch 0.21)
+ */
+notmuch_status_t
+notmuch_query_count_threads_st (notmuch_query_t *query, unsigned *count);
+
+/**
+ * like notmuch_query_count_threads, but without a status return.
+ *
+ * May return 0 in case of errors.
+ *
+ * @deprecated Deprecated as of libnotmuch 4.3 (notmuch 0.21). Please
+ * use notmuch_query_count_threads_st instead.
*/
-unsigned
+NOTMUCH_DEPRECATED(4,3)
+unsigned int
notmuch_query_count_threads (notmuch_query_t *query);
/**
@@ -1678,6 +1762,16 @@ notmuch_filenames_t *
notmuch_directory_get_child_directories (notmuch_directory_t *directory);
/**
+ * Delete directory document from the database, and destroy the
+ * notmuch_directory_t object. Assumes any child directories and files
+ * have been deleted by the caller.
+ *
+ * @since libnotmuch 4.3 (notmuch 0.21)
+ */
+notmuch_status_t
+notmuch_directory_delete (notmuch_directory_t *directory);
+
+/**
* Destroy a notmuch_directory_t object.
*/
void
diff --git a/lib/parse-time-vrp.cc b/lib/parse-time-vrp.cc
index 33f07db3..03804cf5 100644
--- a/lib/parse-time-vrp.cc
+++ b/lib/parse-time-vrp.cc
@@ -31,6 +31,7 @@ Xapian::valueno
ParseTimeValueRangeProcessor::operator() (std::string &begin, std::string &end)
{
time_t t, now;
+ std::string b;
/* Require date: prefix in start of the range... */
if (STRNCMP_LITERAL (begin.c_str (), PREFIX))
@@ -38,6 +39,7 @@ ParseTimeValueRangeProcessor::operator() (std::string &begin, std::string &end)
/* ...and remove it. */
begin.erase (0, sizeof (PREFIX) - 1);
+ b = begin;
/* Use the same 'now' for begin and end. */
if (time (&now) == (time_t) -1)
@@ -51,6 +53,9 @@ ParseTimeValueRangeProcessor::operator() (std::string &begin, std::string &end)
}
if (!end.empty ()) {
+ if (end == "!" && ! b.empty ())
+ end = b;
+
if (parse_time_string (end.c_str (), &t, &now, PARSE_TIME_ROUND_UP_INCLUSIVE))
return Xapian::BAD_VALUENO;
diff --git a/lib/query.cc b/lib/query.cc
index 9cedb6a8..e627bfc2 100644
--- a/lib/query.cc
+++ b/lib/query.cc
@@ -98,7 +98,7 @@ notmuch_query_create (notmuch_database_t *notmuch,
}
const char *
-notmuch_query_get_query_string (notmuch_query_t *query)
+notmuch_query_get_query_string (const notmuch_query_t *query)
{
return query->query_string;
}
@@ -117,7 +117,7 @@ notmuch_query_set_sort (notmuch_query_t *query, notmuch_sort_t sort)
}
notmuch_sort_t
-notmuch_query_get_sort (notmuch_query_t *query)
+notmuch_query_get_sort (const notmuch_query_t *query)
{
return query->sort;
}
@@ -541,9 +541,19 @@ notmuch_threads_destroy (notmuch_threads_t *threads)
talloc_free (threads);
}
-unsigned
+unsigned int
notmuch_query_count_messages (notmuch_query_t *query)
{
+ notmuch_status_t status;
+ unsigned int count;
+
+ status = notmuch_query_count_messages_st (query, &count);
+ return status ? 0 : count;
+}
+
+notmuch_status_t
+notmuch_query_count_messages_st (notmuch_query_t *query, unsigned *count_out)
+{
notmuch_database_t *notmuch = query->notmuch;
const char *query_string = query->query_string;
Xapian::doccount count = 0;
@@ -605,31 +615,44 @@ notmuch_query_count_messages (notmuch_query_t *query)
"Query string was: %s\n",
error.get_msg().c_str(),
query->query_string);
-
+ return NOTMUCH_STATUS_XAPIAN_EXCEPTION;
}
- return count;
+ *count_out = count;
+ return NOTMUCH_STATUS_SUCCESS;
}
unsigned
notmuch_query_count_threads (notmuch_query_t *query)
{
+ notmuch_status_t status;
+ unsigned int count;
+
+ status = notmuch_query_count_threads_st (query, &count);
+ return status ? 0 : count;
+}
+
+notmuch_status_t
+notmuch_query_count_threads_st (notmuch_query_t *query, unsigned *count)
+{
notmuch_messages_t *messages;
GHashTable *hash;
- unsigned int count;
notmuch_sort_t sort;
+ notmuch_status_t ret = NOTMUCH_STATUS_SUCCESS;
sort = query->sort;
query->sort = NOTMUCH_SORT_UNSORTED;
- messages = notmuch_query_search_messages (query);
+ ret = notmuch_query_search_messages_st (query, &messages);
+ if (ret)
+ return ret;
query->sort = sort;
if (messages == NULL)
- return 0;
+ return NOTMUCH_STATUS_XAPIAN_EXCEPTION;
hash = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, NULL);
if (hash == NULL) {
talloc_free (messages);
- return 0;
+ return NOTMUCH_STATUS_OUT_OF_MEMORY;
}
while (notmuch_messages_valid (messages)) {
@@ -638,7 +661,7 @@ notmuch_query_count_threads (notmuch_query_t *query)
char *thread_id_copy = talloc_strdup (messages, thread_id);
if (unlikely (thread_id_copy == NULL)) {
notmuch_message_destroy (message);
- count = 0;
+ ret = NOTMUCH_STATUS_OUT_OF_MEMORY;
goto DONE;
}
g_hash_table_insert (hash, thread_id_copy, NULL);
@@ -646,11 +669,17 @@ notmuch_query_count_threads (notmuch_query_t *query)
notmuch_messages_move_to_next (messages);
}
- count = g_hash_table_size (hash);
+ *count = g_hash_table_size (hash);
DONE:
g_hash_table_unref (hash);
talloc_free (messages);
- return count;
+ return ret;
+}
+
+notmuch_database_t *
+notmuch_query_get_database (const notmuch_query_t *query)
+{
+ return query->notmuch;
}
diff --git a/lib/thread.cc b/lib/thread.cc
index 9847cf8b..0c937d76 100644
--- a/lib/thread.cc
+++ b/lib/thread.cc
@@ -447,6 +447,7 @@ _notmuch_thread_create (void *ctx,
notmuch_messages_t *messages;
notmuch_message_t *message;
+ notmuch_status_t status;
seed_message = _notmuch_message_create (local, notmuch, seed_doc_id, NULL);
if (! seed_message)
@@ -504,7 +505,11 @@ _notmuch_thread_create (void *ctx,
* oldest or newest subject is desired. */
notmuch_query_set_sort (thread_id_query, NOTMUCH_SORT_OLDEST_FIRST);
- for (messages = notmuch_query_search_messages (thread_id_query);
+ status = notmuch_query_search_messages_st (thread_id_query, &messages);
+ if (status)
+ goto DONE;
+
+ for (;
notmuch_messages_valid (messages);
notmuch_messages_move_to_next (messages))
{
diff --git a/mime-node.c b/mime-node.c
index fd9e4a45..e96e6639 100644
--- a/mime-node.c
+++ b/mime-node.c
@@ -129,8 +129,6 @@ DONE:
return status;
}
-#ifdef GMIME_ATLEAST_26
-
/* Signature list destructor (GMime 2.6) */
static int
_signature_list_free (GMimeSignatureList **proxy)
@@ -205,87 +203,6 @@ node_decrypt_and_verify (mime_node_t *node, GMimeObject *part,
g_error_free (err);
}
-#else /* GMIME_ATLEAST_26 */
-
-/* Signature validity destructor (GMime 2.4) */
-static int
-_signature_validity_free (GMimeSignatureValidity **proxy)
-{
- g_mime_signature_validity_free (*proxy);
- return 0;
-}
-
-/* Set up signature validity destructor (GMime 2.4) */
-static void
-set_signature_validity_destructor (mime_node_t *node,
- GMimeSignatureValidity *sig_validity)
-{
- GMimeSignatureValidity **proxy = talloc (node, GMimeSignatureValidity *);
- if (proxy) {
- *proxy = sig_validity;
- talloc_set_destructor (proxy, _signature_validity_free);
- }
-}
-
-/* Verify a signed mime node (GMime 2.4) */
-static void
-node_verify (mime_node_t *node, GMimeObject *part,
- notmuch_crypto_context_t *cryptoctx)
-{
- GError *err = NULL;
- GMimeSignatureValidity *sig_validity;
-
- node->verify_attempted = TRUE;
- sig_validity = g_mime_multipart_signed_verify
- (GMIME_MULTIPART_SIGNED (part), cryptoctx, &err);
- node->sig_validity = sig_validity;
- if (sig_validity) {
- set_signature_validity_destructor (node, sig_validity);
- } else {
- fprintf (stderr, "Failed to verify signed part: %s\n",
- err ? err->message : "no error explanation given");
- }
-
- if (err)
- g_error_free (err);
-}
-
-/* Decrypt and optionally verify an encrypted mime node (GMime 2.4) */
-static void
-node_decrypt_and_verify (mime_node_t *node, GMimeObject *part,
- notmuch_crypto_context_t *cryptoctx)
-{
- GError *err = NULL;
- GMimeMultipartEncrypted *encrypteddata = GMIME_MULTIPART_ENCRYPTED (part);
-
- node->decrypt_attempted = TRUE;
- node->decrypted_child = g_mime_multipart_encrypted_decrypt
- (encrypteddata, cryptoctx, &err);
- if (! node->decrypted_child) {
- fprintf (stderr, "Failed to decrypt part: %s\n",
- err ? err->message : "no error explanation given");
- goto DONE;
- }
-
- node->decrypt_success = TRUE;
- node->verify_attempted = TRUE;
-
- /* The GMimeSignatureValidity returned here is a const, unlike the
- * one returned by g_mime_multipart_signed_verify() in
- * node_verify() above, so the destructor is not needed.
- */
- node->sig_validity = g_mime_multipart_encrypted_get_signature_validity (encrypteddata);
- if (! node->sig_validity)
- fprintf (stderr, "Failed to verify encrypted signed part: %s\n",
- err ? err->message : "no error explanation given");
-
- DONE:
- if (err)
- g_error_free (err);
-}
-
-#endif /* GMIME_ATLEAST_26 */
-
static mime_node_t *
_mime_node_create (mime_node_t *parent, GMimeObject *part)
{
diff --git a/notmuch-client.h b/notmuch-client.h
index fb3021cc..3bd2903e 100644
--- a/notmuch-client.h
+++ b/notmuch-client.h
@@ -30,16 +30,7 @@
#include <gmime/gmime.h>
-/* GMIME_CHECK_VERSION in gmime 2.4 is not usable from the
- * preprocessor (it calls a runtime function). But since
- * GMIME_MAJOR_VERSION and friends were added in gmime 2.6, we can use
- * these to check the version number. */
-#ifdef GMIME_MAJOR_VERSION
-#define GMIME_ATLEAST_26
typedef GMimeCryptoContext notmuch_crypto_context_t;
-#else
-typedef GMimeCipherContext notmuch_crypto_context_t;
-#endif
#include "notmuch.h"
@@ -57,6 +48,7 @@ typedef GMimeCipherContext notmuch_crypto_context_t;
#include <dirent.h>
#include <errno.h>
#include <signal.h>
+#include <ctype.h>
#include "talloc-extra.h"
@@ -394,17 +386,10 @@ struct mime_node {
/* True if signature verification on this part was attempted. */
notmuch_bool_t verify_attempted;
-#ifdef GMIME_ATLEAST_26
+
/* The list of signatures for signed or encrypted containers. If
* there are no signatures, this will be NULL. */
GMimeSignatureList* sig_list;
-#else
- /* For signed or encrypted containers, the validity of the
- * signature. May be NULL if signature verification failed. If
- * there are simply no signatures, this will be non-NULL with an
- * empty signers list. */
- const GMimeSignatureValidity *sig_validity;
-#endif
/* Internal: Context inherited from the root iterator. */
struct mime_node_context *ctx;
@@ -465,5 +450,22 @@ notmuch_database_dump (notmuch_database_t *notmuch,
dump_format_t output_format,
notmuch_bool_t gzip_output);
+/* If status is non-zero (i.e. error) print appropriate
+ messages to stderr.
+*/
+
+notmuch_status_t
+print_status_query (const char *loc,
+ const notmuch_query_t *query,
+ notmuch_status_t status);
+
#include "command-line-arguments.h"
+
+extern char *notmuch_requested_db_uuid;
+extern const notmuch_opt_desc_t notmuch_shared_options [];
+void notmuch_exit_if_unmatched_db_uuid (notmuch_database_t *notmuch);
+
+void notmuch_process_shared_options (const char* subcommand_name);
+int notmuch_minimal_options (const char* subcommand_name,
+ int argc, char **argv);
#endif
diff --git a/notmuch-compact.c b/notmuch-compact.c
index 2fc012a9..93737216 100644
--- a/notmuch-compact.c
+++ b/notmuch-compact.c
@@ -38,12 +38,21 @@ notmuch_compact_command (notmuch_config_t *config, int argc, char *argv[])
notmuch_opt_desc_t options[] = {
{ NOTMUCH_OPT_STRING, &backup_path, "backup", 0, 0 },
{ NOTMUCH_OPT_BOOLEAN, &quiet, "quiet", 'q', 0 },
+ { NOTMUCH_OPT_INHERIT, (void *) &notmuch_shared_options, NULL, 0, 0 },
+ { 0, 0, 0, 0, 0}
};
opt_index = parse_arguments (argc, argv, options, 1);
if (opt_index < 0)
return EXIT_FAILURE;
+ if (notmuch_requested_db_uuid) {
+ fprintf (stderr, "Error: --uuid not implemented for compact\n");
+ return EXIT_FAILURE;
+ }
+
+ notmuch_process_shared_options (argv[0]);
+
if (! quiet)
printf ("Compacting database...\n");
ret = notmuch_database_compact (path, backup_path,
diff --git a/notmuch-config.c b/notmuch-config.c
index 2d5c297b..d252bb25 100644
--- a/notmuch-config.c
+++ b/notmuch-config.c
@@ -872,8 +872,19 @@ int
notmuch_config_command (notmuch_config_t *config, int argc, char *argv[])
{
int ret;
+ int opt_index;
- argc--; argv++; /* skip subcommand argument */
+ opt_index = notmuch_minimal_options ("config", argc, argv);
+ if (opt_index < 0)
+ return EXIT_FAILURE;
+
+ if (notmuch_requested_db_uuid)
+ fprintf (stderr, "Warning: ignoring --uuid=%s\n",
+ notmuch_requested_db_uuid);
+
+ /* skip at least subcommand argument */
+ argc-= opt_index;
+ argv+= opt_index;
if (argc < 1) {
fprintf (stderr, "Error: notmuch config requires at least one argument.\n");
diff --git a/notmuch-count.c b/notmuch-count.c
index 6058f7c9..0b6e6f54 100644
--- a/notmuch-count.c
+++ b/notmuch-count.c
@@ -33,17 +33,19 @@ enum {
EXCLUDE_FALSE,
};
-static unsigned int
+/* Return the number of files matching the query, or -1 for an error */
+static int
count_files (notmuch_query_t *query)
{
notmuch_messages_t *messages;
notmuch_message_t *message;
notmuch_filenames_t *filenames;
- unsigned int count = 0;
+ notmuch_status_t status;
+ int count = 0;
- messages = notmuch_query_search_messages (query);
- if (messages == NULL)
- return 0;
+ status = notmuch_query_search_messages_st (query, &messages);
+ if (print_status_query ("notmuch count", query, status))
+ return -1;
for (;
notmuch_messages_valid (messages);
@@ -65,17 +67,24 @@ count_files (notmuch_query_t *query)
return count;
}
+/* return 0 on success, -1 on failure */
static int
print_count (notmuch_database_t *notmuch, const char *query_str,
- const char **exclude_tags, size_t exclude_tags_length, int output)
+ const char **exclude_tags, size_t exclude_tags_length, int output, int print_lastmod)
{
notmuch_query_t *query;
size_t i;
+ int count;
+ unsigned int ucount;
+ unsigned long revision;
+ const char *uuid;
+ int ret = 0;
+ notmuch_status_t status;
query = notmuch_query_create (notmuch, query_str);
if (query == NULL) {
fprintf (stderr, "Out of memory\n");
- return 1;
+ return -1;
}
for (i = 0; i < exclude_tags_length; i++)
@@ -83,34 +92,54 @@ print_count (notmuch_database_t *notmuch, const char *query_str,
switch (output) {
case OUTPUT_MESSAGES:
- printf ("%u\n", notmuch_query_count_messages (query));
+ status = notmuch_query_count_messages_st (query, &ucount);
+ if (print_status_query ("notmuch count", query, status))
+ return -1;
+ printf ("%u", ucount);
break;
case OUTPUT_THREADS:
- printf ("%u\n", notmuch_query_count_threads (query));
+ status = notmuch_query_count_threads_st (query, &ucount);
+ if (print_status_query ("notmuch count", query, status))
+ return -1;
+ printf ("%u", ucount);
break;
case OUTPUT_FILES:
- printf ("%u\n", count_files (query));
+ count = count_files (query);
+ if (count >= 0) {
+ printf ("%u", count);
+ } else {
+ ret = -1;
+ goto DONE;
+ }
break;
}
+ if (print_lastmod) {
+ revision = notmuch_database_get_revision (notmuch, &uuid);
+ printf ("\t%s\t%lu\n", uuid, revision);
+ } else {
+ fputs ("\n", stdout);
+ }
+
+ DONE:
notmuch_query_destroy (query);
- return 0;
+ return ret;
}
static int
count_file (notmuch_database_t *notmuch, FILE *input, const char **exclude_tags,
- size_t exclude_tags_length, int output)
+ size_t exclude_tags_length, int output, int print_lastmod)
{
char *line = NULL;
ssize_t line_len;
size_t line_size;
int ret = 0;
- while (!ret && (line_len = getline (&line, &line_size, input)) != -1) {
+ while (! ret && (line_len = getline (&line, &line_size, input)) != -1) {
chomp_newline (line);
ret = print_count (notmuch, line, exclude_tags, exclude_tags_length,
- output);
+ output, print_lastmod);
}
if (line)
@@ -130,6 +159,7 @@ notmuch_count_command (notmuch_config_t *config, int argc, char *argv[])
const char **search_exclude_tags = NULL;
size_t search_exclude_tags_length = 0;
notmuch_bool_t batch = FALSE;
+ notmuch_bool_t print_lastmod = FALSE;
FILE *input = stdin;
char *input_file_name = NULL;
int ret;
@@ -144,8 +174,10 @@ notmuch_count_command (notmuch_config_t *config, int argc, char *argv[])
(notmuch_keyword_t []){ { "true", EXCLUDE_TRUE },
{ "false", EXCLUDE_FALSE },
{ 0, 0 } } },
+ { NOTMUCH_OPT_BOOLEAN, &print_lastmod, "lastmod", 'l', 0 },
{ NOTMUCH_OPT_BOOLEAN, &batch, "batch", 0, 0 },
{ NOTMUCH_OPT_STRING, &input_file_name, "input", 'i', 0 },
+ { NOTMUCH_OPT_INHERIT, (void *) &notmuch_shared_options, NULL, 0, 0 },
{ 0, 0, 0, 0, 0 }
};
@@ -153,6 +185,8 @@ notmuch_count_command (notmuch_config_t *config, int argc, char *argv[])
if (opt_index < 0)
return EXIT_FAILURE;
+ notmuch_process_shared_options (argv[0]);
+
if (input_file_name) {
batch = TRUE;
input = fopen (input_file_name, "r");
@@ -172,7 +206,9 @@ notmuch_count_command (notmuch_config_t *config, int argc, char *argv[])
NOTMUCH_DATABASE_MODE_READ_ONLY, &notmuch))
return EXIT_FAILURE;
- query_str = query_string_from_args (config, argc-opt_index, argv+opt_index);
+ notmuch_exit_if_unmatched_db_uuid (notmuch);
+
+ query_str = query_string_from_args (config, argc - opt_index, argv + opt_index);
if (query_str == NULL) {
fprintf (stderr, "Out of memory.\n");
return EXIT_FAILURE;
@@ -185,10 +221,10 @@ notmuch_count_command (notmuch_config_t *config, int argc, char *argv[])
if (batch)
ret = count_file (notmuch, input, search_exclude_tags,
- search_exclude_tags_length, output);
+ search_exclude_tags_length, output, print_lastmod);
else
ret = print_count (notmuch, query_str, search_exclude_tags,
- search_exclude_tags_length, output);
+ search_exclude_tags_length, output, print_lastmod);
notmuch_database_destroy (notmuch);
diff --git a/notmuch-dump.c b/notmuch-dump.c
index 9c6ad7f4..829781f8 100644
--- a/notmuch-dump.c
+++ b/notmuch-dump.c
@@ -48,8 +48,13 @@ database_dump_file (notmuch_database_t *notmuch, gzFile output,
char *buffer = NULL;
size_t buffer_size = 0;
+ notmuch_status_t status;
- for (messages = notmuch_query_search_messages (query);
+ status = notmuch_query_search_messages_st (query, &messages);
+ if (print_status_query ("notmuch dump", query, status))
+ return EXIT_FAILURE;
+
+ for (;
notmuch_messages_valid (messages);
notmuch_messages_move_to_next (messages)) {
int first = 1;
@@ -215,6 +220,8 @@ notmuch_dump_command (notmuch_config_t *config, int argc, char *argv[])
NOTMUCH_DATABASE_MODE_READ_WRITE, &notmuch))
return EXIT_FAILURE;
+ notmuch_exit_if_unmatched_db_uuid (notmuch);
+
char *output_file_name = NULL;
int opt_index;
@@ -228,6 +235,7 @@ notmuch_dump_command (notmuch_config_t *config, int argc, char *argv[])
{ 0, 0 } } },
{ NOTMUCH_OPT_STRING, &output_file_name, "output", 'o', 0 },
{ NOTMUCH_OPT_BOOLEAN, &gzip_output, "gzip", 'z', 0 },
+ { NOTMUCH_OPT_INHERIT, (void *) &notmuch_shared_options, NULL, 0, 0 },
{ 0, 0, 0, 0, 0 }
};
@@ -235,6 +243,8 @@ notmuch_dump_command (notmuch_config_t *config, int argc, char *argv[])
if (opt_index < 0)
return EXIT_FAILURE;
+ notmuch_process_shared_options (argv[0]);
+
if (opt_index < argc) {
query_str = query_string_from_args (notmuch, argc - opt_index, argv + opt_index);
if (query_str == NULL) {
diff --git a/notmuch-emacs-mua b/notmuch-emacs-mua
index 79714305..016fa126 100755
--- a/notmuch-emacs-mua
+++ b/notmuch-emacs-mua
@@ -34,12 +34,13 @@ EMACS=${EMACS-emacs}
EMACSCLIENT=${EMACSCLIENT-emacsclient}
PRINT_ONLY=
+NO_WINDOW=
USE_EMACSCLIENT=
-CLIENT_TYPE="-c"
+AUTO_DAEMON=
+CREATE_FRAME=
# The crux of it all: construct an elisp progn and eval it.
ELISP="(prog1 'done (require 'notmuch) (notmuch-mua-new-mail)"
-ELISP="${ELISP} (setq message-exit-actions (list #'save-buffers-kill-terminal))"
# Short options compatible with mutt(1).
while getopts :s:c:b:i:h opt; do
@@ -63,7 +64,7 @@ while getopts :s:c:b:i:h opt; do
opt=${opt%%=*}
;;
# Long options without arguments.
- --help|--print|--no-window-system|--client)
+ --help|--print|--no-window-system|--client|--auto-daemon|--create-frame)
;;
*)
echo "$0: unknown long option ${opt}, or argument mismatch." >&2
@@ -81,9 +82,6 @@ while getopts :s:c:b:i:h opt; do
--help|h)
exec man notmuch-emacs-mua
;;
- --client)
- USE_EMACSCLIENT="yes"
- ;;
--subject|s)
ELISP="${ELISP} (message-goto-subject) (insert \"${OPTARG}\")"
;;
@@ -103,7 +101,17 @@ while getopts :s:c:b:i:h opt; do
PRINT_ONLY=1
;;
--no-window-system)
- CLIENT_TYPE="-t"
+ NO_WINDOW="-nw"
+ ;;
+ --client)
+ USE_EMACSCLIENT="yes"
+ ;;
+ --auto-daemon)
+ AUTO_DAEMON="--alternate-editor="
+ CREATE_FRAME="-c"
+ ;;
+ --create-frame)
+ CREATE_FRAME="-c"
;;
*)
# We should never end up here.
@@ -122,6 +130,11 @@ for arg; do
ELISP="${ELISP} (message-goto-to) (insert \"${arg}, \")"
done
+# Kill the terminal/frame if we're creating one.
+if [ -z "$USE_EMACSCLIENT" -o -n "$CREATE_FRAME" -o -n "$NO_WINDOW" ]; then
+ ELISP="${ELISP} (setq message-exit-actions (list #'save-buffers-kill-terminal))"
+fi
+
# End progn.
ELISP="${ELISP})"
@@ -132,7 +145,7 @@ fi
if [ -n "$USE_EMACSCLIENT" ]; then
# Evaluate the progn.
- exec ${EMACSCLIENT} ${CLIENT_TYPE} -a '' --eval "${ELISP}"
+ exec ${EMACSCLIENT} ${NO_WINDOW} ${CREATE_FRAME} ${AUTO_DAEMON} --eval "${ELISP}"
else
- exec ${EMACS} --eval "${ELISP}"
+ exec ${EMACS} ${NO_WINDOW} --eval "${ELISP}"
fi
diff --git a/notmuch-insert.c b/notmuch-insert.c
index 90fe3bad..5205c17a 100644
--- a/notmuch-insert.c
+++ b/notmuch-insert.c
@@ -466,6 +466,7 @@ notmuch_insert_command (notmuch_config_t *config, int argc, char *argv[])
{ NOTMUCH_OPT_BOOLEAN, &create_folder, "create-folder", 0, 0 },
{ NOTMUCH_OPT_BOOLEAN, &keep, "keep", 0, 0 },
{ NOTMUCH_OPT_BOOLEAN, &no_hooks, "no-hooks", 'n', 0 },
+ { NOTMUCH_OPT_INHERIT, (void *) &notmuch_shared_options, NULL, 0, 0 },
{ NOTMUCH_OPT_END, 0, 0, 0, 0 }
};
@@ -473,6 +474,8 @@ notmuch_insert_command (notmuch_config_t *config, int argc, char *argv[])
if (opt_index < 0)
return EXIT_FAILURE;
+ notmuch_process_shared_options (argv[0]);
+
db_path = notmuch_config_get_database_path (config);
new_tags = notmuch_config_get_new_tags (config, &new_tags_length);
synchronize_flags = notmuch_config_get_maildir_synchronize_flags (config);
@@ -521,7 +524,7 @@ notmuch_insert_command (notmuch_config_t *config, int argc, char *argv[])
return EXIT_FAILURE;
}
- /* Setup our handler for SIGINT. We do not set SA_RESTART so that copying
+ /* Set up our handler for SIGINT. We do not set SA_RESTART so that copying
* from standard input may be interrupted. */
memset (&action, 0, sizeof (struct sigaction));
action.sa_handler = handle_sigint;
@@ -533,6 +536,8 @@ notmuch_insert_command (notmuch_config_t *config, int argc, char *argv[])
NOTMUCH_DATABASE_MODE_READ_WRITE, &notmuch))
return EXIT_FAILURE;
+ notmuch_exit_if_unmatched_db_uuid (notmuch);
+
/* Write the message to the Maildir new directory. */
newpath = maildir_write_new (config, STDIN_FILENO, maildir);
if (! newpath) {
diff --git a/notmuch-new.c b/notmuch-new.c
index e6c283eb..d45d0af8 100644
--- a/notmuch-new.c
+++ b/notmuch-new.c
@@ -528,6 +528,10 @@ add_files (notmuch_database_t *notmuch,
"%s/%s", path,
notmuch_filenames_get (db_files));
+ if (state->debug)
+ printf ("(D) add_files_recursive, pass 2: queuing passed file %s for deletion from database\n",
+ absolute);
+
_filename_list_add (state->removed_files, absolute);
notmuch_filenames_move_to_next (db_files);
@@ -542,6 +546,9 @@ add_files (notmuch_database_t *notmuch,
{
char *absolute = talloc_asprintf (state->removed_directories,
"%s/%s", path, filename);
+ if (state->debug)
+ printf ("(D) add_files_recursive, pass 2: queuing passed directory %s for deletion from database\n",
+ absolute);
_filename_list_add (state->removed_directories, absolute);
}
@@ -610,6 +617,9 @@ add_files (notmuch_database_t *notmuch,
char *absolute = talloc_asprintf (state->removed_files,
"%s/%s", path,
notmuch_filenames_get (db_files));
+ if (state->debug)
+ printf ("(D) add_files_recursive, pass 3: queuing leftover file %s for deletion from database\n",
+ absolute);
_filename_list_add (state->removed_files, absolute);
@@ -622,6 +632,10 @@ add_files (notmuch_database_t *notmuch,
"%s/%s", path,
notmuch_filenames_get (db_subdirs));
+ if (state->debug)
+ printf ("(D) add_files_recursive, pass 3: queuing leftover directory %s for deletion from database\n",
+ absolute);
+
_filename_list_add (state->removed_directories, absolute);
notmuch_filenames_move_to_next (db_subdirs);
@@ -662,7 +676,7 @@ setup_progress_printing_timer (void)
struct sigaction action;
struct itimerval timerval;
- /* Setup our handler for SIGALRM */
+ /* Set up our handler for SIGALRM */
memset (&action, 0, sizeof (struct sigaction));
action.sa_handler = handle_sigalrm;
sigemptyset (&action.sa_mask);
@@ -864,8 +878,11 @@ _remove_directory (void *ctx,
goto DONE;
}
+ status = notmuch_directory_delete (directory);
+
DONE:
- notmuch_directory_destroy (directory);
+ if (status)
+ notmuch_directory_destroy (directory);
return status;
}
@@ -910,7 +927,11 @@ int
notmuch_new_command (notmuch_config_t *config, int argc, char *argv[])
{
notmuch_database_t *notmuch;
- add_files_state_t add_files_state;
+ add_files_state_t add_files_state = {
+ .verbosity = VERBOSITY_NORMAL,
+ .debug = FALSE,
+ .output_is_a_tty = isatty (fileno (stdout)),
+ };
struct timeval tv_start;
int ret = 0;
struct stat st;
@@ -925,15 +946,12 @@ notmuch_new_command (notmuch_config_t *config, int argc, char *argv[])
notmuch_bool_t quiet = FALSE, verbose = FALSE;
notmuch_status_t status;
- add_files_state.verbosity = VERBOSITY_NORMAL;
- add_files_state.debug = FALSE;
- add_files_state.output_is_a_tty = isatty (fileno (stdout));
-
notmuch_opt_desc_t options[] = {
{ NOTMUCH_OPT_BOOLEAN, &quiet, "quiet", 'q', 0 },
{ NOTMUCH_OPT_BOOLEAN, &verbose, "verbose", 'v', 0 },
{ NOTMUCH_OPT_BOOLEAN, &add_files_state.debug, "debug", 'd', 0 },
{ NOTMUCH_OPT_BOOLEAN, &no_hooks, "no-hooks", 'n', 0 },
+ { NOTMUCH_OPT_INHERIT, (void *) &notmuch_shared_options, NULL, 0, 0 },
{ 0, 0, 0, 0, 0 }
};
@@ -941,6 +959,8 @@ notmuch_new_command (notmuch_config_t *config, int argc, char *argv[])
if (opt_index < 0)
return EXIT_FAILURE;
+ notmuch_process_shared_options (argv[0]);
+
/* quiet trumps verbose */
if (quiet)
add_files_state.verbosity = VERBOSITY_QUIET;
@@ -992,10 +1012,11 @@ notmuch_new_command (notmuch_config_t *config, int argc, char *argv[])
fputs (status_string, stderr);
free (status_string);
}
-
return EXIT_FAILURE;
}
+ notmuch_exit_if_unmatched_db_uuid (notmuch);
+
if (notmuch_database_needs_upgrade (notmuch)) {
time_t now = time (NULL);
struct tm *gm_time = gmtime (&now);
@@ -1047,7 +1068,7 @@ notmuch_new_command (notmuch_config_t *config, int argc, char *argv[])
if (notmuch == NULL)
return EXIT_FAILURE;
- /* Setup our handler for SIGINT. We do this after having
+ /* Set up our handler for SIGINT. We do this after having
* potentially done a database upgrade we this interrupt handler
* won't support. */
memset (&action, 0, sizeof (struct sigaction));
@@ -1059,9 +1080,6 @@ notmuch_new_command (notmuch_config_t *config, int argc, char *argv[])
talloc_free (dot_notmuch_path);
dot_notmuch_path = NULL;
- add_files_state.processed_files = 0;
- add_files_state.added_messages = 0;
- add_files_state.removed_messages = add_files_state.renamed_messages = 0;
gettimeofday (&add_files_state.tv_start, NULL);
add_files_state.removed_files = _filename_list_create (config);
diff --git a/notmuch-reply.c b/notmuch-reply.c
index d51fdfc3..13571429 100644
--- a/notmuch-reply.c
+++ b/notmuch-reply.c
@@ -606,8 +606,13 @@ notmuch_reply_format_default(void *ctx,
notmuch_messages_t *messages;
notmuch_message_t *message;
mime_node_t *root;
+ notmuch_status_t status;
- for (messages = notmuch_query_search_messages (query);
+ status = notmuch_query_search_messages_st (query, &messages);
+ if (print_status_query ("notmuch reply", query, status))
+ return 1;
+
+ for (;
notmuch_messages_valid (messages);
notmuch_messages_move_to_next (messages))
{
@@ -650,13 +655,22 @@ notmuch_reply_format_sprinter(void *ctx,
notmuch_messages_t *messages;
notmuch_message_t *message;
mime_node_t *node;
+ unsigned count;
+ notmuch_status_t status;
- if (notmuch_query_count_messages (query) != 1) {
+ status = notmuch_query_count_messages_st (query, &count);
+ if (print_status_query ("notmuch reply", query, status))
+ return 1;
+
+ if (count != 1) {
fprintf (stderr, "Error: search term did not match precisely one message.\n");
return 1;
}
- messages = notmuch_query_search_messages (query);
+ status = notmuch_query_search_messages_st (query, &messages);
+ if (print_status_query ("notmuch reply", query, status))
+ return 1;
+
message = notmuch_messages_get (messages);
if (mime_node_open (ctx, message, &(params->crypto), &node) != NOTMUCH_STATUS_SUCCESS)
return 1;
@@ -698,8 +712,13 @@ notmuch_reply_format_headers_only(void *ctx,
notmuch_message_t *message;
const char *in_reply_to, *orig_references, *references;
char *reply_headers;
+ notmuch_status_t status;
+
+ status = notmuch_query_search_messages_st (query, &messages);
+ if (print_status_query ("notmuch reply", query, status))
+ return 1;
- for (messages = notmuch_query_search_messages (query);
+ for (;
notmuch_messages_valid (messages);
notmuch_messages_move_to_next (messages))
{
@@ -790,6 +809,7 @@ notmuch_reply_command (notmuch_config_t *config, int argc, char *argv[])
{ "sender", FALSE },
{ 0, 0 } } },
{ NOTMUCH_OPT_BOOLEAN, &params.crypto.decrypt, "decrypt", 'd', 0 },
+ { NOTMUCH_OPT_INHERIT, (void *) &notmuch_shared_options, NULL, 0, 0 },
{ 0, 0, 0, 0, 0 }
};
@@ -797,6 +817,8 @@ notmuch_reply_command (notmuch_config_t *config, int argc, char *argv[])
if (opt_index < 0)
return EXIT_FAILURE;
+ notmuch_process_shared_options (argv[0]);
+
if (format == FORMAT_HEADERS_ONLY) {
reply_format_func = notmuch_reply_format_headers_only;
} else if (format == FORMAT_JSON) {
@@ -828,6 +850,8 @@ notmuch_reply_command (notmuch_config_t *config, int argc, char *argv[])
NOTMUCH_DATABASE_MODE_READ_ONLY, &notmuch))
return EXIT_FAILURE;
+ notmuch_exit_if_unmatched_db_uuid (notmuch);
+
query = notmuch_query_create (notmuch, query_string);
if (query == NULL) {
fprintf (stderr, "Out of memory\n");
diff --git a/notmuch-restore.c b/notmuch-restore.c
index 584d9f96..9abc64fd 100644
--- a/notmuch-restore.c
+++ b/notmuch-restore.c
@@ -154,6 +154,7 @@ notmuch_restore_command (notmuch_config_t *config, int argc, char *argv[])
{ 0, 0 } } },
{ NOTMUCH_OPT_STRING, &input_file_name, "input", 'i', 0 },
{ NOTMUCH_OPT_BOOLEAN, &accumulate, "accumulate", 'a', 0 },
+ { NOTMUCH_OPT_INHERIT, (void *) &notmuch_shared_options, NULL, 0, 0 },
{ 0, 0, 0, 0, 0 }
};
@@ -163,6 +164,9 @@ notmuch_restore_command (notmuch_config_t *config, int argc, char *argv[])
goto DONE;
}
+ notmuch_process_shared_options (argv[0]);
+ notmuch_exit_if_unmatched_db_uuid (notmuch);
+
name_for_error = input_file_name ? input_file_name : "stdin";
if (! accumulate)
diff --git a/notmuch-search.c b/notmuch-search.c
index b81ac013..6d08c250 100644
--- a/notmuch-search.c
+++ b/notmuch-search.c
@@ -37,6 +37,12 @@ typedef enum {
} output_t;
typedef enum {
+ DEDUP_NONE,
+ DEDUP_MAILBOX,
+ DEDUP_ADDRESS,
+} dedup_t;
+
+typedef enum {
NOTMUCH_FORMAT_JSON,
NOTMUCH_FORMAT_TEXT,
NOTMUCH_FORMAT_TEXT0,
@@ -55,6 +61,7 @@ typedef struct {
int limit;
int dupe;
GHashTable *addresses;
+ dedup_t dedup;
} search_context_t;
typedef struct {
@@ -111,15 +118,22 @@ do_search_threads (search_context_t *ctx)
sprinter_t *format = ctx->format;
time_t date;
int i;
+ notmuch_status_t status;
if (ctx->offset < 0) {
- ctx->offset += notmuch_query_count_threads (ctx->query);
+ unsigned count;
+ notmuch_status_t status;
+ status = notmuch_query_count_threads_st (ctx->query, &count);
+ if (print_status_query ("notmuch search", ctx->query, status))
+ return 1;
+
+ ctx->offset += count;
if (ctx->offset < 0)
ctx->offset = 0;
}
- threads = notmuch_query_search_threads (ctx->query);
- if (threads == NULL)
+ status = notmuch_query_search_threads_st (ctx->query, &threads);
+ if (print_status_query("notmuch search", ctx->query, status))
return 1;
format->begin_list (format);
@@ -243,33 +257,87 @@ do_search_threads (search_context_t *ctx)
return 0;
}
+static mailbox_t *new_mailbox (void *ctx, const char *name, const char *addr)
+{
+ mailbox_t *mailbox;
+
+ mailbox = talloc (ctx, mailbox_t);
+ if (! mailbox)
+ return NULL;
+
+ mailbox->name = talloc_strdup (mailbox, name);
+ mailbox->addr = talloc_strdup (mailbox, addr);
+ mailbox->count = 1;
+
+ return mailbox;
+}
+
+static int mailbox_compare (const void *v1, const void *v2)
+{
+ const mailbox_t *m1 = v1, *m2 = v2;
+ int ret;
+
+ ret = strcmp_null (m1->name, m2->name);
+ if (! ret)
+ ret = strcmp (m1->addr, m2->addr);
+
+ return ret;
+}
+
/* Returns TRUE iff name and addr is duplicate. If not, stores the
* name/addr pair in order to detect subsequent duplicates. */
static notmuch_bool_t
is_duplicate (const search_context_t *ctx, const char *name, const char *addr)
{
- notmuch_bool_t duplicate;
char *key;
+ GList *list, *l;
mailbox_t *mailbox;
- key = talloc_asprintf (ctx->format, "%s <%s>", name, addr);
+ list = g_hash_table_lookup (ctx->addresses, addr);
+ if (list) {
+ mailbox_t find = {
+ .name = name,
+ .addr = addr,
+ };
+
+ l = g_list_find_custom (list, &find, mailbox_compare);
+ if (l) {
+ mailbox = l->data;
+ mailbox->count++;
+ return TRUE;
+ }
+
+ mailbox = new_mailbox (ctx->format, name, addr);
+ if (! mailbox)
+ return FALSE;
+
+ /*
+ * XXX: It would be more efficient to prepend to the list, but
+ * then we'd have to store the changed list head back to the
+ * hash table. This check is here just to avoid the compiler
+ * warning for unused result.
+ */
+ if (list != g_list_append (list, mailbox))
+ INTERNAL_ERROR ("appending to list changed list head\n");
+
+ return FALSE;
+ }
+
+ key = talloc_strdup (ctx->format, addr);
if (! key)
return FALSE;
- duplicate = g_hash_table_lookup_extended (ctx->addresses, key, NULL, (gpointer)&mailbox);
+ mailbox = new_mailbox (ctx->format, name, addr);
+ if (! mailbox)
+ return FALSE;
- if (! duplicate) {
- mailbox = talloc (ctx->format, mailbox_t);
- mailbox->name = talloc_strdup (mailbox, name);
- mailbox->addr = talloc_strdup (mailbox, addr);
- mailbox->count = 1;
- g_hash_table_insert (ctx->addresses, key, mailbox);
- } else {
- mailbox->count++;
- talloc_free (key);
- }
+ list = g_list_append (NULL, mailbox);
+ if (! list)
+ return FALSE;
+
+ g_hash_table_insert (ctx->addresses, key, list);
- return duplicate;
+ return FALSE;
}
static void
@@ -287,7 +355,7 @@ print_mailbox (const search_context_t *ctx, const mailbox_t *mailbox)
name_addr = internet_address_to_string (ia, FALSE);
if (format->is_text_printer) {
- if (count > 0) {
+ if (ctx->output & OUTPUT_COUNT) {
format->integer (format, count);
format->string (format, "\t");
}
@@ -301,7 +369,7 @@ print_mailbox (const search_context_t *ctx, const mailbox_t *mailbox)
format->string (format, addr);
format->map_key (format, "name-addr");
format->string (format, name_addr);
- if (count > 0) {
+ if (ctx->output & OUTPUT_COUNT) {
format->map_key (format, "count");
format->integer (format, count);
}
@@ -338,13 +406,15 @@ process_address_list (const search_context_t *ctx,
mailbox_t mbx = {
.name = internet_address_get_name (address),
.addr = internet_address_mailbox_get_addr (mailbox),
- .count = 0,
};
- if (is_duplicate (ctx, mbx.name, mbx.addr))
+ /* OUTPUT_COUNT only works with deduplication */
+ if (ctx->dedup != DEDUP_NONE &&
+ is_duplicate (ctx, mbx.name, mbx.addr))
continue;
- if (ctx->output & OUTPUT_COUNT)
+ /* OUTPUT_COUNT and DEDUP_ADDRESS require a full pass. */
+ if (ctx->output & OUTPUT_COUNT || ctx->dedup == DEDUP_ADDRESS)
continue;
print_mailbox (ctx, &mbx);
@@ -378,14 +448,56 @@ _talloc_free_for_g_hash (void *ptr)
}
static void
-print_hash_value (unused (gpointer key), gpointer value, gpointer user_data)
+_list_free_for_g_hash (void *ptr)
{
- const mailbox_t *mailbox = value;
- search_context_t *ctx = user_data;
+ g_list_free_full (ptr, _talloc_free_for_g_hash);
+}
+
+/* Print the most common variant of a list of unique mailboxes, and
+ * conflate the counts. */
+static void
+print_popular (const search_context_t *ctx, GList *list)
+{
+ GList *l;
+ mailbox_t *mailbox = NULL, *m;
+ int max = 0;
+ int total = 0;
+
+ for (l = list; l; l = l->next) {
+ m = l->data;
+ total += m->count;
+ if (m->count > max) {
+ mailbox = m;
+ max = m->count;
+ }
+ }
+
+ if (! mailbox)
+ INTERNAL_ERROR("Empty list in address hash table\n");
+
+ /* The original count is no longer needed, so overwrite. */
+ mailbox->count = total;
print_mailbox (ctx, mailbox);
}
+static void
+print_list_value (void *mailbox, void *context)
+{
+ print_mailbox (context, mailbox);
+}
+
+static void
+print_hash_value (unused (void *key), void *list, void *context)
+{
+ const search_context_t *ctx = context;
+
+ if (ctx->dedup == DEDUP_ADDRESS)
+ print_popular (ctx, list);
+ else
+ g_list_foreach (list, print_list_value, context);
+}
+
static int
_count_filenames (notmuch_message_t *message)
{
@@ -412,15 +524,22 @@ do_search_messages (search_context_t *ctx)
notmuch_filenames_t *filenames;
sprinter_t *format = ctx->format;
int i;
+ notmuch_status_t status;
if (ctx->offset < 0) {
- ctx->offset += notmuch_query_count_messages (ctx->query);
+ unsigned count;
+ notmuch_status_t status;
+ status = notmuch_query_count_messages_st (ctx->query, &count);
+ if (print_status_query ("notmuch search", ctx->query, status))
+ return 1;
+
+ ctx->offset += count;
if (ctx->offset < 0)
ctx->offset = 0;
}
- messages = notmuch_query_search_messages (ctx->query);
- if (messages == NULL)
+ status = notmuch_query_search_messages_st (ctx->query, &messages);
+ if (print_status_query ("notmuch search", ctx->query, status))
return 1;
format->begin_list (format);
@@ -481,7 +600,8 @@ do_search_messages (search_context_t *ctx)
notmuch_message_destroy (message);
}
- if (ctx->addresses && ctx->output & OUTPUT_COUNT)
+ if (ctx->addresses &&
+ (ctx->output & OUTPUT_COUNT || ctx->dedup == DEDUP_ADDRESS))
g_hash_table_foreach (ctx->addresses, print_hash_value, ctx);
notmuch_messages_destroy (messages);
@@ -508,8 +628,9 @@ do_search_tags (const search_context_t *ctx)
if (strcmp (notmuch_query_get_query_string (query), "*") == 0) {
tags = notmuch_database_get_all_tags (notmuch);
} else {
- messages = notmuch_query_search_messages (query);
- if (messages == NULL)
+ notmuch_status_t status;
+ status = notmuch_query_search_messages_st (query, &messages);
+ if (print_status_query ("notmuch search", query, status))
return 1;
tags = notmuch_messages_collect_tags (messages);
@@ -583,6 +704,8 @@ _notmuch_search_prepare (search_context_t *ctx, notmuch_config_t *config, int ar
return EXIT_FAILURE;
}
+ notmuch_exit_if_unmatched_db_uuid (ctx->notmuch);
+
query_str = query_string_from_args (ctx->notmuch, argc, argv);
if (query_str == NULL) {
fprintf (stderr, "Out of memory.\n");
@@ -640,6 +763,7 @@ static search_context_t search_context = {
.offset = 0,
.limit = -1, /* unlimited */
.dupe = -1,
+ .dedup = DEDUP_MAILBOX,
};
static const notmuch_opt_desc_t common_options[] = {
@@ -681,6 +805,7 @@ notmuch_search_command (notmuch_config_t *config, int argc, char *argv[])
{ NOTMUCH_OPT_INT, &ctx->limit, "limit", 'L', 0 },
{ NOTMUCH_OPT_INT, &ctx->dupe, "duplicate", 'D', 0 },
{ NOTMUCH_OPT_INHERIT, (void *) &common_options, NULL, 0, 0 },
+ { NOTMUCH_OPT_INHERIT, (void *) &notmuch_shared_options, NULL, 0, 0 },
{ 0, 0, 0, 0, 0 }
};
@@ -689,6 +814,8 @@ notmuch_search_command (notmuch_config_t *config, int argc, char *argv[])
if (opt_index < 0)
return EXIT_FAILURE;
+ notmuch_process_shared_options (argv[0]);
+
if (ctx->output != OUTPUT_FILES && ctx->output != OUTPUT_MESSAGES &&
ctx->dupe != -1) {
fprintf (stderr, "Error: --duplicate=N is only supported with --output=files and --output=messages.\n");
@@ -736,7 +863,13 @@ notmuch_address_command (notmuch_config_t *config, int argc, char *argv[])
(notmuch_keyword_t []){ { "true", NOTMUCH_EXCLUDE_TRUE },
{ "false", NOTMUCH_EXCLUDE_FALSE },
{ 0, 0 } } },
+ { NOTMUCH_OPT_KEYWORD, &ctx->dedup, "deduplicate", 'D',
+ (notmuch_keyword_t []){ { "no", DEDUP_NONE },
+ { "mailbox", DEDUP_MAILBOX },
+ { "address", DEDUP_ADDRESS },
+ { 0, 0 } } },
{ NOTMUCH_OPT_INHERIT, (void *) &common_options, NULL, 0, 0 },
+ { NOTMUCH_OPT_INHERIT, (void *) &notmuch_shared_options, NULL, 0, 0 },
{ 0, 0, 0, 0, 0 }
};
@@ -744,15 +877,28 @@ notmuch_address_command (notmuch_config_t *config, int argc, char *argv[])
if (opt_index < 0)
return EXIT_FAILURE;
+ notmuch_process_shared_options (argv[0]);
+
if (! (ctx->output & (OUTPUT_SENDER | OUTPUT_RECIPIENTS)))
ctx->output |= OUTPUT_SENDER;
+ if (ctx->output & OUTPUT_COUNT && ctx->dedup == DEDUP_NONE) {
+ fprintf (stderr, "--output=count is not applicable with --deduplicate=no\n");
+ return EXIT_FAILURE;
+ }
+
if (_notmuch_search_prepare (ctx, config,
argc - opt_index, argv + opt_index))
return EXIT_FAILURE;
- ctx->addresses = g_hash_table_new_full (g_str_hash, g_str_equal,
- _talloc_free_for_g_hash, _talloc_free_for_g_hash);
+ ctx->addresses = g_hash_table_new_full (strcase_hash, strcase_equal,
+ _talloc_free_for_g_hash,
+ _list_free_for_g_hash);
+
+ /* The order is not guaranteed if a full pass is required, so go
+ * for fastest. */
+ if (ctx->output & OUTPUT_COUNT || ctx->dedup == DEDUP_ADDRESS)
+ notmuch_query_set_sort (ctx->query, NOTMUCH_SORT_UNSORTED);
ret = do_search_messages (ctx);
diff --git a/notmuch-setup.c b/notmuch-setup.c
index 36a6171a..9aaf9286 100644
--- a/notmuch-setup.c
+++ b/notmuch-setup.c
@@ -145,6 +145,13 @@ notmuch_setup_command (notmuch_config_t *config,
chomp_newline (response); \
} while (0)
+ if (notmuch_minimal_options ("setup", argc, argv) < 0)
+ return EXIT_FAILURE;
+
+ if (notmuch_requested_db_uuid)
+ fprintf (stderr, "Warning: ignoring --uuid=%s\n",
+ notmuch_requested_db_uuid);
+
if (notmuch_config_is_new (config))
welcome_message_pre_setup ();
diff --git a/notmuch-show.c b/notmuch-show.c
index 43bf71c8..5a83c605 100644
--- a/notmuch-show.c
+++ b/notmuch-show.c
@@ -334,8 +334,6 @@ show_text_part_content (GMimeObject *part, GMimeStream *stream_out,
g_object_unref(stream_filter);
}
-#ifdef GMIME_ATLEAST_26
-
/* Get signature status string (GMime 2.6) */
static const char*
signature_status_to_string (GMimeSignatureStatus x)
@@ -427,91 +425,6 @@ format_part_sigstatus_sprinter (sprinter_t *sp, mime_node_t *node)
sp->end (sp);
}
-#else /* GMIME_ATLEAST_26 */
-
-/* Get signature status string (GMime 2.4) */
-static const char*
-signer_status_to_string (GMimeSignerStatus x)
-{
- switch (x) {
- case GMIME_SIGNER_STATUS_NONE:
- return "none";
- case GMIME_SIGNER_STATUS_GOOD:
- return "good";
- case GMIME_SIGNER_STATUS_BAD:
- return "bad";
- case GMIME_SIGNER_STATUS_ERROR:
- return "error";
- }
- return "unknown";
-}
-
-/* Signature status sprinter (GMime 2.4) */
-static void
-format_part_sigstatus_sprinter (sprinter_t *sp, mime_node_t *node)
-{
- const GMimeSignatureValidity* validity = node->sig_validity;
-
- sp->begin_list (sp);
-
- if (!validity) {
- sp->end (sp);
- return;
- }
-
- const GMimeSigner *signer = g_mime_signature_validity_get_signers (validity);
- while (signer) {
- sp->begin_map (sp);
-
- /* status */
- sp->map_key (sp, "status");
- sp->string (sp, signer_status_to_string (signer->status));
-
- if (signer->status == GMIME_SIGNER_STATUS_GOOD)
- {
- if (signer->fingerprint) {
- sp->map_key (sp, "fingerprint");
- sp->string (sp, signer->fingerprint);
- }
- /* these dates are seconds since the epoch; should we
- * provide a more human-readable format string? */
- if (signer->created) {
- sp->map_key (sp, "created");
- sp->integer (sp, signer->created);
- }
- if (signer->expires) {
- sp->map_key (sp, "expires");
- sp->integer (sp, signer->expires);
- }
- /* output user id only if validity is FULL or ULTIMATE. */
- /* note that gmime is using the term "trust" here, which
- * is WRONG. It's actually user id "validity". */
- if ((signer->name) && (signer->trust)) {
- if ((signer->trust == GMIME_SIGNER_TRUST_FULLY) || (signer->trust == GMIME_SIGNER_TRUST_ULTIMATE)) {
- sp->map_key (sp, "userid");
- sp->string (sp, signer->name);
- }
- }
- } else {
- if (signer->keyid) {
- sp->map_key (sp, "keyid");
- sp->string (sp, signer->keyid);
- }
- }
- if (signer->errors != GMIME_SIGNER_ERROR_NONE) {
- sp->map_key (sp, "errors");
- sp->integer (sp, signer->errors);
- }
-
- sp->end (sp);
- signer = signer->next;
- }
-
- sp->end (sp);
-}
-
-#endif /* GMIME_ATLEAST_26 */
-
static notmuch_status_t
format_part_text (const void *ctx, sprinter_t *sp, mime_node_t *node,
int indent, const notmuch_show_params_t *params)
@@ -982,13 +895,22 @@ do_show_single (void *ctx,
{
notmuch_messages_t *messages;
notmuch_message_t *message;
+ notmuch_status_t status;
+ unsigned int count;
- if (notmuch_query_count_messages (query) != 1) {
+ status = notmuch_query_count_messages_st (query, &count);
+ if (print_status_query ("notmuch show", query, status))
+ return 1;
+
+ if (count != 1) {
fprintf (stderr, "Error: search term did not match precisely one message.\n");
return 1;
}
- messages = notmuch_query_search_messages (query);
+ status = notmuch_query_search_messages_st (query, &messages);
+ if (print_status_query ("notmuch show", query, status))
+ return 1;
+
message = notmuch_messages_get (messages);
if (message == NULL) {
@@ -1015,8 +937,8 @@ do_show (void *ctx,
notmuch_messages_t *messages;
notmuch_status_t status, res = NOTMUCH_STATUS_SUCCESS;
- threads = notmuch_query_search_threads (query);
- if (! threads)
+ status= notmuch_query_search_threads_st (query, &threads);
+ if (print_status_query ("notmuch show", query, status))
return 1;
sp->begin_list (sp);
@@ -1114,6 +1036,7 @@ notmuch_show_command (notmuch_config_t *config, int argc, char *argv[])
{ NOTMUCH_OPT_BOOLEAN, &params.crypto.verify, "verify", 'v', 0 },
{ NOTMUCH_OPT_BOOLEAN, &params.output_body, "body", 'b', 0 },
{ NOTMUCH_OPT_BOOLEAN, &params.include_html, "include-html", 0, 0 },
+ { NOTMUCH_OPT_INHERIT, (void *) &notmuch_shared_options, NULL, 0, 0 },
{ 0, 0, 0, 0, 0 }
};
@@ -1121,6 +1044,8 @@ notmuch_show_command (notmuch_config_t *config, int argc, char *argv[])
if (opt_index < 0)
return EXIT_FAILURE;
+ notmuch_process_shared_options (argv[0]);
+
/* decryption implies verification */
if (params.crypto.decrypt)
params.crypto.verify = TRUE;
@@ -1210,6 +1135,8 @@ notmuch_show_command (notmuch_config_t *config, int argc, char *argv[])
NOTMUCH_DATABASE_MODE_READ_ONLY, &notmuch))
return EXIT_FAILURE;
+ notmuch_exit_if_unmatched_db_uuid (notmuch);
+
query = notmuch_query_create (notmuch, query_string);
if (query == NULL) {
fprintf (stderr, "Out of memory\n");
diff --git a/notmuch-tag.c b/notmuch-tag.c
index 5b2f1e48..c020cb6f 100644
--- a/notmuch-tag.c
+++ b/notmuch-tag.c
@@ -97,6 +97,8 @@ tag_query (void *ctx, notmuch_database_t *notmuch, const char *query_string,
notmuch_query_t *query;
notmuch_messages_t *messages;
notmuch_message_t *message;
+ notmuch_status_t status;
+
int ret = NOTMUCH_STATUS_SUCCESS;
if (! (flags & TAG_FLAG_REMOVE_ALL)) {
@@ -119,7 +121,11 @@ tag_query (void *ctx, notmuch_database_t *notmuch, const char *query_string,
/* tagging is not interested in any special sort order */
notmuch_query_set_sort (query, NOTMUCH_SORT_UNSORTED);
- for (messages = notmuch_query_search_messages (query);
+ status = notmuch_query_search_messages_st (query, &messages);
+ if (print_status_query ("notmuch tag", query, status))
+ return status;
+
+ for (;
notmuch_messages_valid (messages) && ! interrupted;
notmuch_messages_move_to_next (messages)) {
message = notmuch_messages_get (messages);
@@ -195,7 +201,7 @@ notmuch_tag_command (notmuch_config_t *config, int argc, char *argv[])
int opt_index;
int ret;
- /* Setup our handler for SIGINT */
+ /* Set up our handler for SIGINT */
memset (&action, 0, sizeof (struct sigaction));
action.sa_handler = handle_sigint;
sigemptyset (&action.sa_mask);
@@ -206,6 +212,7 @@ notmuch_tag_command (notmuch_config_t *config, int argc, char *argv[])
{ NOTMUCH_OPT_BOOLEAN, &batch, "batch", 0, 0 },
{ NOTMUCH_OPT_STRING, &input_file_name, "input", 'i', 0 },
{ NOTMUCH_OPT_BOOLEAN, &remove_all, "remove-all", 0, 0 },
+ { NOTMUCH_OPT_INHERIT, (void *) &notmuch_shared_options, NULL, 0, 0 },
{ 0, 0, 0, 0, 0 }
};
@@ -213,6 +220,8 @@ notmuch_tag_command (notmuch_config_t *config, int argc, char *argv[])
if (opt_index < 0)
return EXIT_FAILURE;
+ notmuch_process_shared_options (argv[0]);
+
if (input_file_name) {
batch = TRUE;
input = fopen (input_file_name, "r");
@@ -258,6 +267,8 @@ notmuch_tag_command (notmuch_config_t *config, int argc, char *argv[])
NOTMUCH_DATABASE_MODE_READ_WRITE, &notmuch))
return EXIT_FAILURE;
+ notmuch_exit_if_unmatched_db_uuid (notmuch);
+
if (notmuch_config_get_maildir_synchronize_flags (config))
tag_flags |= TAG_FLAG_MAILDIR_SYNC;
diff --git a/notmuch.c b/notmuch.c
index a5b2877a..ce6c5756 100644
--- a/notmuch.c
+++ b/notmuch.c
@@ -43,11 +43,64 @@ notmuch_help_command (notmuch_config_t *config, int argc, char *argv[]);
static int
notmuch_command (notmuch_config_t *config, int argc, char *argv[]);
+static int
+_help_for (const char *topic);
+
+static notmuch_bool_t print_version = FALSE, print_help = FALSE;
+char *notmuch_requested_db_uuid = NULL;
+
+const notmuch_opt_desc_t notmuch_shared_options [] = {
+ { NOTMUCH_OPT_BOOLEAN, &print_version, "version", 'v', 0 },
+ { NOTMUCH_OPT_BOOLEAN, &print_help, "help", 'h', 0 },
+ { NOTMUCH_OPT_STRING, &notmuch_requested_db_uuid, "uuid", 'u', 0 },
+ {0, 0, 0, 0, 0}
+};
+
+/* any subcommand wanting to support these options should call
+ * inherit notmuch_shared_options and call
+ * notmuch_process_shared_options (subcommand_name);
+ */
+void
+notmuch_process_shared_options (const char *subcommand_name) {
+ if (print_version) {
+ printf ("notmuch " STRINGIFY(NOTMUCH_VERSION) "\n");
+ exit (EXIT_SUCCESS);
+ }
+
+ if (print_help) {
+ int ret = _help_for (subcommand_name);
+ exit (ret);
+ }
+}
+
+/* This is suitable for subcommands that do not actually open the
+ * database.
+ */
+int notmuch_minimal_options (const char *subcommand_name,
+ int argc, char **argv)
+{
+ int opt_index;
+
+ notmuch_opt_desc_t options[] = {
+ { NOTMUCH_OPT_INHERIT, (void *) &notmuch_shared_options, NULL, 0, 0 },
+ { 0, 0, 0, 0, 0 }
+ };
+
+ opt_index = parse_arguments (argc, argv, options, 1);
+
+ if (opt_index < 0)
+ return -1;
+
+ /* We can't use argv here as it is sometimes NULL */
+ notmuch_process_shared_options (subcommand_name);
+ return opt_index;
+}
+
static command_t commands[] = {
{ NULL, notmuch_command, TRUE,
"Notmuch main command." },
{ "setup", notmuch_setup_command, TRUE,
- "Interactively setup notmuch for first use." },
+ "Interactively set up notmuch for first use." },
{ "new", notmuch_new_command, FALSE,
"Find and import new messages to the notmuch database." },
{ "insert", notmuch_insert_command, FALSE,
@@ -167,6 +220,22 @@ be supported in the future.\n", notmuch_format_version);
}
}
+void
+notmuch_exit_if_unmatched_db_uuid (notmuch_database_t *notmuch)
+{
+ const char *uuid = NULL;
+
+ if (!notmuch_requested_db_uuid)
+ return;
+ IGNORE_RESULT (notmuch_database_get_revision (notmuch, &uuid));
+
+ if (strcmp (notmuch_requested_db_uuid, uuid) != 0){
+ fprintf (stderr, "Error: requested database revision %s does not match %s\n",
+ notmuch_requested_db_uuid, uuid);
+ exit (1);
+ }
+}
+
static void
exec_man (const char *page)
{
@@ -177,21 +246,19 @@ exec_man (const char *page)
}
static int
-notmuch_help_command (notmuch_config_t *config, int argc, char *argv[])
+_help_for (const char *topic_name)
{
command_t *command;
help_topic_t *topic;
unsigned int i;
- argc--; argv++; /* Ignore "help" */
-
- if (argc == 0) {
+ if (!topic_name) {
printf ("The notmuch mail system.\n\n");
usage (stdout);
return EXIT_SUCCESS;
}
- if (strcmp (argv[0], "help") == 0) {
+ if (strcmp (topic_name, "help") == 0) {
printf ("The notmuch help system.\n\n"
"\tNotmuch uses the man command to display help. In case\n"
"\tof difficulties check that MANPATH includes the pages\n"
@@ -200,26 +267,46 @@ notmuch_help_command (notmuch_config_t *config, int argc, char *argv[])
return EXIT_SUCCESS;
}
- command = find_command (argv[0]);
+ command = find_command (topic_name);
if (command) {
- char *page = talloc_asprintf (config, "notmuch-%s", command->name);
+ char *page = talloc_asprintf (NULL, "notmuch-%s", command->name);
exec_man (page);
}
for (i = 0; i < ARRAY_SIZE (help_topics); i++) {
topic = &help_topics[i];
- if (strcmp (argv[0], topic->name) == 0) {
- char *page = talloc_asprintf (config, "notmuch-%s", topic->name);
+ if (strcmp (topic_name, topic->name) == 0) {
+ char *page = talloc_asprintf (NULL, "notmuch-%s", topic->name);
exec_man (page);
}
}
fprintf (stderr,
"\nSorry, %s is not a known command. There's not much I can do to help.\n\n",
- argv[0]);
+ topic_name);
return EXIT_FAILURE;
}
+static int
+notmuch_help_command (unused (notmuch_config_t * config), int argc, char *argv[])
+{
+ int opt_index;
+
+ opt_index = notmuch_minimal_options ("help", argc, argv);
+ if (opt_index < 0)
+ return EXIT_FAILURE;
+
+ /* skip at least subcommand argument */
+ argc-= opt_index;
+ argv+= opt_index;
+
+ if (argc == 0) {
+ return _help_for (NULL);
+ }
+
+ return _help_for (argv[0]);
+}
+
/* Handle the case of "notmuch" being invoked with no command
* argument. For now we just call notmuch_setup_command, but we plan
* to be more clever about this in the future.
@@ -285,14 +372,12 @@ main (int argc, char *argv[])
command_t *command;
char *config_file_name = NULL;
notmuch_config_t *config = NULL;
- notmuch_bool_t print_help=FALSE, print_version=FALSE;
int opt_index;
int ret;
notmuch_opt_desc_t options[] = {
- { NOTMUCH_OPT_BOOLEAN, &print_help, "help", 'h', 0 },
- { NOTMUCH_OPT_BOOLEAN, &print_version, "version", 'v', 0 },
{ NOTMUCH_OPT_STRING, &config_file_name, "config", 'c', 0 },
+ { NOTMUCH_OPT_INHERIT, (void *) &notmuch_shared_options, NULL, 0, 0 },
{ 0, 0, 0, 0, 0 }
};
@@ -314,28 +399,11 @@ main (int argc, char *argv[])
goto DONE;
}
- /* Handle notmuch --help [command] and notmuch command --help. */
- if (print_help ||
- (opt_index + 1 < argc && strcmp (argv[opt_index + 1], "--help") == 0)) {
- /*
- * Pass the first positional argument as argv[1] so the help
- * command can give help for it. The help command ignores the
- * argv[0] passed to it.
- */
- ret = notmuch_help_command (NULL, argc - opt_index + 1,
- argv + opt_index - 1);
- goto DONE;
- }
-
- if (print_version) {
- printf ("notmuch " STRINGIFY(NOTMUCH_VERSION) "\n");
- ret = EXIT_SUCCESS;
- goto DONE;
- }
-
if (opt_index < argc)
command_name = argv[opt_index];
+ notmuch_process_shared_options (command_name);
+
command = find_command (command_name);
if (!command) {
fprintf (stderr, "Error: Unknown command '%s' (see \"notmuch help\")\n",
diff --git a/performance-test/M00-new.sh b/performance-test/M00-new.sh
index 99c3f520..a040a97e 100755
--- a/performance-test/M00-new.sh
+++ b/performance-test/M00-new.sh
@@ -2,7 +2,7 @@
test_description='notmuch new'
-. ./perf-test-lib.sh
+. ./perf-test-lib.sh || exit 1
# ensure initial 'notmuch new' is run by memory_start
uncache_database
diff --git a/performance-test/M01-dump-restore.sh b/performance-test/M01-dump-restore.sh
index be5894a6..8fea9824 100755
--- a/performance-test/M01-dump-restore.sh
+++ b/performance-test/M01-dump-restore.sh
@@ -2,7 +2,7 @@
test_description='dump and restore'
-. ./perf-test-lib.sh
+. ./perf-test-lib.sh || exit 1
memory_start
diff --git a/performance-test/T00-new.sh b/performance-test/T00-new.sh
index 553bb8b6..b9f21158 100755
--- a/performance-test/T00-new.sh
+++ b/performance-test/T00-new.sh
@@ -2,7 +2,7 @@
test_description='notmuch new'
-. ./perf-test-lib.sh
+. ./perf-test-lib.sh || exit 1
uncache_database
diff --git a/performance-test/T01-dump-restore.sh b/performance-test/T01-dump-restore.sh
index b2ff9400..9cfd5cd6 100755
--- a/performance-test/T01-dump-restore.sh
+++ b/performance-test/T01-dump-restore.sh
@@ -2,7 +2,7 @@
test_description='dump and restore'
-. ./perf-test-lib.sh
+. ./perf-test-lib.sh || exit 1
time_start
diff --git a/performance-test/T02-tag.sh b/performance-test/T02-tag.sh
index 78cecccc..dacb50b8 100755
--- a/performance-test/T02-tag.sh
+++ b/performance-test/T02-tag.sh
@@ -2,7 +2,7 @@
test_description='tagging'
-. ./perf-test-lib.sh
+. ./perf-test-lib.sh || exit 1
time_start
diff --git a/performance-test/perf-test-lib.sh b/performance-test/perf-test-lib.sh
index 75e3d878..00d2f1c6 100644
--- a/performance-test/perf-test-lib.sh
+++ b/performance-test/perf-test-lib.sh
@@ -1,4 +1,4 @@
-. ./version.sh
+. ./version.sh || exit 1
corpus_size=large
@@ -25,7 +25,7 @@ do
echo "error: unknown performance test option '$1'" >&2; exit 1 ;;
esac
done
-. ../test/test-lib-common.sh
+. ../test/test-lib-common.sh || exit 1
set -e
@@ -203,7 +203,7 @@ time_done ()
fi
}
-cd -P "$test" || error "Cannot setup test environment"
+cd -P "$test" || error "Cannot set up test environment"
test_failure=0
test_count=0
diff --git a/status.c b/status.c
new file mode 100644
index 00000000..8fa81cbf
--- /dev/null
+++ b/status.c
@@ -0,0 +1,21 @@
+#include "notmuch-client.h"
+
+notmuch_status_t
+print_status_query (const char *loc,
+ const notmuch_query_t *query,
+ notmuch_status_t status)
+{
+ if (status) {
+ const char *msg;
+ notmuch_database_t *notmuch;
+
+ fprintf (stderr, "%s: %s\n", loc,
+ notmuch_status_to_string (status));
+
+ notmuch = notmuch_query_get_database (query);
+ msg = notmuch_database_status_string (notmuch);
+ if (msg)
+ fputs (msg, stderr);
+ }
+ return status;
+}
diff --git a/test/Makefile.local b/test/Makefile.local
index 2331ceb1..2b186914 100644
--- a/test/Makefile.local
+++ b/test/Makefile.local
@@ -56,7 +56,17 @@ TEST_BINARIES := $(TEST_BINARIES:.cc=)
test-binaries: $(TEST_BINARIES)
test: all test-binaries
+ifeq ($V,)
+ @echo 'Use "$(MAKE) V=1" to print test headings and PASSIng results.'
+ @env NOTMUCH_TEST_QUIET=1 ${test_src_dir}/notmuch-test $(OPTIONS)
+else
+# The user has explicitly enabled quiet execution.
+ifeq ($V,0)
+ @env NOTMUCH_TEST_QUIET=1 ${test_src_dir}/notmuch-test $(OPTIONS)
+else
@${test_src_dir}/notmuch-test $(OPTIONS)
+endif
+endif
check: test
diff --git a/test/README b/test/README
index daf41600..e54e36b7 100644
--- a/test/README
+++ b/test/README
@@ -117,6 +117,13 @@ Note that some tests in the existing test suite rely on previous test
items, so you cannot arbitrarily skip any test and expect the
remaining tests to be unaffected.
+Currently we do not consider skipped tests as build failures. For
+maximum robustness, when setting up automated build processes, you
+should explicitely skip tests, rather than relying on notmuch's
+detection of missing prerequisites. In the future we may treat tests
+unable to run because of missing prerequisites, but not explicitely
+skipped by the user, as failures.
+
Writing Tests
-------------
The test script is written as a shell script. It should start with
@@ -138,7 +145,7 @@ Source 'test-lib.sh'
After assigning test_description, the test script should source
test-lib.sh like this:
- . ./test-lib.sh
+ . ./test-lib.sh || exit 1
This test harness library does the following things:
diff --git a/test/T000-basic.sh b/test/T000-basic.sh
index ef642457..d6811bd1 100755
--- a/test/T000-basic.sh
+++ b/test/T000-basic.sh
@@ -14,7 +14,7 @@ then
exit 1
fi
-. ./test-lib.sh
+. ./test-lib.sh || exit 1
################################################################
# Test harness
diff --git a/test/T010-help-test.sh b/test/T010-help-test.sh
index caf8bdb0..c1732376 100755
--- a/test/T010-help-test.sh
+++ b/test/T010-help-test.sh
@@ -1,7 +1,7 @@
#!/usr/bin/env bash
test_description="online help"
-. ./test-lib.sh
+. ./test-lib.sh || exit 1
test_expect_success 'notmuch --help' 'notmuch --help'
test_expect_success 'notmuch help' 'notmuch help'
@@ -12,9 +12,9 @@ if [ $NOTMUCH_HAVE_MAN -eq 1 ]; then
test_expect_success 'notmuch help tag' 'notmuch help tag'
else
test_expect_success 'notmuch --help tag (man pages not available)' \
- 'test_must_fail notmuch --help tag'
+ 'test_must_fail notmuch --help tag >/dev/null'
test_expect_success 'notmuch help tag (man pages not available)' \
- 'test_must_fail notmuch help tag'
+ 'test_must_fail notmuch help tag >/dev/null'
fi
test_done
diff --git a/test/T020-compact.sh b/test/T020-compact.sh
index 507f7698..8b4dbbc4 100755
--- a/test/T020-compact.sh
+++ b/test/T020-compact.sh
@@ -1,6 +1,6 @@
#!/usr/bin/env bash
test_description='"notmuch compact"'
-. ./test-lib.sh
+. ./test-lib.sh || exit 1
add_message '[subject]=One'
add_message '[subject]=Two'
diff --git a/test/T030-config.sh b/test/T030-config.sh
index 7d14a85f..f404908a 100755
--- a/test/T030-config.sh
+++ b/test/T030-config.sh
@@ -1,7 +1,7 @@
#!/usr/bin/env bash
test_description='"notmuch config"'
-. ./test-lib.sh
+. ./test-lib.sh || exit 1
test_begin_subtest "Get string value"
test_expect_equal "$(notmuch config get user.name)" "Notmuch Test Suite"
diff --git a/test/T040-setup.sh b/test/T040-setup.sh
index 0e9f279a..cf0c00bc 100755
--- a/test/T040-setup.sh
+++ b/test/T040-setup.sh
@@ -1,7 +1,7 @@
#!/usr/bin/env bash
test_description='"notmuch setup"'
-. ./test-lib.sh
+. ./test-lib.sh || exit 1
test_begin_subtest "Notmuch new without a config suggests notmuch setup"
output=$(notmuch --config=new-notmuch-config new 2>&1)
diff --git a/test/T050-new.sh b/test/T050-new.sh
index e6c3291f..81cf2fad 100755
--- a/test/T050-new.sh
+++ b/test/T050-new.sh
@@ -1,27 +1,27 @@
#!/usr/bin/env bash
test_description='"notmuch new" in several variations'
-. ./test-lib.sh
+. ./test-lib.sh || exit 1
test_begin_subtest "No new messages"
-output=$(NOTMUCH_NEW)
+output=$(NOTMUCH_NEW --debug)
test_expect_equal "$output" "No new mail."
test_begin_subtest "Single new message"
generate_message
-output=$(NOTMUCH_NEW)
+output=$(NOTMUCH_NEW --debug)
test_expect_equal "$output" "Added 1 new message to the database."
test_begin_subtest "Multiple new messages"
generate_message
generate_message
-output=$(NOTMUCH_NEW)
+output=$(NOTMUCH_NEW --debug)
test_expect_equal "$output" "Added 2 new messages to the database."
test_begin_subtest "No new messages (non-empty DB)"
-output=$(NOTMUCH_NEW)
+output=$(NOTMUCH_NEW --debug)
test_expect_equal "$output" "No new mail."
@@ -31,7 +31,7 @@ mkdir "${MAIL_DIR}"/def
mkdir "${MAIL_DIR}"/ghi
generate_message [dir]=def
-output=$(NOTMUCH_NEW)
+output=$(NOTMUCH_NEW --debug)
test_expect_equal "$output" "Added 1 new message to the database."
@@ -42,7 +42,7 @@ mv "${MAIL_DIR}"/ghi "${MAIL_DIR}"/abc
rm "${MAIL_DIR}"/def/*
generate_message [dir]=abc
-output=$(NOTMUCH_NEW)
+output=$(NOTMUCH_NEW --debug)
test_expect_equal "$output" "Added 1 new message to the database."
@@ -54,7 +54,7 @@ mkdir -p "$(dirname "$tmp_msg_filename")"
mv "$gen_msg_filename" "$tmp_msg_filename"
notmuch new > /dev/null
mv "$tmp_msg_filename" "$gen_msg_filename"
-output=$(NOTMUCH_NEW)
+output=$(NOTMUCH_NEW --debug)
test_expect_equal "$output" "Added 1 new message to the database."
@@ -63,15 +63,18 @@ test_begin_subtest "Renamed message"
generate_message
notmuch new > /dev/null
mv "$gen_msg_filename" "${gen_msg_filename}"-renamed
-output=$(NOTMUCH_NEW)
-test_expect_equal "$output" "No new mail. Detected 1 file rename."
+output=$(NOTMUCH_NEW --debug)
+test_expect_equal "$output" "(D) add_files_recursive, pass 2: queuing passed file ${gen_msg_filename} for deletion from database
+No new mail. Detected 1 file rename."
test_begin_subtest "Deleted message"
rm "${gen_msg_filename}"-renamed
-output=$(NOTMUCH_NEW)
-test_expect_equal "$output" "No new mail. Removed 1 message."
+output=$(NOTMUCH_NEW --debug)
+test_expect_equal "$output" "(D) add_files_recursive, pass 3: queuing leftover file ${gen_msg_filename}-renamed for deletion from database
+No new mail. Removed 1 message."
+
test_begin_subtest "Renamed directory"
@@ -84,16 +87,17 @@ notmuch new > /dev/null
mv "${MAIL_DIR}"/dir "${MAIL_DIR}"/dir-renamed
-output=$(NOTMUCH_NEW)
-test_expect_equal "$output" "No new mail. Detected 3 file renames."
+output=$(NOTMUCH_NEW --debug)
+test_expect_equal "$output" "(D) add_files_recursive, pass 2: queuing passed directory ${MAIL_DIR}/dir for deletion from database
+No new mail. Detected 3 file renames."
test_begin_subtest "Deleted directory"
-
rm -rf "${MAIL_DIR}"/dir-renamed
-output=$(NOTMUCH_NEW)
-test_expect_equal "$output" "No new mail. Removed 3 messages."
+output=$(NOTMUCH_NEW --debug)
+test_expect_equal "$output" "(D) add_files_recursive, pass 2: queuing passed directory ${MAIL_DIR}/dir-renamed for deletion from database
+No new mail. Removed 3 messages."
test_begin_subtest "New directory (at end of list)"
@@ -102,7 +106,7 @@ generate_message [dir]=zzz
generate_message [dir]=zzz
generate_message [dir]=zzz
-output=$(NOTMUCH_NEW)
+output=$(NOTMUCH_NEW --debug)
test_expect_equal "$output" "Added 3 new messages to the database."
@@ -110,8 +114,9 @@ test_begin_subtest "Deleted directory (end of list)"
rm -rf "${MAIL_DIR}"/zzz
-output=$(NOTMUCH_NEW)
-test_expect_equal "$output" "No new mail. Removed 3 messages."
+output=$(NOTMUCH_NEW --debug)
+test_expect_equal "$output" "(D) add_files_recursive, pass 3: queuing leftover directory ${MAIL_DIR}/zzz for deletion from database
+No new mail. Removed 3 messages."
test_begin_subtest "New symlink to directory"
@@ -122,7 +127,7 @@ mv "${MAIL_DIR}" "${TMP_DIRECTORY}"/actual_maildir
mkdir "${MAIL_DIR}"
ln -s "${TMP_DIRECTORY}"/actual_maildir "${MAIL_DIR}"/symlink
-output=$(NOTMUCH_NEW)
+output=$(NOTMUCH_NEW --debug)
test_expect_equal "$output" "Added 1 new message to the database."
@@ -132,13 +137,13 @@ external_msg_filename="${TMP_DIRECTORY}"/external/"$(basename "$gen_msg_filename
mkdir -p "$(dirname "$external_msg_filename")"
mv "$gen_msg_filename" "$external_msg_filename"
ln -s "$external_msg_filename" "$gen_msg_filename"
-output=$(NOTMUCH_NEW)
+output=$(NOTMUCH_NEW --debug)
test_expect_equal "$output" "Added 1 new message to the database."
test_begin_subtest "Broken symlink aborts"
ln -s does-not-exist "${MAIL_DIR}/broken"
-output=$(NOTMUCH_NEW 2>&1)
+output=$(NOTMUCH_NEW --debug 2>&1)
test_expect_equal "$output" \
"Error reading file ${MAIL_DIR}/broken: No such file or directory
Note: A fatal error was encountered: Something went wrong trying to read or write a file
@@ -152,7 +157,7 @@ generate_message [dir]=two/levels
generate_message [dir]=two/levels
generate_message [dir]=two/levels
-output=$(NOTMUCH_NEW)
+output=$(NOTMUCH_NEW --debug)
test_expect_equal "$output" "Added 3 new messages to the database."
@@ -160,10 +165,11 @@ test_begin_subtest "Deleted two-level directory"
rm -rf "${MAIL_DIR}"/two
-output=$(NOTMUCH_NEW)
-test_expect_equal "$output" "No new mail. Removed 3 messages."
+output=$(NOTMUCH_NEW --debug)
+test_expect_equal "$output" "(D) add_files_recursive, pass 3: queuing leftover directory ${MAIL_DIR}/two for deletion from database
+No new mail. Removed 3 messages."
-test_begin_subtest "Support single-message mbox (deprecated)"
+test_begin_subtest "Support single-message mbox"
cat > "${MAIL_DIR}"/mbox_file1 <<EOF
From test_suite@notmuchmail.org Fri Jan 5 15:43:57 2001
From: Notmuch Test Suite <test_suite@notmuchmail.org>
@@ -172,7 +178,7 @@ Subject: Test mbox message 1
Body.
EOF
-output=$(NOTMUCH_NEW 2>&1)
+output=$(NOTMUCH_NEW --debug 2>&1)
test_expect_equal "$output" "Added 1 new message to the database."
# This test requires that notmuch new has been run at least once.
@@ -196,7 +202,7 @@ Subject: Test mbox message 2
Body 2.
EOF
-output=$(NOTMUCH_NEW 2>&1)
+output=$(NOTMUCH_NEW --debug 2>&1)
test_expect_equal "$output" \
"Note: Ignoring non-mail file: ${MAIL_DIR}/.git/config
Note: Ignoring non-mail file: ${MAIL_DIR}/.ignored_hidden_file
@@ -263,23 +269,23 @@ OLDCONFIG=$(notmuch config get new.tags)
test_begin_subtest "Empty tags in new.tags are forbidden"
notmuch config set new.tags "foo;;bar"
-output=$(NOTMUCH_NEW 2>&1)
+output=$(NOTMUCH_NEW --debug 2>&1)
test_expect_equal "$output" "Error: tag '' in new.tags: empty tag forbidden"
test_begin_subtest "Tags starting with '-' in new.tags are forbidden"
notmuch config set new.tags "-foo;bar"
-output=$(NOTMUCH_NEW 2>&1)
+output=$(NOTMUCH_NEW --debug 2>&1)
test_expect_equal "$output" "Error: tag '-foo' in new.tags: tag starting with '-' forbidden"
test_expect_code 1 "Invalid tags set exit code" \
- "NOTMUCH_NEW 2>&1"
+ "NOTMUCH_NEW --debug 2>&1"
notmuch config set new.tags $OLDCONFIG
test_begin_subtest "Xapian exception: read only files"
chmod u-w ${MAIL_DIR}/.notmuch/xapian/*.DB
-output=$(NOTMUCH_NEW 2>&1 | sed 's/: .*$//' )
+output=$(NOTMUCH_NEW --debug 2>&1 | sed 's/: .*$//' )
chmod u+w ${MAIL_DIR}/.notmuch/xapian/*.DB
test_expect_equal "$output" "A Xapian exception occurred opening database"
diff --git a/test/T060-count.sh b/test/T060-count.sh
index da86c8cc..3fec94e8 100755
--- a/test/T060-count.sh
+++ b/test/T060-count.sh
@@ -1,6 +1,6 @@
#!/usr/bin/env bash
test_description='"notmuch count" for messages and threads'
-. ./test-lib.sh
+. ./test-lib.sh || exit 1
add_email_corpus
@@ -93,5 +93,35 @@ notmuch count --output=messages >>EXPECTED
notmuch count --output=messages tag:inbox >>EXPECTED
test_expect_equal_file EXPECTED OUTPUT
+backup_database
+test_begin_subtest "error message for database open"
+dd if=/dev/zero of="${MAIL_DIR}/.notmuch/xapian/postlist.DB" count=3
+notmuch count '*' 2>OUTPUT 1>/dev/null
+output=$(sed 's/^\(A Xapian exception [^:]*\):.*$/\1/' OUTPUT)
+test_expect_equal "${output}" "A Xapian exception occurred opening database"
+restore_database
+
+cat <<EOF > count-files.gdb
+set breakpoint pending on
+break count_files
+commands
+shell cp /dev/null ${MAIL_DIR}/.notmuch/xapian/postlist.DB
+continue
+end
+run
+EOF
+
+backup_database
+test_begin_subtest "error message from query_search_messages"
+gdb --batch-silent --return-child-result -x count-files.gdb \
+ --args notmuch count --output=files '*' 2>OUTPUT 1>/dev/null
+cat <<EOF > EXPECTED
+notmuch count: A Xapian exception occurred
+A Xapian exception occurred performing query
+Query string was: *
+EOF
+sed 's/^\(A Xapian exception [^:]*\):.*$/\1/' < OUTPUT > OUTPUT.clean
+test_expect_equal_file EXPECTED OUTPUT.clean
+restore_database
test_done
diff --git a/test/T070-insert.sh b/test/T070-insert.sh
index 74f19552..e7ec6a6c 100755
--- a/test/T070-insert.sh
+++ b/test/T070-insert.sh
@@ -1,6 +1,6 @@
#!/usr/bin/env bash
test_description='"notmuch insert"'
-. ./test-lib.sh
+. ./test-lib.sh || exit 1
test_require_external_prereq gdb
@@ -188,7 +188,7 @@ notmuch config set new.tags $OLDCONFIG
# DUPLICATE_MESSAGE_ID is not tested here, because it should actually pass.
for code in OUT_OF_MEMORY XAPIAN_EXCEPTION FILE_NOT_EMAIL \
- READ_ONLY_DATABASE UPGRADE_REQUIRED; do
+ READ_ONLY_DATABASE UPGRADE_REQUIRED PATH_ERROR; do
gen_insert_msg
cat <<EOF > index-file-$code.gdb
set breakpoint pending on
diff --git a/test/T080-search.sh b/test/T080-search.sh
index 05027fb0..5e8b20ce 100755
--- a/test/T080-search.sh
+++ b/test/T080-search.sh
@@ -1,6 +1,6 @@
#!/usr/bin/env bash
test_description='"notmuch search" in several variations'
-. ./test-lib.sh
+. ./test-lib.sh || exit 1
add_email_corpus
diff --git a/test/T090-search-output.sh b/test/T090-search-output.sh
index fe2ec9af..a28eaf20 100755
--- a/test/T090-search-output.sh
+++ b/test/T090-search-output.sh
@@ -1,6 +1,6 @@
#!/usr/bin/env bash
test_description='various settings for "notmuch search --output="'
-. ./test-lib.sh
+. ./test-lib.sh || exit 1
add_email_corpus
diff --git a/test/T095-address.sh b/test/T095-address.sh
index ed0cac78..a194faf3 100755
--- a/test/T095-address.sh
+++ b/test/T095-address.sh
@@ -1,6 +1,6 @@
#!/usr/bin/env bash
test_description='"notmuch address" in several variants'
-. ./test-lib.sh
+. ./test-lib.sh || exit 1
add_email_corpus
@@ -145,4 +145,148 @@ cat <<EOF >EXPECTED
EOF
test_expect_equal_file OUTPUT EXPECTED
+test_begin_subtest "--deduplicate=no --sort=oldest-first --output=sender"
+notmuch address --deduplicate=no --sort=oldest-first --output=sender '*' >OUTPUT
+cat <<EOF >EXPECTED
+Mikhail Gusarov <dottedmag@dottedmag.net>
+Mikhail Gusarov <dottedmag@dottedmag.net>
+Carl Worth <cworth@cworth.org>
+Lars Kellogg-Stedman <lars@seas.harvard.edu>
+Mikhail Gusarov <dottedmag@dottedmag.net>
+Alex Botero-Lowry <alex.boterolowry@gmail.com>
+Carl Worth <cworth@cworth.org>
+Lars Kellogg-Stedman <lars@seas.harvard.edu>
+Mikhail Gusarov <dottedmag@dottedmag.net>
+Mikhail Gusarov <dottedmag@dottedmag.net>
+Keith Packard <keithp@keithp.com>
+Keith Packard <keithp@keithp.com>
+Keith Packard <keithp@keithp.com>
+Jan Janak <jan@ryngle.com>
+Jan Janak <jan@ryngle.com>
+Jan Janak <jan@ryngle.com>
+Israel Herraiz <isra@herraiz.org>
+Adrian Perez de Castro <aperez@igalia.com>
+Aron Griffis <agriffis@n01se.net>
+Ingmar Vanhassel <ingmar@exherbo.org>
+Alex Botero-Lowry <alex.boterolowry@gmail.com>
+Lars Kellogg-Stedman <lars@seas.harvard.edu>
+Lars Kellogg-Stedman <lars@seas.harvard.edu>
+Lars Kellogg-Stedman <lars@seas.harvard.edu>
+Stewart Smith <stewart@flamingspork.com>
+Stewart Smith <stewart@flamingspork.com>
+Keith Packard <keithp@keithp.com>
+Keith Packard <keithp@keithp.com>
+Keith Packard <keithp@keithp.com>
+Stewart Smith <stewart@flamingspork.com>
+Jjgod Jiang <gzjjgod@gmail.com>
+Jan Janak <jan@ryngle.com>
+Rolland Santimano <rollandsantimano@yahoo.com>
+Alexander Botero-Lowry <alex.boterolowry@gmail.com>
+Jjgod Jiang <gzjjgod@gmail.com>
+Alexander Botero-Lowry <alex.boterolowry@gmail.com>
+Alexander Botero-Lowry <alex.boterolowry@gmail.com>
+Keith Packard <keithp@keithp.com>
+Alexander Botero-Lowry <alex.boterolowry@gmail.com>
+Carl Worth <cworth@cworth.org>
+Carl Worth <cworth@cworth.org>
+Carl Worth <cworth@cworth.org>
+Carl Worth <cworth@cworth.org>
+Carl Worth <cworth@cworth.org>
+Carl Worth <cworth@cworth.org>
+Carl Worth <cworth@cworth.org>
+Carl Worth <cworth@cworth.org>
+Carl Worth <cworth@cworth.org>
+Carl Worth <cworth@cworth.org>
+Chris Wilson <chris@chris-wilson.co.uk>
+Olivier Berger <olivier.berger@it-sudparis.eu>
+François Boulogne <boulogne.f@gmail.com>
+EOF
+test_expect_equal_file OUTPUT EXPECTED
+
+test_begin_subtest "--deduplicate=no --sort=newest-first --output=sender --output=recipients"
+notmuch address --deduplicate=no --sort=newest-first --output=sender --output=recipients path:foo/new >OUTPUT
+cat <<EOF >EXPECTED
+Mikhail Gusarov <dottedmag@dottedmag.net>
+notmuch@notmuchmail.org
+Mikhail Gusarov <dottedmag@dottedmag.net>
+notmuch@notmuchmail.org
+Lars Kellogg-Stedman <lars@seas.harvard.edu>
+notmuch@notmuchmail.org
+EOF
+test_expect_equal_file OUTPUT EXPECTED
+
+test_begin_subtest "--deduplicate=address --output=sender --output=recipients"
+notmuch address --deduplicate=address --output=sender --output=recipients '*' | sort >OUTPUT
+cat <<EOF >EXPECTED
+"Discussion about the Arch User Repository (AUR)" <aur-general@archlinux.org>
+Adrian Perez de Castro <aperez@igalia.com>
+Alexander Botero-Lowry <alex.boterolowry@gmail.com>
+Allan McRae <allan@archlinux.org>
+Aron Griffis <agriffis@n01se.net>
+Carl Worth <cworth@cworth.org>
+Chris Wilson <chris@chris-wilson.co.uk>
+François Boulogne <boulogne.f@gmail.com>
+Ingmar Vanhassel <ingmar@exherbo.org>
+Israel Herraiz <isra@herraiz.org>
+Jan Janak <jan@ryngle.com>
+Jjgod Jiang <gzjjgod@gmail.com>
+Keith Packard <keithp@keithp.com>
+Lars Kellogg-Stedman <lars@seas.harvard.edu>
+Mikhail Gusarov <dottedmag@dottedmag.net>
+Olivier Berger <olivier.berger@it-sudparis.eu>
+Rolland Santimano <rollandsantimano@yahoo.com>
+Stewart Smith <stewart@flamingspork.com>
+notmuch@notmuchmail.org
+EOF
+test_expect_equal_file OUTPUT EXPECTED
+
+generate_message '[from]="Foo Bar <foo.bar@example.com>"'
+generate_message '[from]="Foo Bar <Foo.Bar@Example.Com>"'
+generate_message '[from]="Foo Bar <foo.bar@example.com>"'
+generate_message '[from]="Bar <Foo.Bar@Example.Com>"'
+generate_message '[from]="Foo <foo.bar@example.com>"'
+generate_message '[from]="<foo.bar@example.com>"'
+generate_message '[from]="foo.bar@example.com"'
+generate_message '[from]="Baz <foo.bar+baz@example.com>"'
+generate_message '[from]="Foo Bar <foo.bar+baz@example.com>"'
+generate_message '[from]="Baz <foo.bar+baz@example.com>"'
+notmuch new > /dev/null
+
+test_begin_subtest "--deduplicate=no --output=sender"
+notmuch address --deduplicate=no --output=sender from:example.com | sort >OUTPUT
+cat <<EOF >EXPECTED
+Bar <Foo.Bar@Example.Com>
+Baz <foo.bar+baz@example.com>
+Baz <foo.bar+baz@example.com>
+Foo <foo.bar@example.com>
+Foo Bar <Foo.Bar@Example.Com>
+Foo Bar <foo.bar+baz@example.com>
+Foo Bar <foo.bar@example.com>
+Foo Bar <foo.bar@example.com>
+foo.bar@example.com
+foo.bar@example.com
+EOF
+test_expect_equal_file OUTPUT EXPECTED
+
+test_begin_subtest "--deduplicate=mailbox --output=sender --output=count"
+notmuch address --deduplicate=mailbox --output=sender --output=count from:example.com | sort -n >OUTPUT
+cat <<EOF >EXPECTED
+1 Bar <Foo.Bar@Example.Com>
+1 Foo <foo.bar@example.com>
+1 Foo Bar <Foo.Bar@Example.Com>
+1 Foo Bar <foo.bar+baz@example.com>
+2 Baz <foo.bar+baz@example.com>
+2 Foo Bar <foo.bar@example.com>
+2 foo.bar@example.com
+EOF
+test_expect_equal_file OUTPUT EXPECTED
+
+test_begin_subtest "--deduplicate=address --output=sender --output=count"
+notmuch address --deduplicate=address --output=sender --output=count from:example.com | sort -n >OUTPUT
+cat <<EOF >EXPECTED
+3 Baz <foo.bar+baz@example.com>
+7 Foo Bar <foo.bar@example.com>
+EOF
+test_expect_equal_file OUTPUT EXPECTED
+
test_done
diff --git a/test/T100-search-by-folder.sh b/test/T100-search-by-folder.sh
index 583bdf5e..2844ec61 100755
--- a/test/T100-search-by-folder.sh
+++ b/test/T100-search-by-folder.sh
@@ -1,6 +1,6 @@
#!/usr/bin/env bash
test_description='"notmuch search" by folder: and path: (with variations)'
-. ./test-lib.sh
+. ./test-lib.sh || exit 1
add_message '[dir]=bad' '[subject]="To the bone"'
add_message '[dir]=.' '[subject]="Top level"'
diff --git a/test/T110-search-position-overlap-bug.sh b/test/T110-search-position-overlap-bug.sh
index 5da6ad6f..2a4238f9 100755
--- a/test/T110-search-position-overlap-bug.sh
+++ b/test/T110-search-position-overlap-bug.sh
@@ -18,7 +18,7 @@
# id:3wd4o8wa7fx.fsf@testarossa.amd.com
test_description='that notmuch does not overlap term positions'
-. ./test-lib.sh
+. ./test-lib.sh || exit 1
add_message '[to]="a@b.c, x@y.z"'
diff --git a/test/T120-search-insufficient-from-quoting.sh b/test/T120-search-insufficient-from-quoting.sh
index e83ea3d1..4862d826 100755
--- a/test/T120-search-insufficient-from-quoting.sh
+++ b/test/T120-search-insufficient-from-quoting.sh
@@ -1,6 +1,6 @@
#!/usr/bin/env bash
test_description='messages with unquoted . in name'
-. ./test-lib.sh
+. ./test-lib.sh || exit 1
add_message \
'[from]="Some.Name for Someone <bugs@quoting.com>"' \
diff --git a/test/T130-search-limiting.sh b/test/T130-search-limiting.sh
index 303762cf..c8986f4e 100755
--- a/test/T130-search-limiting.sh
+++ b/test/T130-search-limiting.sh
@@ -1,6 +1,6 @@
#!/usr/bin/env bash
test_description='"notmuch search" --offset and --limit parameters'
-. ./test-lib.sh
+. ./test-lib.sh || exit 1
add_email_corpus
diff --git a/test/T140-excludes.sh b/test/T140-excludes.sh
index 8bbbc2dd..f91d4d7f 100755
--- a/test/T140-excludes.sh
+++ b/test/T140-excludes.sh
@@ -1,6 +1,6 @@
#!/usr/bin/env bash
test_description='"notmuch search, count and show" with excludes in several variations'
-. ./test-lib.sh
+. ./test-lib.sh || exit 1
# Generates a thread consisting of a top level message and 'length'
# replies. The subject of the top message 'subject: top message"
diff --git a/test/T150-tagging.sh b/test/T150-tagging.sh
index 4a2673d4..821d3933 100755
--- a/test/T150-tagging.sh
+++ b/test/T150-tagging.sh
@@ -1,6 +1,6 @@
#!/usr/bin/env bash
test_description='"notmuch tag"'
-. ./test-lib.sh
+. ./test-lib.sh || exit 1
add_message '[subject]=One'
add_message '[subject]=Two'
diff --git a/test/T160-json.sh b/test/T160-json.sh
index c1cf649d..b346f37e 100755
--- a/test/T160-json.sh
+++ b/test/T160-json.sh
@@ -1,6 +1,6 @@
#!/usr/bin/env bash
test_description="--format=json output"
-. ./test-lib.sh
+. ./test-lib.sh || exit 1
test_begin_subtest "Show message: json"
add_message "[subject]=\"json-show-subject\"" "[date]=\"Sat, 01 Jan 2000 12:00:00 -0000\"" "[bcc]=\"test_suite+bcc@notmuchmail.org\"" "[reply-to]=\"test_suite+replyto@notmuchmail.org\"" "[body]=\"json-show-message\""
diff --git a/test/T170-sexp.sh b/test/T170-sexp.sh
index 667e3195..800ebc63 100755
--- a/test/T170-sexp.sh
+++ b/test/T170-sexp.sh
@@ -1,6 +1,6 @@
#!/usr/bin/env bash
test_description="--format=sexp output"
-. ./test-lib.sh
+. ./test-lib.sh || exit 1
test_begin_subtest "Show message: sexp"
add_message "[subject]=\"sexp-show-subject\"" "[date]=\"Sat, 01 Jan 2000 12:00:00 -0000\"" "[bcc]=\"test_suite+bcc@notmuchmail.org\"" "[reply-to]=\"test_suite+replyto@notmuchmail.org\"" "[body]=\"sexp-show-message\""
diff --git a/test/T180-text.sh b/test/T180-text.sh
index b5ccefc9..3a265dbd 100755
--- a/test/T180-text.sh
+++ b/test/T180-text.sh
@@ -1,6 +1,6 @@
#!/usr/bin/env bash
test_description="--format=text output"
-. ./test-lib.sh
+. ./test-lib.sh || exit 1
test_begin_subtest "Show message: text"
add_message "[subject]=\"text-show-subject\"" "[date]=\"Sat, 01 Jan 2000 12:00:00 -0000\"" "[body]=\"text-show-message\""
diff --git a/test/T190-multipart.sh b/test/T190-multipart.sh
index ad8d29ea..7c4c9f71 100755
--- a/test/T190-multipart.sh
+++ b/test/T190-multipart.sh
@@ -1,6 +1,6 @@
#!/usr/bin/env bash
test_description="output of multipart message"
-. ./test-lib.sh
+. ./test-lib.sh || exit 1
cat <<EOF > embedded_message
From: Carl Worth <cworth@cworth.org>
diff --git a/test/T200-thread-naming.sh b/test/T200-thread-naming.sh
index dcfc1b36..132c1d77 100755
--- a/test/T200-thread-naming.sh
+++ b/test/T200-thread-naming.sh
@@ -1,6 +1,6 @@
#!/usr/bin/env bash
test_description="naming of threads with changing subject"
-. ./test-lib.sh
+. ./test-lib.sh || exit 1
test_begin_subtest "Initial thread name (oldest-first search)"
add_message '[subject]="thread-naming: Initial thread subject"' \
diff --git a/test/T205-author-naming.sh b/test/T205-author-naming.sh
index cb678ae8..69d8dc50 100755
--- a/test/T205-author-naming.sh
+++ b/test/T205-author-naming.sh
@@ -1,6 +1,6 @@
#!/usr/bin/env bash
test_description="naming of authors with unusual addresses"
-. ./test-lib.sh
+. ./test-lib.sh || exit 1
test_begin_subtest "Add author with empty quoted real name"
add_message '[subject]="author-naming: Initial thread subject"' \
diff --git a/test/T210-raw.sh b/test/T210-raw.sh
index daf5735c..dfea2d19 100755
--- a/test/T210-raw.sh
+++ b/test/T210-raw.sh
@@ -1,7 +1,7 @@
#!/usr/bin/env bash
test_description='notmuch show --format=raw'
-. ./test-lib.sh
+. ./test-lib.sh || exit 1
add_message
add_message
diff --git a/test/T220-reply.sh b/test/T220-reply.sh
index b0d854a1..30b78f67 100755
--- a/test/T220-reply.sh
+++ b/test/T220-reply.sh
@@ -1,6 +1,6 @@
#!/usr/bin/env bash
test_description="\"notmuch reply\" in several variations"
-. ./test-lib.sh
+. ./test-lib.sh || exit 1
test_begin_subtest "Basic reply"
add_message '[from]="Sender <sender@example.com>"' \
diff --git a/test/T230-reply-to-sender.sh b/test/T230-reply-to-sender.sh
index 30e5e385..608334dc 100755
--- a/test/T230-reply-to-sender.sh
+++ b/test/T230-reply-to-sender.sh
@@ -1,6 +1,6 @@
#!/usr/bin/env bash
test_description="\"notmuch reply --reply-to=sender\" in several variations"
-. ./test-lib.sh
+. ./test-lib.sh || exit 1
test_begin_subtest "Basic reply-to-sender"
add_message '[from]="Sender <sender@example.com>"' \
diff --git a/test/T240-dump-restore.sh b/test/T240-dump-restore.sh
index efe463ea..e6976ff8 100755
--- a/test/T240-dump-restore.sh
+++ b/test/T240-dump-restore.sh
@@ -1,6 +1,6 @@
#!/usr/bin/env bash
test_description="\"notmuch dump\" and \"notmuch restore\""
-. ./test-lib.sh
+. ./test-lib.sh || exit 1
add_email_corpus
diff --git a/test/T250-uuencode.sh b/test/T250-uuencode.sh
index b3e1ac19..6f45d395 100755
--- a/test/T250-uuencode.sh
+++ b/test/T250-uuencode.sh
@@ -1,6 +1,6 @@
#!/usr/bin/env bash
test_description="handling of uuencoded data"
-. ./test-lib.sh
+. ./test-lib.sh || exit 1
add_message [subject]=uuencodetest '[date]="Sat, 01 Jan 2000 12:00:00 -0000"' \
'[body]="This message is used to ensure that notmuch correctly handles a
diff --git a/test/T260-thread-order.sh b/test/T260-thread-order.sh
index 5239bd44..f720c998 100755
--- a/test/T260-thread-order.sh
+++ b/test/T260-thread-order.sh
@@ -1,6 +1,6 @@
#!/usr/bin/env bash
test_description="threading when messages received out of order"
-. ./test-lib.sh
+. ./test-lib.sh || exit 1
# Generate all single-root four message thread structures. We'll use
# this for multiple tests below.
diff --git a/test/T270-author-order.sh b/test/T270-author-order.sh
index 6ffeffc7..9124ece6 100755
--- a/test/T270-author-order.sh
+++ b/test/T270-author-order.sh
@@ -1,6 +1,6 @@
#!/usr/bin/env bash
test_description="author reordering;"
-. ./test-lib.sh
+. ./test-lib.sh || exit 1
test_begin_subtest "Adding parent message"
generate_message [body]=findme [id]=new-parent-id [subject]=author-reorder-threadtest '[from]="User <user@example.com>"' '[date]="Sat, 01 Jan 2000 12:00:00 -0000"'
diff --git a/test/T280-from-guessing.sh b/test/T280-from-guessing.sh
index 6dfaa40a..7c562fb9 100755
--- a/test/T280-from-guessing.sh
+++ b/test/T280-from-guessing.sh
@@ -1,6 +1,6 @@
#!/usr/bin/env bash
test_description="From line heuristics (with multiple configured addresses)"
-. ./test-lib.sh
+. ./test-lib.sh || exit 1
test_begin_subtest "Magic from guessing (nothing to go on)"
add_message '[from]="Sender <sender@example.com>"' \
diff --git a/test/T290-long-id.sh b/test/T290-long-id.sh
index 85e620fa..1fb7c037 100755
--- a/test/T290-long-id.sh
+++ b/test/T290-long-id.sh
@@ -1,6 +1,6 @@
#!/usr/bin/env bash
test_description="messages with ridiculously-long message IDs"
-. ./test-lib.sh
+. ./test-lib.sh || exit 1
test_begin_subtest "Referencing long ID before adding"
generate_message '[subject]="Reference of ridiculously-long message ID"' \
diff --git a/test/T300-encoding.sh b/test/T300-encoding.sh
index b6c86bf0..8d201c7e 100755
--- a/test/T300-encoding.sh
+++ b/test/T300-encoding.sh
@@ -1,6 +1,6 @@
#!/usr/bin/env bash
test_description="encoding issues"
-. ./test-lib.sh
+. ./test-lib.sh || exit 1
test_begin_subtest "Message with text of unknown charset"
add_message '[content-type]="text/plain; charset=unknown-8bit"' \
diff --git a/test/T310-emacs.sh b/test/T310-emacs.sh
index d72799b4..61bc369a 100755
--- a/test/T310-emacs.sh
+++ b/test/T310-emacs.sh
@@ -1,7 +1,7 @@
#!/usr/bin/env bash
test_description="emacs interface"
-. ./test-lib.sh
+. ./test-lib.sh || exit 1
EXPECTED=$TEST_DIRECTORY/emacs.expected-output
diff --git a/test/T320-emacs-large-search-buffer.sh b/test/T320-emacs-large-search-buffer.sh
index 8b1251fe..3fd6958a 100755
--- a/test/T320-emacs-large-search-buffer.sh
+++ b/test/T320-emacs-large-search-buffer.sh
@@ -1,6 +1,6 @@
#!/usr/bin/env bash
test_description="Emacs with large search results buffer"
-. ./test-lib.sh
+. ./test-lib.sh || exit 1
x=xxxxxxxxxx # 10
x=$x$x$x$x$x$x$x$x$x$x # 100
diff --git a/test/T330-emacs-subject-to-filename.sh b/test/T330-emacs-subject-to-filename.sh
index 230c324d..517fa839 100755
--- a/test/T330-emacs-subject-to-filename.sh
+++ b/test/T330-emacs-subject-to-filename.sh
@@ -1,7 +1,7 @@
#!/usr/bin/env bash
test_description="emacs: mail subject to filename"
-. ./test-lib.sh
+. ./test-lib.sh || exit 1
# emacs server can't be started in a child process with $(test_emacs ...)
test_emacs '(ignore)' > /dev/null
diff --git a/test/T340-maildir-sync.sh b/test/T340-maildir-sync.sh
index 3186e70f..efeaa3f6 100755
--- a/test/T340-maildir-sync.sh
+++ b/test/T340-maildir-sync.sh
@@ -2,7 +2,7 @@
test_description="maildir synchronization"
-. ./test-lib.sh
+. ./test-lib.sh || exit 1
# Create the expected maildir structure
mkdir $MAIL_DIR/cur
diff --git a/test/T350-crypto.sh b/test/T350-crypto.sh
index 477b397e..3656cce9 100755
--- a/test/T350-crypto.sh
+++ b/test/T350-crypto.sh
@@ -5,7 +5,7 @@
# - verification of signatures from expired/revoked keys
test_description='PGP/MIME signature verification and decryption'
-. ./test-lib.sh
+. ./test-lib.sh || exit 1
add_gnupg_home ()
{
diff --git a/test/T360-symbol-hiding.sh b/test/T360-symbol-hiding.sh
index d2b5d1f5..4ec0ea65 100755
--- a/test/T360-symbol-hiding.sh
+++ b/test/T360-symbol-hiding.sh
@@ -9,18 +9,19 @@
test_description='exception symbol hiding'
-. ./test-lib.sh
+. ./test-lib.sh || exit 1
-run_test(){
- result=$(LD_LIBRARY_PATH="$TEST_DIRECTORY/../lib${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}" $TEST_DIRECTORY/symbol-test 2>&1)
-}
+test_begin_subtest 'running test' run_test
+mkdir -p ${PWD}/fakedb/.notmuch
+( LD_LIBRARY_PATH="$TEST_DIRECTORY/../lib${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}" \
+ $TEST_DIRECTORY/symbol-test ${PWD}/fakedb ${PWD}/nonexistent \
+ 2>&1 | sed "s,${PWD},CWD,g") > OUTPUT
-output="A Xapian exception occurred opening database: Couldn't stat 'fakedb/.notmuch/xapian'
-caught No chert database found at path \`./nonexistent'"
-
-mkdir -p fakedb/.notmuch
-
-test_expect_success 'running test' run_test
+cat <<EOF > EXPECTED
+A Xapian exception occurred opening database: Couldn't stat 'CWD/fakedb/.notmuch/xapian'
+caught No chert database found at path \`CWD/nonexistent'
+EOF
+test_expect_equal_file EXPECTED OUTPUT
test_begin_subtest 'checking output'
test_expect_equal "$result" "$output"
diff --git a/test/T370-search-folder-coherence.sh b/test/T370-search-folder-coherence.sh
index 5e72a6cc..da4ec2b8 100755
--- a/test/T370-search-folder-coherence.sh
+++ b/test/T370-search-folder-coherence.sh
@@ -1,6 +1,6 @@
#!/usr/bin/env bash
test_description='folder tags removed and added through file renames remain consistent'
-. ./test-lib.sh
+. ./test-lib.sh || exit 1
test_begin_subtest "No new messages"
output=$(NOTMUCH_NEW)
diff --git a/test/T380-atomicity.sh b/test/T380-atomicity.sh
index ee1e2f43..845dfde7 100755
--- a/test/T380-atomicity.sh
+++ b/test/T380-atomicity.sh
@@ -1,6 +1,6 @@
#!/usr/bin/env bash
test_description='atomicity'
-. ./test-lib.sh
+. ./test-lib.sh || exit 1
# This script tests the effects of killing and restarting "notmuch
# new" at arbitrary points. If notmuch new is properly atomic, the
diff --git a/test/T390-python.sh b/test/T390-python.sh
index 3f03a2e3..4726bc36 100755
--- a/test/T390-python.sh
+++ b/test/T390-python.sh
@@ -1,6 +1,8 @@
#!/usr/bin/env bash
test_description="python bindings"
-. ./test-lib.sh
+. ./test-lib.sh || exit 1
+
+test_require_external_prereq ${NOTMUCH_PYTHON}
add_email_corpus
@@ -11,7 +13,7 @@ db = notmuch.Database(mode=notmuch.Database.MODE.READ_ONLY)
q_new = notmuch.Query(db, 'tag:inbox')
q_new.set_sort(notmuch.Query.SORT.OLDEST_FIRST)
for t in q_new.search_threads():
- print t.get_thread_id()
+ print (t.get_thread_id())
EOF
notmuch search --sort=oldest-first --output=threads tag:inbox | sed s/^thread:// > EXPECTED
test_expect_equal_file OUTPUT EXPECTED
@@ -23,7 +25,7 @@ db = notmuch.Database(mode=notmuch.Database.MODE.READ_ONLY)
q_new = notmuch.Query(db, 'tag:inbox')
q_new.set_sort(notmuch.Query.SORT.OLDEST_FIRST)
for m in q_new.search_messages():
- print m.get_message_id()
+ print (m.get_message_id())
EOF
notmuch search --sort=oldest-first --output=messages tag:inbox | sed s/^id:// > EXPECTED
test_expect_equal_file OUTPUT EXPECTED
@@ -32,7 +34,7 @@ test_begin_subtest "get non-existent file"
test_python <<EOF
import notmuch
db = notmuch.Database(mode=notmuch.Database.MODE.READ_ONLY)
-print db.find_message_by_filename("i-dont-exist")
+print (db.find_message_by_filename("i-dont-exist"))
EOF
test_expect_equal "$(cat OUTPUT)" "None"
diff --git a/test/T395-ruby.sh b/test/T395-ruby.sh
new file mode 100755
index 00000000..d5cade8f
--- /dev/null
+++ b/test/T395-ruby.sh
@@ -0,0 +1,86 @@
+#!/usr/bin/env bash
+test_description="ruby bindings"
+. ./test-lib.sh || exit 1
+
+if [ "${NOTMUCH_HAVE_RUBY_DEV}" = "0" ]; then
+ test_subtest_missing_external_prereq_["ruby development files"]=t
+fi
+
+add_email_corpus
+
+test_begin_subtest "compare thread ids"
+test_ruby <<"EOF"
+require 'notmuch'
+$maildir = ENV['MAIL_DIR']
+if not $maildir then
+ abort('environment variable MAIL_DIR must be set')
+end
+@db = Notmuch::Database.new($maildir)
+@q = @db.query('tag:inbox')
+@q.sort = Notmuch::SORT_OLDEST_FIRST
+for t in @q.search_threads do
+ print t.thread_id, "\n"
+end
+EOF
+notmuch search --sort=oldest-first --output=threads tag:inbox | sed s/^thread:// > EXPECTED
+test_expect_equal_file OUTPUT EXPECTED
+
+test_begin_subtest "compare message ids"
+test_ruby <<"EOF"
+require 'notmuch'
+$maildir = ENV['MAIL_DIR']
+if not $maildir then
+ abort('environment variable MAIL_DIR must be set')
+end
+@db = Notmuch::Database.new($maildir)
+@q = @db.query('tag:inbox')
+@q.sort = Notmuch::SORT_OLDEST_FIRST
+for m in @q.search_messages do
+ print m.message_id, "\n"
+end
+EOF
+notmuch search --sort=oldest-first --output=messages tag:inbox | sed s/^id:// > EXPECTED
+test_expect_equal_file OUTPUT EXPECTED
+
+test_begin_subtest "get non-existent file"
+test_ruby <<"EOF"
+require 'notmuch'
+$maildir = ENV['MAIL_DIR']
+if not $maildir then
+ abort('environment variable MAIL_DIR must be set')
+end
+@db = Notmuch::Database.new($maildir)
+result = @db.find_message_by_filename('i-dont-exist')
+print (result == nil)
+EOF
+test_expect_equal "$(cat OUTPUT)" "true"
+
+test_begin_subtest "count messages"
+test_ruby <<"EOF"
+require 'notmuch'
+$maildir = ENV['MAIL_DIR']
+if not $maildir then
+ abort('environment variable MAIL_DIR must be set')
+end
+@db = Notmuch::Database.new($maildir)
+@q = @db.query('tag:inbox')
+print @q.count_messages(),"\n"
+EOF
+notmuch count --output=messages tag:inbox > EXPECTED
+test_expect_equal_file OUTPUT EXPECTED
+
+test_begin_subtest "count threads"
+test_ruby <<"EOF"
+require 'notmuch'
+$maildir = ENV['MAIL_DIR']
+if not $maildir then
+ abort('environment variable MAIL_DIR must be set')
+end
+@db = Notmuch::Database.new($maildir)
+@q = @db.query('tag:inbox')
+print @q.count_threads(),"\n"
+EOF
+notmuch count --output=threads tag:inbox > EXPECTED
+test_expect_equal_file OUTPUT EXPECTED
+
+test_done
diff --git a/test/T400-hooks.sh b/test/T400-hooks.sh
index 1adab2f0..ed119131 100755
--- a/test/T400-hooks.sh
+++ b/test/T400-hooks.sh
@@ -1,6 +1,6 @@
#!/usr/bin/env bash
test_description='hooks'
-. ./test-lib.sh
+. ./test-lib.sh || exit 1
HOOK_DIR=${MAIL_DIR}/.notmuch/hooks
diff --git a/test/T410-argument-parsing.sh b/test/T410-argument-parsing.sh
index 2e5d7ae3..f8ff8ff9 100755
--- a/test/T410-argument-parsing.sh
+++ b/test/T410-argument-parsing.sh
@@ -1,6 +1,6 @@
#!/usr/bin/env bash
test_description="argument parsing"
-. ./test-lib.sh
+. ./test-lib.sh || exit 1
test_begin_subtest "sanity check"
$TEST_DIRECTORY/arg-test pos1 --keyword=one --string=foo pos2 --int=7 --flag=one --flag=three > OUTPUT
diff --git a/test/T420-emacs-test-functions.sh b/test/T420-emacs-test-functions.sh
index ca4a7988..955c5f7f 100755
--- a/test/T420-emacs-test-functions.sh
+++ b/test/T420-emacs-test-functions.sh
@@ -1,7 +1,7 @@
#!/usr/bin/env bash
test_description="emacs test function sanity"
-. ./test-lib.sh
+. ./test-lib.sh || exit 1
test_begin_subtest "emacs test function sanity"
test_emacs_expect_t 't'
diff --git a/test/T430-emacs-address-cleaning.sh b/test/T430-emacs-address-cleaning.sh
index 04723467..664b79d2 100755
--- a/test/T430-emacs-address-cleaning.sh
+++ b/test/T430-emacs-address-cleaning.sh
@@ -1,7 +1,7 @@
#!/usr/bin/env bash
test_description="emacs address cleaning"
-. ./test-lib.sh
+. ./test-lib.sh || exit 1
test_begin_subtest "notmuch-test-address-clean part 1"
test_emacs_expect_t '(notmuch-test-address-cleaning-1)'
diff --git a/test/T440-emacs-hello.sh b/test/T440-emacs-hello.sh
index f7296166..a8ed2282 100755
--- a/test/T440-emacs-hello.sh
+++ b/test/T440-emacs-hello.sh
@@ -1,7 +1,7 @@
#!/usr/bin/env bash
test_description="emacs notmuch-hello view"
-. ./test-lib.sh
+. ./test-lib.sh || exit 1
EXPECTED=$TEST_DIRECTORY/emacs.expected-output
diff --git a/test/T450-emacs-show.sh b/test/T450-emacs-show.sh
index bfcd5efe..0342a874 100755
--- a/test/T450-emacs-show.sh
+++ b/test/T450-emacs-show.sh
@@ -1,7 +1,7 @@
#!/usr/bin/env bash
test_description="emacs notmuch-show view"
-. ./test-lib.sh
+. ./test-lib.sh || exit 1
EXPECTED=$TEST_DIRECTORY/emacs-show.expected-output
diff --git a/test/T455-emacs-charsets.sh b/test/T455-emacs-charsets.sh
index 3078f9c9..7624fa4d 100755
--- a/test/T455-emacs-charsets.sh
+++ b/test/T455-emacs-charsets.sh
@@ -1,7 +1,7 @@
#!/usr/bin/env bash
test_description="emacs notmuch-show charset handling"
-. ./test-lib.sh
+. ./test-lib.sh || exit 1
UTF8_YEN=$'\xef\xbf\xa5'
diff --git a/test/T460-emacs-tree.sh b/test/T460-emacs-tree.sh
index 8e9f37cb..b6181b51 100755
--- a/test/T460-emacs-tree.sh
+++ b/test/T460-emacs-tree.sh
@@ -1,7 +1,7 @@
#!/usr/bin/env bash
test_description="emacs tree view interface"
-. test-lib.sh
+. ./test-lib.sh || exit 1
EXPECTED=$TEST_DIRECTORY/tree.expected-output
diff --git a/test/T470-missing-headers.sh b/test/T470-missing-headers.sh
index cb38301c..e256c10a 100755
--- a/test/T470-missing-headers.sh
+++ b/test/T470-missing-headers.sh
@@ -1,6 +1,6 @@
#!/usr/bin/env bash
test_description='messages with missing headers'
-. ./test-lib.sh
+. ./test-lib.sh || exit 1
# Notmuch requires at least one of from, subject, or to or it will
# ignore the file. Generate two messages so that together they cover
diff --git a/test/T480-hex-escaping.sh b/test/T480-hex-escaping.sh
index ad50e1bc..10527b18 100755
--- a/test/T480-hex-escaping.sh
+++ b/test/T480-hex-escaping.sh
@@ -1,6 +1,6 @@
#!/usr/bin/env bash
test_description="hex encoding and decoding"
-. ./test-lib.sh
+. ./test-lib.sh || exit 1
test_begin_subtest "round trip"
find $TEST_DIRECTORY/corpus -type f -print | sort | xargs cat > EXPECTED
diff --git a/test/T490-parse-time-string.sh b/test/T490-parse-time-string.sh
index 6aa9d433..ab90fcc5 100755
--- a/test/T490-parse-time-string.sh
+++ b/test/T490-parse-time-string.sh
@@ -1,6 +1,6 @@
#!/usr/bin/env bash
test_description="date/time parser module"
-. ./test-lib.sh
+. ./test-lib.sh || exit 1
# Sanity/smoke tests for the date/time parser independent of notmuch
diff --git a/test/T500-search-date.sh b/test/T500-search-date.sh
index 70bcf344..f5cea421 100755
--- a/test/T500-search-date.sh
+++ b/test/T500-search-date.sh
@@ -1,6 +1,6 @@
#!/usr/bin/env bash
test_description="date:since..until queries"
-. ./test-lib.sh
+. ./test-lib.sh || exit 1
add_email_corpus
@@ -8,6 +8,10 @@ test_begin_subtest "Absolute date range"
output=$(notmuch search date:2010-12-16..12/16/2010 | notmuch_search_sanitize)
test_expect_equal "$output" "thread:XXX 2010-12-16 [1/1] Olivier Berger; Essai accentué (inbox unread)"
+test_begin_subtest "Absolute date range with 'same' operator"
+output=$(notmuch search date:2010-12-16..! | notmuch_search_sanitize)
+test_expect_equal "$output" "thread:XXX 2010-12-16 [1/1] Olivier Berger; Essai accentué (inbox unread)"
+
test_begin_subtest "Absolute time range with TZ"
notmuch search date:18-Nov-2009_02:19:26-0800..2009-11-18_04:49:52-06:00 | notmuch_search_sanitize > OUTPUT
cat <<EOF >EXPECTED
diff --git a/test/T510-thread-replies.sh b/test/T510-thread-replies.sh
index 1392fbed..5ab066ac 100755
--- a/test/T510-thread-replies.sh
+++ b/test/T510-thread-replies.sh
@@ -9,7 +9,7 @@ test_description='test of proper handling of in-reply-to and references headers'
# database is constructed properly, even in the presence of
# non-RFC-compliant headers'
-. ./test-lib.sh
+. ./test-lib.sh || exit 1
test_begin_subtest "Use References when In-Reply-To is broken"
add_message '[id]="foo@one.com"' \
diff --git a/test/T520-show.sh b/test/T520-show.sh
index 0657c993..fb232a32 100755
--- a/test/T520-show.sh
+++ b/test/T520-show.sh
@@ -1,7 +1,7 @@
#!/usr/bin/env bash
test_description='"notmuch show"'
-. ./test-lib.sh
+. ./test-lib.sh || exit 1
add_email_corpus
diff --git a/test/T530-upgrade.sh b/test/T530-upgrade.sh
index 6b42a690..7faf03d3 100755
--- a/test/T530-upgrade.sh
+++ b/test/T530-upgrade.sh
@@ -1,7 +1,7 @@
#!/usr/bin/env bash
test_description="database upgrade"
-. ./test-lib.sh
+. ./test-lib.sh || exit 1
dbtarball=database-v1.tar.xz
diff --git a/test/T550-db-features.sh b/test/T550-db-features.sh
index 5569768c..f94a660d 100755
--- a/test/T550-db-features.sh
+++ b/test/T550-db-features.sh
@@ -1,7 +1,7 @@
#!/usr/bin/env bash
test_description="database version and feature compatibility"
-. ./test-lib.sh
+. ./test-lib.sh || exit 1
test_begin_subtest "future database versions abort open"
${TEST_DIRECTORY}/make-db-version ${MAIL_DIR} 9999 ""
diff --git a/test/T560-lib-error.sh b/test/T560-lib-error.sh
index c99b17ed..c280939c 100755
--- a/test/T560-lib-error.sh
+++ b/test/T560-lib-error.sh
@@ -1,17 +1,7 @@
#!/usr/bin/env bash
test_description="error reporting for library"
-. ./test-lib.sh
-
-backup_database () {
- rm -rf notmuch-dir-backup
- cp -pR ${MAIL_DIR}/.notmuch notmuch-dir-backup
-}
-restore_database () {
- rm -rf ${MAIL_DIR}/.notmuch
- cp -pR notmuch-dir-backup ${MAIL_DIR}/.notmuch
-}
-
+. ./test-lib.sh || exit 1
add_email_corpus
@@ -35,7 +25,7 @@ Error: Cannot open a database for a NULL path.
EOF
test_expect_equal_file EXPECTED OUTPUT
-test_begin_subtest "Open nonexistent database"
+test_begin_subtest "Open relative path"
test_C <<'EOF'
#include <stdio.h>
#include <notmuch.h>
@@ -49,7 +39,43 @@ EOF
cat <<'EOF' >EXPECTED
== stdout ==
== stderr ==
-Error opening database at ./nonexistent/foo/.notmuch: No such file or directory
+Error: Database path must be absolute.
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "Create database in relative path"
+test_C <<'EOF'
+#include <stdio.h>
+#include <notmuch.h>
+int main (int argc, char** argv)
+{
+ notmuch_database_t *db;
+ notmuch_status_t stat;
+ stat = notmuch_database_create ("./nonexistent/foo", &db);
+}
+EOF
+cat <<'EOF' >EXPECTED
+== stdout ==
+== stderr ==
+Error: Database path must be absolute.
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "Open nonexistent database"
+test_C ${PWD}/nonexistent/foo <<'EOF'
+#include <stdio.h>
+#include <notmuch.h>
+int main (int argc, char** argv)
+{
+ notmuch_database_t *db;
+ notmuch_status_t stat;
+ stat = notmuch_database_open (argv[1], 0, 0);
+}
+EOF
+cat <<'EOF' >EXPECTED
+== stdout ==
+== stderr ==
+Error opening database at CWD/nonexistent/foo/.notmuch: No such file or directory
EOF
test_expect_equal_file EXPECTED OUTPUT
@@ -70,21 +96,21 @@ Error: Cannot create a database for a NULL path.
EOF
test_expect_equal_file EXPECTED OUTPUT
-test_begin_subtest "Create database in non-existant directory"
-test_C <<'EOF'
+test_begin_subtest "Create database in nonexistent directory"
+test_C ${PWD}/nonexistent/foo<<'EOF'
#include <stdio.h>
#include <notmuch.h>
int main (int argc, char** argv)
{
notmuch_database_t *db;
notmuch_status_t stat;
- stat = notmuch_database_create ("./nonexistent/foo", &db);
+ stat = notmuch_database_create (argv[1], &db);
}
EOF
cat <<'EOF' >EXPECTED
== stdout ==
== stderr ==
-Error: Cannot create database at ./nonexistent/foo: No such file or directory.
+Error: Cannot create database at CWD/nonexistent/foo: No such file or directory.
EOF
test_expect_equal_file EXPECTED OUTPUT
diff --git a/test/T570-revision-tracking.sh b/test/T570-revision-tracking.sh
new file mode 100755
index 00000000..0936011a
--- /dev/null
+++ b/test/T570-revision-tracking.sh
@@ -0,0 +1,93 @@
+#!/usr/bin/env bash
+test_description="database revision tracking"
+
+. ./test-lib.sh || exit 1
+
+add_email_corpus
+
+test_begin_subtest "notmuch_database_get_revision"
+test_C ${MAIL_DIR} <<'EOF'
+#include <stdio.h>
+#include <string.h>
+#include <notmuch.h>
+int main (int argc, char** argv)
+{
+ notmuch_database_t *db;
+ notmuch_status_t stat;
+ unsigned long revision;
+ const char *uuid;
+
+ unsigned long rev;
+
+ stat = notmuch_database_open (argv[1], NOTMUCH_DATABASE_MODE_READ_ONLY, &db);
+ if (stat)
+ fputs ("open failed\n", stderr);
+ revision = notmuch_database_get_revision (db, &uuid);
+ printf("%s\t%lu\n", uuid, revision);
+}
+EOF
+notmuch_uuid_sanitize < OUTPUT > CLEAN
+cat <<'EOF' >EXPECTED
+== stdout ==
+UUID 53
+== stderr ==
+EOF
+test_expect_equal_file EXPECTED CLEAN
+
+grep '^[0-9a-f]' OUTPUT > INITIAL_OUTPUT
+
+test_begin_subtest "output of count matches test code"
+notmuch count --lastmod '*' | cut -f2-3 > OUTPUT
+test_expect_equal_file INITIAL_OUTPUT OUTPUT
+
+test_begin_subtest "modification count increases"
+before=$(notmuch count --lastmod '*' | cut -f3)
+notmuch tag +a-random-tag-8743632 '*'
+after=$(notmuch count --lastmod '*' | cut -f3)
+result=$(($before < $after))
+test_expect_equal 1 ${result}
+
+notmuch count --lastmod '*' | cut -f2 > UUID
+
+test_expect_success 'search succeeds with correct uuid' \
+ "notmuch search --uuid=$(cat UUID) '*'"
+
+test_expect_success 'uuid works as global option ' \
+ "notmuch --uuid=$(cat UUID) search '*'"
+
+test_expect_code 1 'uuid works as global option II' \
+ "notmuch --uuid=this-is-no-uuid search '*'"
+
+test_expect_code 1 'search fails with incorrect uuid' \
+ "notmuch search --uuid=this-is-no-uuid '*'"
+
+test_expect_success 'show succeeds with correct uuid' \
+ "notmuch show --uuid=$(cat UUID) '*'"
+
+test_expect_code 1 'show fails with incorrect uuid' \
+ "notmuch show --uuid=this-is-no-uuid '*'"
+
+test_expect_success 'tag succeeds with correct uuid' \
+ "notmuch tag --uuid=$(cat UUID) +test '*'"
+
+test_expect_code 1 'tag fails with incorrect uuid' \
+ "notmuch tag --uuid=this-is-no-uuid '*' +test2"
+
+test_begin_subtest 'lastmod:0.. matches everything'
+total=$(notmuch count '*')
+modtotal=$(notmuch count lastmod:0..)
+test_expect_equal "$total" "$modtotal"
+
+test_begin_subtest 'lastmod:1000000.. matches nothing'
+modtotal=$(notmuch count lastmod:1000000..)
+test_expect_equal 0 "$modtotal"
+
+test_begin_subtest 'exclude one message using lastmod'
+lastmod=$(notmuch count --lastmod '*' | cut -f3)
+total=$(notmuch count '*')
+notmuch tag +4EFC743A.3060609@april.org id:4EFC743A.3060609@april.org
+subtotal=$(notmuch count lastmod:..$lastmod)
+result=$(($subtotal == $total-1))
+test_expect_equal 1 "$result"
+
+test_done
diff --git a/test/aggregate-results.sh b/test/aggregate-results.sh
index b016edb9..63228546 100755
--- a/test/aggregate-results.sh
+++ b/test/aggregate-results.sh
@@ -82,7 +82,10 @@ if [ "$skipped" != "0" ]; then
echo "$skipped $tests skipped."
fi
-if [ $success -gt 0 -a $fixed -eq 0 -a $failed -eq 0 -a $skipped -eq 0 ]
+# Note that we currently do not consider skipped tests as failing the
+# build.
+
+if [ $success -gt 0 -a $fixed -eq 0 -a $failed -eq 0 ]
then
exit 0
else
diff --git a/test/random-corpus.c b/test/random-corpus.c
index 790193d2..d74271d9 100644
--- a/test/random-corpus.c
+++ b/test/random-corpus.c
@@ -114,6 +114,25 @@ random_utf8_string (void *ctx, size_t char_count)
return buf;
}
+/* stubs since we cannot link with notmuch.o */
+const notmuch_opt_desc_t notmuch_shared_options[] = {
+ { 0, 0, 0, 0, 0 }
+};
+
+char *notmuch_requested_db_uuid = NULL;
+
+void
+notmuch_process_shared_options (unused (const char *dummy))
+{
+}
+
+int
+notmuch_minimal_options (unused (const char *subcommand),
+ unused (int argc),
+ unused (char **argv))
+{
+ return 0;
+}
int
main (int argc, char **argv)
diff --git a/test/symbol-test.cc b/test/symbol-test.cc
index f17ddc85..7454838b 100644
--- a/test/symbol-test.cc
+++ b/test/symbol-test.cc
@@ -3,23 +3,28 @@
#include <xapian.h>
#include <notmuch.h>
+int main (int argc, char** argv)
+{
+ notmuch_database_t *notmuch;
+ char *message = NULL;
-int main() {
- notmuch_database_t *notmuch;
- char *message = NULL;
+ if (argc != 3)
+ return 1;
- if (notmuch_database_open_verbose ("fakedb", NOTMUCH_DATABASE_MODE_READ_ONLY, &notmuch, &message))
- if (message) {
- fputs (message, stderr);
- free (message);
- }
+ if (notmuch_database_open_verbose (argv[1], NOTMUCH_DATABASE_MODE_READ_ONLY,
+ &notmuch, &message)) {
+ if (message) {
+ fputs (message, stderr);
+ free (message);
+ }
+ }
- try {
- (void) new Xapian::WritableDatabase("./nonexistent", Xapian::DB_OPEN);
- } catch (const Xapian::Error &error) {
- printf("caught %s\n", error.get_msg().c_str());
- return 0;
- }
+ try {
+ (void) new Xapian::WritableDatabase (argv[2], Xapian::DB_OPEN);
+ } catch (const Xapian::Error &error) {
+ printf("caught %s\n", error.get_msg().c_str());
+ return 0;
+ }
- return 1;
+ return 1;
}
diff --git a/test/test-lib-common.sh b/test/test-lib-common.sh
index f99ed111..5eb618c1 100644
--- a/test/test-lib-common.sh
+++ b/test/test-lib-common.sh
@@ -34,13 +34,25 @@ find_notmuch_path ()
done
}
+backup_database () {
+ test_name=$(basename $0 .sh)
+ rm -rf notmuch-dir-backup."$test_name"
+ cp -pR ${MAIL_DIR}/.notmuch notmuch-dir-backup."${test_name}"
+}
+
+restore_database () {
+ test_name=$(basename $0 .sh)
+ rm -rf ${MAIL_DIR}/.notmuch
+ cp -pR notmuch-dir-backup."${test_name}" ${MAIL_DIR}/.notmuch
+}
+
# Test the binaries we have just built. The tests are kept in
# test/ subdirectory and are run in 'trash directory' subdirectory.
TEST_DIRECTORY=$(pwd)
notmuch_path=`find_notmuch_path "$TEST_DIRECTORY"`
# configure output
-. $notmuch_path/sh.config
+. $notmuch_path/sh.config || exit 1
if test -n "$valgrind"
then
diff --git a/test/test-lib.sh b/test/test-lib.sh
index 486d1c43..126911fb 100644
--- a/test/test-lib.sh
+++ b/test/test-lib.sh
@@ -96,7 +96,8 @@ _x32="$_x04$_x04$_x04$_x04$_x04$_x04$_x04$_x04"
# test_description='Description of this test...
# This test checks if command xyzzy does the right thing...
# '
-# . ./test-lib.sh
+# . ./test-lib.sh || exit 1
+
[ "x$ORIGINAL_TERM" != "xdumb" ] && (
TERM=$ORIGINAL_TERM &&
export TERM &&
@@ -487,7 +488,7 @@ emacs_deliver_message ()
(message-goto-body)
(insert \"${body}\")
$@
- (message-send-and-exit))"
+ (notmuch-mua-send-and-exit))"
# In case message was sent properly, client waits for confirmation
# before exiting and resuming control here; therefore making sure
@@ -522,7 +523,7 @@ emacs_fcc_message ()
(message-goto-body)
(insert \"${body}\")
$@
- (message-send-and-exit))" || return 1
+ (notmuch-mua-send-and-exit))" || return 1
notmuch new >/dev/null
}
@@ -621,9 +622,9 @@ test_expect_equal_json () {
# The test suite forces LC_ALL=C, but this causes Python 3 to
# decode stdin as ASCII. We need to read JSON in UTF-8, so
# override Python's stdio encoding defaults.
- output=$(echo "$1" | PYTHONIOENCODING=utf-8 python -mjson.tool \
+ output=$(echo "$1" | PYTHONIOENCODING=utf-8 $NOTMUCH_PYTHON -mjson.tool \
|| echo "$1")
- expected=$(echo "$2" | PYTHONIOENCODING=utf-8 python -mjson.tool \
+ expected=$(echo "$2" | PYTHONIOENCODING=utf-8 $NOTMUCH_PYTHON -mjson.tool \
|| echo "$2")
shift 2
test_expect_equal "$output" "$expected" "$@"
@@ -719,6 +720,11 @@ notmuch_date_sanitize ()
sed \
-e 's/^Date: Fri, 05 Jan 2001 .*0000/Date: GENERATED_DATE/'
}
+
+notmuch_uuid_sanitize ()
+{
+ sed 's/[0-9a-f]\{8\}-[0-9a-f]\{4\}-[0-9a-f]\{4\}-[0-9a-f]\{4\}-[0-9a-f]\{12\}/UUID/g'
+}
# End of notmuch helper functions
# Use test_set_prereq to tell that a particular prerequisite is available.
@@ -1153,14 +1159,13 @@ test_python() {
export LD_LIBRARY_PATH=$TEST_DIRECTORY/../lib
export PYTHONPATH=$TEST_DIRECTORY/../bindings/python
- # Some distros (e.g. Arch Linux) ship Python 2.* as /usr/bin/python2,
- # most others as /usr/bin/python. So first try python2, and fallback to
- # python if python2 doesn't exist.
- cmd=python2
- [[ ${test_missing_external_prereq_[python2]} == t ]] && cmd=python
-
(echo "import sys; _orig_stdout=sys.stdout; sys.stdout=open('OUTPUT', 'w')"; cat) \
- | $cmd -
+ | $NOTMUCH_PYTHON -
+}
+
+test_ruby() {
+ export LD_LIBRARY_PATH=$TEST_DIRECTORY/../lib
+ MAIL_DIR=$MAIL_DIR ruby -I $TEST_DIRECTORY/../bindings/ruby> OUTPUT
}
test_C () {
@@ -1224,14 +1229,14 @@ test_init_ () {
}
-. ./test-lib-common.sh
+. ./test-lib-common.sh || exit 1
emacs_generate_script
# Use -P to resolve symlinks in our working directory so that the cwd
# in subprocesses like git equals our $PWD (for pathname comparisons).
-cd -P "$test" || error "Cannot setup test environment"
+cd -P "$test" || error "Cannot set up test environment"
if test "$verbose" = "t"
then
@@ -1320,5 +1325,4 @@ test_declare_external_prereq emacs
test_declare_external_prereq ${TEST_EMACSCLIENT}
test_declare_external_prereq gdb
test_declare_external_prereq gpg
-test_declare_external_prereq python
-test_declare_external_prereq python2
+test_declare_external_prereq ${NOTMUCH_PYTHON}
diff --git a/test/test-verbose b/test/test-verbose
index 4100c051..1723ce66 100755
--- a/test/test-verbose
+++ b/test/test-verbose
@@ -2,7 +2,7 @@
test_description='the verbosity options of the test framework itself.'
-. ./test-lib.sh
+. ./test-lib.sh || exit 1
test_expect_success 'print something in test_expect_success and pass' '
echo "hello stdout" &&
diff --git a/util/string-util.c b/util/string-util.c
index a90501ee..92af937f 100644
--- a/util/string-util.c
+++ b/util/string-util.c
@@ -221,3 +221,37 @@ parse_boolean_term (void *ctx, const char *str,
errno = err;
return -1;
}
+
+int
+strcmp_null (const char *s1, const char *s2)
+{
+ if (s1 && s2)
+ return strcmp (s1, s2);
+ else if (! s1 && ! s2)
+ return 0;
+ else if (s1)
+ return 1; /* s1 (non-NULL) is greater than s2 (NULL) */
+ else
+ return -1; /* s1 (NULL) is less than s2 (non-NULL) */
+}
+
+int
+strcase_equal (const void *a, const void *b)
+{
+ return strcasecmp (a, b) == 0;
+}
+
+unsigned int
+strcase_hash (const void *ptr)
+{
+ const char *s = ptr;
+
+ /* This is the djb2 hash. */
+ unsigned int hash = 5381;
+ while (s && *s) {
+ hash = ((hash << 5) + hash) + tolower (*s);
+ s++;
+ }
+
+ return hash;
+}
diff --git a/util/string-util.h b/util/string-util.h
index e409cb3d..87917b8f 100644
--- a/util/string-util.h
+++ b/util/string-util.h
@@ -64,6 +64,17 @@ int
parse_boolean_term (void *ctx, const char *str,
char **prefix_out, char **term_out);
+/* strcmp that handles NULL strings; in strcmp terms a NULL string is
+ * considered to be less than a non-NULL string.
+ */
+int strcmp_null (const char *s1, const char *s2);
+
+/* GLib GEqualFunc compatible strcasecmp wrapper */
+int strcase_equal (const void *a, const void *b);
+
+/* GLib GHashFunc compatible case insensitive hash function */
+unsigned int strcase_hash (const void *ptr);
+
#ifdef __cplusplus
}
#endif
diff --git a/version b/version
index 727d97b9..5320adc1 100644
--- a/version
+++ b/version
@@ -1 +1 @@
-0.20.2
+0.21