]> git.notmuchmail.org Git - notmuch/commitdiff
Merge branch 'release'
authorDavid Bremner <david@tethera.net>
Mon, 6 Nov 2017 00:25:54 +0000 (20:25 -0400)
committerDavid Bremner <david@tethera.net>
Mon, 6 Nov 2017 00:25:54 +0000 (20:25 -0400)
Changes from 0.25.2 release

209 files changed:
.gitignore
.travis.yml
Makefile.local
NEWS
bindings/python/.gitignore
bindings/python/README
bindings/python/docs/source/database.rst
bindings/python/notmuch/database.py
bindings/python/notmuch/directory.py
bindings/python/notmuch/message.py
bindings/ruby/.gitignore
bindings/ruby/database.c
command-line-arguments.c
command-line-arguments.h
compat/.gitignore
completion/notmuch-completion.bash
configure
contrib/go/.gitignore
contrib/go/src/notmuch/notmuch.go
contrib/notmuch-mutt/.gitignore
crypto.c [deleted file]
debian/.gitignore
debugger.c
devel/check-out-of-tree-build.sh [new file with mode: 0755]
devel/nmbug/doc/.gitignore
doc/.gitignore
doc/conf.py
doc/index.rst
doc/man1/notmuch-address.rst
doc/man1/notmuch-compact.rst
doc/man1/notmuch-config.rst
doc/man1/notmuch-count.rst
doc/man1/notmuch-dump.rst
doc/man1/notmuch-insert.rst
doc/man1/notmuch-new.rst
doc/man1/notmuch-reindex.rst [new file with mode: 0644]
doc/man1/notmuch-reply.rst
doc/man1/notmuch-restore.rst
doc/man1/notmuch-search.rst
doc/man1/notmuch-show.rst
doc/man1/notmuch-tag.rst
doc/man1/notmuch.rst
doc/man5/notmuch-hooks.rst
doc/man7/notmuch-properties.rst [new file with mode: 0644]
doc/man7/notmuch-search-terms.rst
emacs/.gitignore
emacs/notmuch-address.el
emacs/notmuch-lib.el
emacs/notmuch-mua.el
emacs/notmuch-show.el
emacs/notmuch-tag.el
emacs/notmuch-tree.el
emacs/notmuch.el
gmime-filter-reply.c
lib/Makefile.local
lib/add-message.cc [new file with mode: 0644]
lib/built-with.c
lib/config.cc
lib/database-private.h
lib/database.cc
lib/directory.cc
lib/filenames.c
lib/index.cc
lib/indexopts.c [new file with mode: 0644]
lib/message-file.c
lib/message-id.c [new file with mode: 0644]
lib/message-private.h
lib/message-property.cc
lib/message.cc
lib/messages.c
lib/notmuch-private.h
lib/notmuch.h
lib/query.cc
lib/string-list.c
lib/string-map.c
lib/thread.cc
mime-node.c
notmuch-client.h
notmuch-compact.c
notmuch-config.c
notmuch-count.c
notmuch-dump.c
notmuch-insert.c
notmuch-new.c
notmuch-reindex.c [new file with mode: 0644]
notmuch-reply.c
notmuch-restore.c
notmuch-search.c
notmuch-setup.c
notmuch-show.c
notmuch-tag.c
notmuch.c
performance-test/.gitignore
performance-test/M00-new.sh
performance-test/M01-dump-restore.sh
performance-test/M02-show.sh
performance-test/M03-search.sh
performance-test/M04-reply.sh
performance-test/M05-reindex.sh [new file with mode: 0755]
performance-test/M06-insert.sh [new file with mode: 0755]
performance-test/T00-new.sh
performance-test/T01-dump-restore.sh
performance-test/T02-tag.sh
performance-test/T03-reindex.sh [new file with mode: 0755]
performance-test/download/.gitignore
performance-test/perf-test-lib.sh
sprinter-json.c
sprinter-sexp.c
sprinter-text.c
sprinter.h
tag-util.c
tag-util.h
test/.gitignore
test/Makefile.local
test/README
test/T000-basic.sh
test/T010-help-test.sh
test/T020-compact.sh
test/T030-config.sh
test/T040-setup.sh
test/T050-new.sh
test/T060-count.sh
test/T070-insert.sh
test/T080-search.sh
test/T090-search-output.sh
test/T095-address.sh
test/T100-search-by-folder.sh
test/T110-search-position-overlap-bug.sh
test/T120-search-insufficient-from-quoting.sh
test/T130-search-limiting.sh
test/T140-excludes.sh
test/T150-tagging.sh
test/T160-json.sh
test/T170-sexp.sh
test/T180-text.sh
test/T190-multipart.sh
test/T200-thread-naming.sh
test/T205-author-naming.sh
test/T210-raw.sh
test/T220-reply.sh
test/T230-reply-to-sender.sh
test/T240-dump-restore.sh
test/T250-uuencode.sh
test/T260-thread-order.sh
test/T270-author-order.sh
test/T280-from-guessing.sh
test/T290-long-id.sh
test/T300-encoding.sh
test/T310-emacs.sh
test/T320-emacs-large-search-buffer.sh
test/T330-emacs-subject-to-filename.sh
test/T340-maildir-sync.sh
test/T350-crypto.sh
test/T355-smime.sh
test/T357-index-decryption.sh [new file with mode: 0755]
test/T360-symbol-hiding.sh
test/T370-search-folder-coherence.sh
test/T380-atomicity.sh
test/T390-python.sh
test/T395-ruby.sh
test/T400-hooks.sh
test/T410-argument-parsing.sh
test/T420-emacs-test-functions.sh
test/T430-emacs-address-cleaning.sh
test/T440-emacs-hello.sh
test/T450-emacs-show.sh
test/T455-emacs-charsets.sh
test/T460-emacs-tree.sh
test/T470-missing-headers.sh
test/T480-hex-escaping.sh
test/T490-parse-time-string.sh
test/T500-search-date.sh
test/T510-thread-replies.sh
test/T520-show.sh
test/T530-upgrade.sh
test/T550-db-features.sh
test/T560-lib-error.sh
test/T570-revision-tracking.sh
test/T580-thread-search.sh
test/T590-libconfig.sh
test/T590-thread-breakage.sh
test/T600-named-queries.sh
test/T610-message-property.sh
test/T620-lock.sh
test/T630-emacs-draft.sh
test/T640-database-modified.sh
test/T650-regexp-query.sh
test/T660-bad-date.sh
test/T670-duplicate-mid.sh [new file with mode: 0755]
test/T680-html-indexing.sh
test/T690-command-line-args.sh
test/T700-reindex.sh [new file with mode: 0755]
test/arg-test.c
test/export-dirs.sh [new file with mode: 0644]
test/hex-xcode.c
test/notmuch-test
test/random-corpus.c
test/smtp-dummy.c
test/test-databases/.gitignore
test/test-lib-common.sh
test/test-lib.sh
test/test-verbose
util/Makefile.local
util/crypto.c [new file with mode: 0644]
util/crypto.h [new file with mode: 0644]
util/error_util.c
util/gmime-extra.h
util/string-util.c
util/string-util.h

index 7b283fb3bff2482738b29e5a502596ed2176ec4b..e06101ce51c67c65d9118b198b4c165501b0d6e5 100644 (file)
@@ -1,16 +1,17 @@
-.first-build-message
-Makefile.config
-sh.config
-version.stamp
+/.first-build-message
+/Makefile.config
+/sh.config
+/version.stamp
 TAGS
 tags
 *cscope*
-.deps
+/.deps
 /notmuch
-notmuch-shared
-libnotmuch.so*
-libnotmuch*.dylib
+/notmuch-shared
+/lib/libnotmuch.so*
+/lib/libnotmuch*.dylib
 *.[ao]
 *~
 .*.swp
-releases
+/releases
+/.stamps
index dbd6434e6ab3dcc66b3a52626f983b5e6960a9db..802efd98c30d2eeef2963ad210368a62091b5e5a 100644 (file)
@@ -1,19 +1,22 @@
 language: c
-before_install:
-  - sudo apt-get update -qq
-  - sudo apt-get install dtach libxapian-dev libgmime-2.6-dev libtalloc-dev python-sphinx
 
-  # Notmuch requires zlib 1.2.5.2, unfortunately travis runs on Ubuntu 12.04LTS which
-  # ships with zlib 1.2.3.3. We need to update to zlib 1.2.5.2 to be able to build.
-  # TODO: Watch https://github.com/travis-ci/travis-ci/issues/2046 and remove
-  #       this hack once travis-ci switches to Ubuntu 14.04
-  - wget 'https://github.com/notmuch/travis-files/raw/master/zlib1g-dev_1.2.8.dfsg-1ubuntu1_amd64.deb'
-  - wget 'https://github.com/notmuch/travis-files/raw/master/zlib1g_1.2.8.dfsg-1ubuntu1_amd64.deb'
-  - sudo dpkg -i zlib1g-dev_1.2.8.dfsg-1ubuntu1_amd64.deb zlib1g_1.2.8.dfsg-1ubuntu1_amd64.deb
-  - sudo apt-get install -f
+dist: trusty
+sudo: false
+
+addons:
+  apt:
+    packages:
+    - dtach
+    - libxapian-dev
+    - libgmime-2.6-dev
+    - libtalloc-dev
+    - python-sphinx
+    - gdb
+    - gpgsm
 
 script:
   - ./configure
+  - make download-test-databases
   - make test
 
 notifications:
index 6bc78ef8e969b2d8649d68de6b590e8ded886cec..9505b7fee70b43b281bf5e6b428dfb7c522b14c2 100644 (file)
@@ -182,14 +182,14 @@ verify-newer:
 # user how to enable verbose compiles.
 ifeq ($(V),)
 quiet_DOC := "Use \"$(MAKE) V=1\" to see the verbose compile lines.\n"
-quiet = @printf $(quiet_DOC)$(eval quiet_DOC:=)"$1 $@\n"; $($(shell echo $1 | sed -e s'/ .*//'))
+quiet = @printf $(quiet_DOC)$(eval quiet_DOC:=)"$(1) $(or $(2),$@)\n"; $($(word 1, $(1)))
 endif
 # The user has explicitly enabled quiet compilation.
 ifeq ($(V),0)
-quiet = @printf "$1 $@\n"; $($(shell echo $1 | sed -e s'/ .*//'))
+quiet = @printf "$(1) $(or $(2),$@)\n"; $($(word 1, $(1)))
 endif
 # Otherwise, print the full command line.
-quiet ?= $($(shell echo $1 | sed -e s'/ .*//'))
+quiet ?= $($(word 1, $(1)))
 
 %.o: %.cc $(global_deps)
        @mkdir -p $(patsubst %/.,%,.deps/$(@D))
@@ -199,9 +199,17 @@ quiet ?= $($(shell echo $1 | sed -e s'/ .*//'))
        @mkdir -p $(patsubst %/.,%,.deps/$(@D))
        $(call quiet,CC $(CPPFLAGS) $(CFLAGS)) -c $(FINAL_CFLAGS) $< -o $@ -MD -MP -MF .deps/$*.d
 
+CPPCHECK=cppcheck
+.stamps/cppcheck/%: %
+       @mkdir -p $(@D)
+       $(call quiet,CPPCHECK,$<) --template=gcc --error-exitcode=1 --quiet $<
+       @touch $@
+
+CLEAN := $(CLEAN) .stamps
+
 .PHONY : clean
 clean:
-       rm -rf $(CLEAN); rm -rf .deps
+       rm -rf $(CLEAN)
 
 .PHONY: distclean
 distclean: clean
@@ -225,6 +233,7 @@ notmuch_client_srcs =               \
        notmuch-dump.c          \
        notmuch-insert.c        \
        notmuch-new.c           \
+       notmuch-reindex.c       \
        notmuch-reply.c         \
        notmuch-restore.c       \
        notmuch-search.c        \
@@ -237,7 +246,6 @@ notmuch_client_srcs =               \
        sprinter-text.c         \
        query-string.c          \
        mime-node.c             \
-       crypto.c                \
        tag-util.c
 
 notmuch_client_modules = $(notmuch_client_srcs:.c=.o)
@@ -279,9 +287,20 @@ endif
 SRCS  := $(SRCS) $(notmuch_client_srcs)
 CLEAN := $(CLEAN) notmuch notmuch-shared $(notmuch_client_modules)
 CLEAN := $(CLEAN) version.stamp notmuch-*.tar.gz.tmp
+CLEAN := $(CLEAN) .deps
 
 DISTCLEAN := $(DISTCLEAN) .first-build-message Makefile.config sh.config
 
+CPPCHECK_STAMPS := $(SRCS:%=.stamps/cppcheck/%)
+.PHONY: cppcheck
+ifeq ($(HAVE_CPPCHECK),1)
+cppcheck: ${CPPCHECK_STAMPS}
+else
+cppcheck:
+       @echo "No cppcheck found during configure; skipping static checking"
+endif
+
+
 DEPS := $(SRCS:%.c=.deps/%.d)
 DEPS := $(DEPS:%.cc=.deps/%.d)
 -include $(DEPS)
diff --git a/NEWS b/NEWS
index 24e7982c9d7791ec85c6d7fa924844db8b1e0ee8..412c678d1bfdddc823fd826b2a4ec49b855e3172 100644 (file)
--- a/NEWS
+++ b/NEWS
@@ -1,3 +1,33 @@
+Notmuch 0.26 (UNRELEASED)
+=========================
+
+Test Suite
+----------
+
+Out-of-tree builds
+
+  The test suite now works properly with out-of-tree builds, i.e. with
+  separate source and build directories. The --root option to tests
+  has been dropped. The same can now be achieved more reliably using
+  out-of-tree builds.
+
+Encrypted Mail
+--------------
+
+Indexing cleartext of encrypted e-mails
+
+  It's now possible to include the cleartext of encrypted e-mails in
+  the notmuch index.  This makes it possible to search your encrypted
+  e-mails with the same ease as searching cleartext.  This can be done
+  on a per-message basis with the --try-decrypt argument to indexing
+  commands (new, insert, reindex), or by default by running "notmuch
+  config set index.try_decrypt true".
+
+  Note that the contents of the index are sufficient to roughly
+  reconstruct the cleartext of the message itself, so please ensure
+  that the notmuch index itself is adequately protected.  DO NOT USE
+  this feature without considering the security of your index.
+
 Notmuch 0.25.2 (2017-11-05)
 ===========================
 
index da0732e90b31349143a60dd4f273776481efe80e..601acdd79095677e43b0c2683debc1ab6d588fa6 100644 (file)
@@ -1,4 +1,4 @@
 *.py[co]
 /docs/build
 /docs/html
-build/
+/build/
index fe7a21819d67f3ad0be98573675e998971c7f834..5bf076d262f93efadbf971f8fcddd97636ef2a33 100644 (file)
@@ -3,7 +3,7 @@ notmuch -- The python interface to notmuch
 
 This module makes the functionality of the notmuch library
 (`https://notmuchmail.org`_) available to python. Successful import of
-this modul depends on a libnotmuch.so|dll being available on the
+this module depends on a libnotmuch.so|dll being available on the
 user's system.
 
 If you have downloaded the full source tarball, you can create the
index 5f1cdc14b3f6dcbf107d4b708bf7a9228fdbdd7c..079dc75492f589f0a5357bbc90bddb20e1415039 100644 (file)
@@ -25,7 +25,7 @@
 
    .. automethod:: get_directory
 
-   .. automethod:: add_message
+   .. automethod:: index_file
 
    .. automethod:: remove_message
 
index 8f918069f34d24ad5059a3a89e0a5e55cd8a8b95..1279804a1c81eab28018e663f0b543085c0ce250 100644 (file)
@@ -19,6 +19,7 @@ Copyright 2010 Sebastian Spaeth <Sebastian@SSpaeth.de>
 
 import os
 import codecs
+import warnings
 from ctypes import c_char_p, c_void_p, c_uint, byref, POINTER
 from .compat import SafeConfigParser
 from .globals import (
@@ -285,7 +286,7 @@ class Database(object):
         """Does this database need to be upgraded before writing to it?
 
         If this function returns `True` then no functions that modify the
-        database (:meth:`add_message`,
+        database (:meth:`index_file`,
         :meth:`Message.add_tag`, :meth:`Directory.set_mtime`,
         etc.) will work unless :meth:`upgrade` is called successfully first.
 
@@ -399,12 +400,13 @@ class Database(object):
         # return the Directory, init it with the absolute path
         return Directory(abs_dirpath, dir_p, self)
 
-    _add_message = nmlib.notmuch_database_add_message
-    _add_message.argtypes = [NotmuchDatabaseP, c_char_p,
+    _index_file = nmlib.notmuch_database_index_file
+    _index_file.argtypes = [NotmuchDatabaseP, c_char_p,
+                             c_void_p,
                              POINTER(NotmuchMessageP)]
-    _add_message.restype = c_uint
+    _index_file.restype = c_uint
 
-    def add_message(self, filename, sync_maildir_flags=False):
+    def index_file(self, filename, sync_maildir_flags=False):
         """Adds a new message to the database
 
         :param filename: should be a path relative to the path of the
@@ -455,7 +457,7 @@ class Database(object):
         """
         self._assert_db_is_initialized()
         msg_p = NotmuchMessageP()
-        status = self._add_message(self._db, _str(filename), byref(msg_p))
+        status = self._index_file(self._db, _str(filename), c_void_p(None), byref(msg_p))
 
         if not status in [STATUS.SUCCESS, STATUS.DUPLICATE_MESSAGE_ID]:
             raise NotmuchError(status)
@@ -467,6 +469,14 @@ class Database(object):
             msg.maildir_flags_to_tags()
         return (msg, status)
 
+    def add_message(self, filename, sync_maildir_flags=False):
+        """Deprecated alias for :meth:`index_file`
+        """
+        warnings.warn(
+                "This function is deprecated and will be removed in the future, use index_file.", DeprecationWarning)
+
+        return self.index_file(filename, sync_maildir_flags=sync_maildir_flags)
+
     _remove_message = nmlib.notmuch_database_remove_message
     _remove_message.argtypes = [NotmuchDatabaseP, c_char_p]
     _remove_message.restype = c_uint
index 7f86b1ac15588aac8836d3bd31dcb5a3a945dc0c..b30c9e35e6427322630e0c7afd118e97e09484f0 100644 (file)
@@ -93,7 +93,7 @@ class Directory(object):
 
         * Read the mtime of a directory from the filesystem
 
-        * Call :meth:`Database.add_message` for all mail files in
+        * Call :meth:`Database.index_file` for all mail files in
           the directory
 
         * Call notmuch_directory_set_mtime with the mtime read from the
index fc177eb2b1557cb68c7c5c17bcedaf974b563869..d5b98e4fdfdf5ab19e41fcda61ce0fc57d7b301f 100644 (file)
@@ -41,6 +41,7 @@ from .tag import Tags
 from .filenames import Filenames
 
 import email
+import sys
 
 
 class Message(Python3StringMixIn):
@@ -566,7 +567,7 @@ class Message(Python3StringMixIn):
         logical OR operator.)
 
         As a convenience, you can set the sync_maildir_flags parameter in
-        :meth:`Database.add_message` to implicitly call this.
+        :meth:`Database.index_file` to implicitly call this.
 
         :returns: a :class:`STATUS`. In short, you want to see
             notmuch.STATUS.SUCCESS here. See there for details."""
@@ -587,8 +588,11 @@ class Message(Python3StringMixIn):
 
     def get_message_parts(self):
         """Output like notmuch show"""
-        fp = open(self.get_filename())
-        email_msg = email.message_from_file(fp)
+        fp = open(self.get_filename(), 'rb')
+        if sys.version_info[0] < 3:
+            email_msg = email.message_from_file(fp)
+        else:
+            email_msg = email.message_from_binary_file(fp)
         fp.close()
 
         out = []
index d682798abfe6421881c0f113fbecaae3c455abb6..c57ae63f43921f026ac32cf3c222c8d1b3530431 100644 (file)
@@ -1,7 +1,7 @@
 # .gitignore for bindings/ruby
 
 # Generated files
-Makefile
-mkmf.log
-notmuch.so
+/Makefile
+/mkmf.log
+/notmuch.so
 *.o
index 12e6bab7b095388e356e25507146283b3da61e03..416eb709f19d231bfdf6460c37a8e2262fad24af 100644 (file)
@@ -291,7 +291,7 @@ notmuch_rb_database_add_message (VALUE self, VALUE pathv)
     SafeStringValue (pathv);
     path = RSTRING_PTR (pathv);
 
-    ret = notmuch_database_add_message (db, path, &message);
+    ret = notmuch_database_index_file (db, path, NULL, &message);
     notmuch_rb_status_raise (ret);
     return rb_assoc_new (Data_Wrap_Struct (notmuch_rb_cMessage, NULL, NULL, message),
         (ret == NOTMUCH_STATUS_DUPLICATE_MESSAGE_ID) ? Qtrue : Qfalse);
index dc517b06ff6090c2290c5e85b6355e3c46f53978..1ff5aae578c651a0b780fa00bcbd2c49dd3963b1 100644 (file)
 
 /*
   Search the array of keywords for a given argument, assigning the
-  output variable to the corresponding value.  Return FALSE if nothing
+  output variable to the corresponding value.  Return false if nothing
   matches.
 */
 
-static notmuch_bool_t
+static bool
 _process_keyword_arg (const notmuch_opt_desc_t *arg_desc, char next, const char *arg_str) {
 
-    const notmuch_keyword_t *keywords = arg_desc->keywords;
+    const notmuch_keyword_t *keywords;
 
     if (next == '\0') {
        /* No keyword given */
        arg_str = "";
     }
 
-    while (keywords->name) {
-       if (strcmp (arg_str, keywords->name) == 0) {
-           if (arg_desc->output_var) {
-               if (arg_desc->opt_type == NOTMUCH_OPT_KEYWORD_FLAGS)
-                   *((int *)arg_desc->output_var) |= keywords->value;
-               else
-                   *((int *)arg_desc->output_var) = keywords->value;
-           }
-           return TRUE;
-       }
-       keywords++;
+    for (keywords = arg_desc->keywords; keywords->name; keywords++) {
+       if (strcmp (arg_str, keywords->name) != 0)
+           continue;
+
+       if (arg_desc->opt_flags)
+           *arg_desc->opt_flags |= keywords->value;
+       else
+           *arg_desc->opt_keyword = keywords->value;
+
+       return true;
     }
     if (next != '\0')
        fprintf (stderr, "Unknown keyword argument \"%s\" for option \"%s\".\n", arg_str, arg_desc->name);
     else
        fprintf (stderr, "Option \"%s\" needs a keyword argument.\n", arg_desc->name);
-    return FALSE;
+    return false;
 }
 
-static notmuch_bool_t
+static bool
 _process_boolean_arg (const notmuch_opt_desc_t *arg_desc, char next, const char *arg_str) {
-
-    if (next == '\0') {
-       *((notmuch_bool_t *)arg_desc->output_var) = TRUE;
-       return TRUE;
-    }
-    if (strcmp (arg_str, "false") == 0) {
-       *((notmuch_bool_t *)arg_desc->output_var) = FALSE;
-       return TRUE;
-    }
-    if (strcmp (arg_str, "true") == 0) {
-       *((notmuch_bool_t *)arg_desc->output_var) = TRUE;
-       return TRUE;
+    bool value;
+
+    if (next == '\0' || strcmp (arg_str, "true") == 0) {
+       value = true;
+    } else if (strcmp (arg_str, "false") == 0) {
+       value = false;
+    } else {
+       fprintf (stderr, "Unknown argument \"%s\" for (boolean) option \"%s\".\n", arg_str, arg_desc->name);
+       return false;
     }
-    fprintf (stderr, "Unknown argument \"%s\" for (boolean) option \"%s\".\n", arg_str, arg_desc->name);
-    return FALSE;
+
+    *arg_desc->opt_bool = value;
+
+    return true;
 }
 
-static notmuch_bool_t
+static bool
 _process_int_arg (const notmuch_opt_desc_t *arg_desc, char next, const char *arg_str) {
 
     char *endptr;
     if (next == '\0' || arg_str[0] == '\0') {
        fprintf (stderr, "Option \"%s\" needs an integer argument.\n", arg_desc->name);
-       return FALSE;
+       return false;
     }
 
-    *((int *)arg_desc->output_var) = strtol (arg_str, &endptr, 10);
+    *arg_desc->opt_int = strtol (arg_str, &endptr, 10);
     if (*endptr == '\0')
-       return TRUE;
+       return true;
 
     fprintf (stderr, "Unable to parse argument \"%s\" for option \"%s\" as an integer.\n",
             arg_str, arg_desc->name);
-    return FALSE;
+    return false;
 }
 
-static notmuch_bool_t
+static bool
 _process_string_arg (const notmuch_opt_desc_t *arg_desc, char next, const char *arg_str) {
 
     if (next == '\0') {
        fprintf (stderr, "Option \"%s\" needs a string argument.\n", arg_desc->name);
-       return FALSE;
+       return false;
     }
     if (arg_str[0] == '\0') {
        fprintf (stderr, "String argument for option \"%s\" must be non-empty.\n", arg_desc->name);
-       return FALSE;
+       return false;
     }
-    *((const char **)arg_desc->output_var) = arg_str;
-    return TRUE;
+    *arg_desc->opt_string = arg_str;
+    return true;
+}
+
+/* Return number of non-NULL opt_* fields in opt_desc. */
+static int _opt_set_count (const notmuch_opt_desc_t *opt_desc)
+{
+    return
+       !!opt_desc->opt_inherit +
+       !!opt_desc->opt_bool +
+       !!opt_desc->opt_int +
+       !!opt_desc->opt_keyword +
+       !!opt_desc->opt_flags +
+       !!opt_desc->opt_string +
+       !!opt_desc->opt_position;
+}
+
+/* Return true if opt_desc is valid. */
+static bool _opt_valid (const notmuch_opt_desc_t *opt_desc)
+{
+    int n = _opt_set_count (opt_desc);
+
+    if (n > 1)
+       INTERNAL_ERROR ("more than one non-NULL opt_* field for argument \"%s\"",
+                       opt_desc->name);
+
+    return n > 0;
 }
 
 /*
-   Search for the {pos_arg_index}th position argument, return FALSE if
+   Search for the {pos_arg_index}th position argument, return false if
    that does not exist.
 */
 
-notmuch_bool_t
+bool
 parse_position_arg (const char *arg_str, int pos_arg_index,
                    const notmuch_opt_desc_t *arg_desc) {
 
     int pos_arg_counter = 0;
-    while (arg_desc->opt_type != NOTMUCH_OPT_END){
-       if (arg_desc->opt_type == NOTMUCH_OPT_POSITION) {
+    while (_opt_valid (arg_desc)) {
+       if (arg_desc->opt_position) {
            if (pos_arg_counter == pos_arg_index) {
-               if (arg_desc->output_var) {
-                   *((const char **)arg_desc->output_var) = arg_str;
-               }
-               return TRUE;
+               *arg_desc->opt_position = arg_str;
+               if (arg_desc->present)
+                   *arg_desc->present = true;
+               return true;
            }
            pos_arg_counter++;
        }
        arg_desc++;
     }
-    return FALSE;
+    return false;
 }
 
 /*
@@ -138,9 +161,9 @@ parse_option (int argc, char **argv, const notmuch_opt_desc_t *options, int opt_
     if (opt_index < argc - 1  && strncmp (argv[opt_index + 1], "--", 2) != 0)
        next_arg = argv[opt_index + 1];
 
-    for (try = options; try->opt_type != NOTMUCH_OPT_END; try++) {
-       if (try->opt_type == NOTMUCH_OPT_INHERIT) {
-           int new_index = parse_option (argc, argv, try->output_var, opt_index);
+    for (try = options; _opt_valid (try); try++) {
+       if (try->opt_inherit) {
+           int new_index = parse_option (argc, argv, try->opt_inherit, opt_index);
            if (new_index >= 0)
                return new_index;
        }
@@ -163,40 +186,31 @@ parse_option (int argc, char **argv, const notmuch_opt_desc_t *options, int opt_
        if (next != '=' && next != ':' && next != '\0')
            continue;
 
-       if (next == '\0' && next_arg != NULL && try->opt_type != NOTMUCH_OPT_BOOLEAN) {
+       if (next == '\0' && next_arg != NULL && ! try->opt_bool) {
            next = ' ';
            value = next_arg;
            opt_index ++;
        }
 
-       if (try->output_var == NULL)
-           INTERNAL_ERROR ("output pointer NULL for option %s", try->name);
-
-       notmuch_bool_t opt_status = FALSE;
-       switch (try->opt_type) {
-       case NOTMUCH_OPT_KEYWORD:
-       case NOTMUCH_OPT_KEYWORD_FLAGS:
+       bool opt_status = false;
+       if (try->opt_keyword || try->opt_flags)
            opt_status = _process_keyword_arg (try, next, value);
-           break;
-       case NOTMUCH_OPT_BOOLEAN:
+       else if (try->opt_bool)
            opt_status = _process_boolean_arg (try, next, value);
-           break;
-       case NOTMUCH_OPT_INT:
+       else if (try->opt_int)
            opt_status = _process_int_arg (try, next, value);
-           break;
-       case NOTMUCH_OPT_STRING:
+       else if (try->opt_string)
            opt_status = _process_string_arg (try, next, value);
-           break;
-       case NOTMUCH_OPT_POSITION:
-       case NOTMUCH_OPT_END:
-       default:
-           INTERNAL_ERROR ("unknown or unhandled option type %d", try->opt_type);
-           /*UNREACHED*/
-       }
-       if (opt_status)
-           return opt_index+1;
        else
+           INTERNAL_ERROR ("unknown or unhandled option \"%s\"", try->name);
+
+       if (! opt_status)
            return -1;
+
+       if (try->present)
+           *try->present = true;
+
+       return opt_index+1;
     }
     return -1;
 }
@@ -207,7 +221,7 @@ parse_arguments (int argc, char **argv,
                 const notmuch_opt_desc_t *options, int opt_index) {
 
     int pos_arg_index = 0;
-    notmuch_bool_t more_args = TRUE;
+    bool more_args = true;
 
     while (more_args && opt_index < argc) {
        if (strncmp (argv[opt_index],"--",2) != 0) {
@@ -228,7 +242,7 @@ parse_arguments (int argc, char **argv,
            opt_index = parse_option (argc, argv, options, opt_index);
            if (opt_index < 0) {
                fprintf (stderr, "Unrecognized option: %s\n", argv[prev_opt_index]);
-               more_args = FALSE;
+               more_args = false;
            }
        }
     }
index 4c4d240e10dc17a4e3a966b839a7013c6d40a360..76ca4dcbb276714a15b8524fbe106ffd6ba2fc78 100644 (file)
@@ -1,18 +1,9 @@
 #ifndef NOTMUCH_OPTS_H
 #define NOTMUCH_OPTS_H
 
-#include "notmuch.h"
+#include <stdbool.h>
 
-enum notmuch_opt_type {
-    NOTMUCH_OPT_END = 0,
-    NOTMUCH_OPT_INHERIT,       /* another options table */
-    NOTMUCH_OPT_BOOLEAN,       /* --verbose              */
-    NOTMUCH_OPT_INT,           /* --frob=8               */
-    NOTMUCH_OPT_KEYWORD,       /* --format=raw|json|text */
-    NOTMUCH_OPT_KEYWORD_FLAGS, /* the above with values OR'd together */
-    NOTMUCH_OPT_STRING,                /* --file=/tmp/gnarf.txt  */
-    NOTMUCH_OPT_POSITION       /* notmuch dump pos_arg   */
-};
+#include "notmuch.h"
 
 /*
  * Describe one of the possibilities for a keyword option
@@ -24,22 +15,24 @@ typedef struct notmuch_keyword {
     int value;
 } notmuch_keyword_t;
 
-/*
- * Describe one option.
- *
- * First two parameters are mandatory.
- *
- * name is mandatory _except_ for positional arguments.
- *
- * arg_id is currently unused, but could define short arguments.
- *
- * keywords is a (possibly NULL) pointer to an array of keywords
- */
+/* Describe one option. */
 typedef struct notmuch_opt_desc {
-    enum notmuch_opt_type opt_type;
-    void *output_var;
+    /* One and only one of opt_* must be set. */
+    const struct notmuch_opt_desc *opt_inherit;
+    bool *opt_bool;
+    int *opt_int;
+    int *opt_keyword;
+    int *opt_flags;
+    const char **opt_string;
+    const char **opt_position;
+
+    /* Must be set except for opt_inherit and opt_position. */
     const char *name;
-    int  arg_id;
+
+    /* Optional, if non-NULL, set to true if the option is present. */
+    bool *present;
+
+    /* Must be set for opt_keyword and opt_flags. */
     const struct notmuch_keyword *keywords;
 } notmuch_opt_desc_t;
 
@@ -73,7 +66,7 @@ parse_arguments (int argc, char **argv, const notmuch_opt_desc_t *options, int o
 int
 parse_option (int argc, char **argv, const notmuch_opt_desc_t* options, int opt_index);
 
-notmuch_bool_t
+bool
 parse_position_arg (const char *arg,
                    int position_arg_index,
                    const notmuch_opt_desc_t* options);
index 107ba17aaeba555153a95cbdc8e3d94fe0619e61..7ede45e93d50148284a77d35bc7d07f22ccff976 100644 (file)
@@ -1 +1 @@
-zlib.pc
+/zlib.pc
index e4e4b36bccf723ead969dd8c48bb01cccfc96682..7aae4297ae0e1fecdbff7f97c857fff3529efcac 100644 (file)
@@ -287,12 +287,16 @@ _notmuch_insert()
                sed "s|^$path/||" | grep -v "\(^\|/\)\(cur\|new\|tmp\)$" ) )
            return
            ;;
+       --try-decrypt)
+           COMPREPLY=( $( compgen -W "true false" -- "${cur}" ) )
+           return
+           ;;
     esac
 
     ! $split &&
     case "${cur}" in
        --*)
-           local options="--create-folder --folder= --keep --no-hooks ${_notmuch_shared_options}"
+           local options="--create-folder --folder= --keep --no-hooks --try-decrypt= ${_notmuch_shared_options}"
            compopt -o nospace
            COMPREPLY=( $(compgen -W "$options" -- ${cur}) )
            return
@@ -311,11 +315,20 @@ _notmuch_insert()
 _notmuch_new()
 {
     local cur prev words cword split
-    _init_completion || return
+    _init_completion -s || return
+
+    $split &&
+    case "${prev}" in
+       --try-decrypt)
+           COMPREPLY=( $( compgen -W "true false" -- "${cur}" ) )
+           return
+           ;;
+    esac
 
+    ! $split &&
     case "${cur}" in
        -*)
-           local options="--no-hooks --quiet ${_notmuch_shared_options}"
+           local options="--no-hooks --try-decrypt= --quiet ${_notmuch_shared_options}"
            compopt -o nospace
            COMPREPLY=( $(compgen -W "${options}" -- ${cur}) )
            ;;
@@ -417,6 +430,32 @@ _notmuch_search()
     esac
 }
 
+_notmuch_reindex()
+{
+    local cur prev words cword split
+    _init_completion -s || return
+
+    $split &&
+    case "${prev}" in
+       --try-decrypt)
+           COMPREPLY=( $( compgen -W "true false" -- "${cur}" ) )
+           return
+           ;;
+    esac
+
+    ! $split &&
+    case "${cur}" in
+       -*)
+           local options="--try-decrypt= ${_notmuch_shared_options}"
+           compopt -o nospace
+           COMPREPLY=( $(compgen -W "$options" -- ${cur}) )
+           ;;
+       *)
+           _notmuch_search_terms
+           ;;
+    esac
+}
+
 _notmuch_address()
 {
     local cur prev words cword split
@@ -532,7 +571,7 @@ _notmuch_tag()
 
 _notmuch()
 {
-    local _notmuch_commands="compact config count dump help insert new reply restore search address setup show tag emacs-mua"
+    local _notmuch_commands="compact config count dump help insert new reply restore reindex search address setup show tag emacs-mua"
     local arg cur prev words cword split
 
     # require bash-completion with _init_completion
index c5e2ffed9e6705a9b16f5cf52f3c57235bc40044..d3e30b53ea518c945c1fa2b3676c13c6cb83e4d8 100755 (executable)
--- a/configure
+++ b/configure
@@ -40,10 +40,6 @@ if [ "$srcdir" != "." ]; then
        cp "$srcdir"/"$dir"/Makefile "$dir"
     done
 
-    # Easiest way to get the test suite to work is to just copy the
-    # whole thing into the build directory.
-    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 ideal).
     cp -a "$srcdir"/emacs/*.el emacs
@@ -646,6 +642,15 @@ if [ $WITH_DESKTOP = "1" ]; then
     fi
 fi
 
+printf "Checking for cppcheck... "
+if command -v cppcheck > /dev/null; then
+    have_cppcheck=1
+    printf "Yes.\n"
+else
+    have_cppcheck=0
+    printf "No.\n"
+fi
+
 libdir_in_ldconfig=0
 
 printf "Checking which platform we are on... "
@@ -1065,6 +1070,9 @@ zsh_completion_dir = ${ZSHCOMLETIONDIR:=\$(prefix)/share/zsh/functions/Completio
 # build its own version)
 HAVE_CANONICALIZE_FILE_NAME = ${have_canonicalize_file_name}
 
+# Whether the cppcheck static checker is available
+HAVE_CPPCHECK = ${have_cppcheck}
+
 # Whether the getline function is available (if not, then notmuch will
 # build its own version)
 HAVE_GETLINE = ${have_getline}
index c394479fe7cba2be7384954122f88c5f1f839ac3..223504b17147446a4ff16f128d1f1fd6b6e4b629 100644 (file)
@@ -1,3 +1,3 @@
-src/github.com/
-pkg/
-bin/
+/src/github.com/
+/pkg/
+/bin/
index 2d6843112ae0ba5cec49ae068cdb4070442f35a6..89093b2da34dab037e4ccba6f4f7f767fe8564d3 100644 (file)
@@ -170,7 +170,7 @@ func (self *Database) GetVersion() uint {
 /* Does this database need to be upgraded before writing to it?
  *
  * If this function returns TRUE then no functions that modify the
- * database (notmuch_database_add_message, notmuch_message_add_tag,
+ * database (notmuch_database_index_file, notmuch_message_add_tag,
  * notmuch_directory_set_mtime, etc.) will work unless the function
  * notmuch_database_upgrade is called successfully first. */
 func (self *Database) NeedsUpgrade() bool {
index 682a57793ce75520cd975f32333229dfdbb37666..116bb715b681d16c25ec0f715db6fd1f5be26e3d 100644 (file)
@@ -1,2 +1,2 @@
-notmuch-mutt.1
-README.html
+/notmuch-mutt.1
+/README.html
diff --git a/crypto.c b/crypto.c
deleted file mode 100644 (file)
index cc45b88..0000000
--- a/crypto.c
+++ /dev/null
@@ -1,140 +0,0 @@
-/* notmuch - Not much of an email program, (just index and search)
- *
- * Copyright © 2012 Jameson Rollins
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see https://www.gnu.org/licenses/ .
- *
- * Authors: Jameson Rollins <jrollins@finestructure.net>
- */
-
-#include "notmuch-client.h"
-#if (GMIME_MAJOR_VERSION < 3)
-/* Create a GPG context (GMime 2.6) */
-static notmuch_crypto_context_t *
-create_gpg_context (notmuch_crypto_t *crypto)
-{
-    notmuch_crypto_context_t *gpgctx;
-
-    if (crypto->gpgctx)
-       return crypto->gpgctx;
-
-    /* TODO: GMimePasswordRequestFunc */
-    gpgctx = g_mime_gpg_context_new (NULL, crypto->gpgpath ? crypto->gpgpath : "gpg");
-    if (! gpgctx) {
-       fprintf (stderr, "Failed to construct gpg context.\n");
-       return NULL;
-    }
-    crypto->gpgctx = gpgctx;
-
-    g_mime_gpg_context_set_use_agent ((GMimeGpgContext *) gpgctx, TRUE);
-    g_mime_gpg_context_set_always_trust ((GMimeGpgContext *) gpgctx, FALSE);
-
-    return gpgctx;
-}
-
-/* Create a PKCS7 context (GMime 2.6) */
-static notmuch_crypto_context_t *
-create_pkcs7_context (notmuch_crypto_t *crypto)
-{
-    notmuch_crypto_context_t *pkcs7ctx;
-
-    if (crypto->pkcs7ctx)
-       return crypto->pkcs7ctx;
-
-    /* TODO: GMimePasswordRequestFunc */
-    pkcs7ctx = g_mime_pkcs7_context_new (NULL);
-    if (! pkcs7ctx) {
-       fprintf (stderr, "Failed to construct pkcs7 context.\n");
-       return NULL;
-    }
-    crypto->pkcs7ctx = pkcs7ctx;
-
-    g_mime_pkcs7_context_set_always_trust ((GMimePkcs7Context *) pkcs7ctx,
-                                          FALSE);
-
-    return pkcs7ctx;
-}
-static const struct {
-    const char *protocol;
-    notmuch_crypto_context_t *(*get_context) (notmuch_crypto_t *crypto);
-} protocols[] = {
-    {
-       .protocol = "application/pgp-signature",
-       .get_context = create_gpg_context,
-    },
-    {
-       .protocol = "application/pgp-encrypted",
-       .get_context = create_gpg_context,
-    },
-    {
-       .protocol = "application/pkcs7-signature",
-       .get_context = create_pkcs7_context,
-    },
-    {
-       .protocol = "application/x-pkcs7-signature",
-       .get_context = create_pkcs7_context,
-    },
-};
-
-/* for the specified protocol return the context pointer (initializing
- * if needed) */
-notmuch_crypto_context_t *
-notmuch_crypto_get_context (notmuch_crypto_t *crypto, const char *protocol)
-{
-    notmuch_crypto_context_t *cryptoctx = NULL;
-    size_t i;
-
-    if (! protocol) {
-       fprintf (stderr, "Cryptographic protocol is empty.\n");
-       return cryptoctx;
-    }
-
-    /* As per RFC 1847 section 2.1: "the [protocol] value token is
-     * comprised of the type and sub-type tokens of the Content-Type".
-     * As per RFC 1521 section 2: "Content-Type values, subtypes, and
-     * parameter names as defined in this document are
-     * case-insensitive."  Thus, we use strcasecmp for the protocol.
-     */
-    for (i = 0; i < ARRAY_SIZE (protocols); i++) {
-       if (strcasecmp (protocol, protocols[i].protocol) == 0)
-           return protocols[i].get_context (crypto);
-    }
-
-    fprintf (stderr, "Unknown or unsupported cryptographic protocol %s.\n",
-            protocol);
-
-    return NULL;
-}
-
-int
-notmuch_crypto_cleanup (notmuch_crypto_t *crypto)
-{
-    if (crypto->gpgctx) {
-       g_object_unref (crypto->gpgctx);
-       crypto->gpgctx = NULL;
-    }
-
-    if (crypto->pkcs7ctx) {
-       g_object_unref (crypto->pkcs7ctx);
-       crypto->pkcs7ctx = NULL;
-    }
-
-    return 0;
-}
-#else
-int notmuch_crypto_cleanup (unused(notmuch_crypto_t *crypto))
-{
-    return 0;
-}
-#endif
index e8c2e82a0247b187671752e7053c4b73ebd48526..cd0decc29f365c497f8bb410a4d7fa99f8fd7141 100644 (file)
@@ -1,14 +1,14 @@
-tmp/
-libnotmuch-dev/
-libnotmuch*/
-notmuch-emacs/
-notmuch/
-notmuch-dbg/
-notmuch-mutt/
-notmuch-vim/
-ruby-notmuch/
-python*-notmuch/
-*.debhelper
-*.debhelper.log
-*.substvars
-files
+/tmp/
+/libnotmuch-dev/
+/libnotmuch*/
+/notmuch-emacs/
+/elpa-notmuch/
+/notmuch/
+/notmuch-mutt/
+/notmuch-vim/
+/ruby-notmuch/
+/python*-notmuch/
+/*.debhelper
+/*.debhelper.log
+/*.substvars
+/files
index 0fa0fb6bda23881ed9c01b42f974f6e4b652526b..5cb38ac444e35d774152b64973790c4fd5ec7f4d 100644 (file)
 #define RUNNING_ON_VALGRIND 0
 #endif
 
-notmuch_bool_t
+bool
 debugger_is_active (void)
 {
     char buf[1024];
 
     if (RUNNING_ON_VALGRIND)
-       return TRUE;
+       return true;
 
     sprintf (buf, "/proc/%d/exe", getppid ());
     if (readlink (buf, buf, sizeof (buf)) != -1 &&
        strncmp (basename (buf), "gdb", 3) == 0)
     {
-       return TRUE;
+       return true;
     }
 
-    return FALSE;
+    return false;
 }
diff --git a/devel/check-out-of-tree-build.sh b/devel/check-out-of-tree-build.sh
new file mode 100755 (executable)
index 0000000..3e443ea
--- /dev/null
@@ -0,0 +1,15 @@
+#!/bin/sh
+# test out-of-tree builds in a temp directory
+# passes all args to make
+
+set -eu
+
+srcdir=$(cd "$(dirname "$0")"/.. && pwd)
+builddir=$(mktemp -d)
+
+cd "$builddir"
+
+"$srcdir"/configure
+make "$@"
+
+rm -rf "$builddir"
index 4930881a69ad694c5401d9f71f38ca3b98ce0d20..f25d695cccbf924085af44ed3f67e57c7e993103 100644 (file)
@@ -1,2 +1,2 @@
 *.pyc
-_build
+/_build
index 9fa35d08a95e3c850ceafccdea523f310f775e43..bbb749fabda3b6820db81a46330440df6052006a 100644 (file)
@@ -1,3 +1,3 @@
 *.pyc
-_build
-config.dox
+/_build
+/config.dox
index a3d8269696a366dca12b1caa4a8d67e6005d993c..c7013bece1df5bad288d6b1a9c6c7d73e9feac2b 100644 (file)
@@ -47,6 +47,10 @@ html_static_path = []
 # Output file base name for HTML help builder.
 htmlhelp_basename = 'notmuchdoc'
 
+# Disable SmartyPants, as it mangles command lines.
+# Despite the name, this actually affects manual pages as well.
+html_use_smartypants = False
+
 # -- Options for manual page output ---------------------------------------
 
 # One entry per manual page. List of tuples
@@ -95,6 +99,14 @@ man_pages = [
      u'incorporate new mail into the notmuch database',
      [notmuch_authors], 1),
 
+    ('man7/notmuch-properties', 'notmuch-properties',
+     u'notmuch message property conventions and documentation',
+     [notmuch_authors], 7),
+
+    ('man1/notmuch-reindex', 'notmuch-reindex',
+     u're-index matching messages',
+     [notmuch_authors], 1),
+
     ('man1/notmuch-reply', 'notmuch-reply',
      u'constructs a reply template for a set of messages',
      [notmuch_authors], 1),
index 344606d9e1814434dac78a722a138bb204adf19f..4440d93aad285ba1d3da14d692ba9f54c47af3c6 100644 (file)
@@ -18,6 +18,8 @@ Contents:
    man5/notmuch-hooks
    man1/notmuch-insert
    man1/notmuch-new
+   man7/notmuch-properties
+   man1/notmuch-reindex
    man1/notmuch-reply
    man1/notmuch-restore
    man1/notmuch-search
index cc31cc5aa963879ef516843bfbb1b5ce5825592a..dbac20f7b012f460e3ede9ba8bb6aaefa9a05862 100644 (file)
@@ -115,8 +115,16 @@ This command supports the following special exit status codes
 SEE ALSO
 ========
 
-**notmuch(1)**, **notmuch-config(1)**, **notmuch-count(1)**,
-**notmuch-dump(1)**, **notmuch-hooks(5)**, **notmuch-insert(1)**,
-**notmuch-new(1)**, **notmuch-reply(1)**, **notmuch-restore(1)**,
-**notmuch-search-terms(7)**, **notmuch-show(1)**, **notmuch-tag(1)**,
+**notmuch(1)**,
+**notmuch-config(1)**,
+**notmuch-count(1)**,
+**notmuch-dump(1)**,
+**notmuch-hooks(5)**,
+**notmuch-insert(1)**,
+**notmuch-new(1)**,
+**notmuch-reply(1)**,
+**notmuch-restore(1)**,
+**notmuch-search-terms(7)**,
+**notmuch-show(1)**,
+**notmuch-tag(1)**,
 **notmuch-search(1)**
index e0109dce80e07015a9aec6daee36e9ac4c32363c..25692c5b48e7a3cf4e73982170e0cca361b37456 100644 (file)
@@ -46,7 +46,15 @@ of notmuch.
 SEE ALSO
 ========
 
-**notmuch(1)**, **notmuch-count(1)**, **notmuch-dump(1)**,
-**notmuch-hooks(5)**, **notmuch-insert(1)**, **notmuch-new(1)**,
-**notmuch-reply(1)**, **notmuch-restore(1)**, **notmuch-search(1)**,
-**notmuch-search-terms(7)**, **notmuch-show(1)**, **notmuch-tag(1)**
+**notmuch(1)**,
+**notmuch-count(1)**,
+**notmuch-dump(1)**,
+**notmuch-hooks(5)**,
+**notmuch-insert(1)**,
+**notmuch-new(1)**,
+**notmuch-reply(1)**,
+**notmuch-restore(1)**,
+**notmuch-search(1)**,
+**notmuch-search-terms(7)**,
+**notmuch-show(1)**,
+**notmuch-tag(1)**
index 6a51e64f1517e18faea95c516a3ffa7ef254eba8..6961737f3d220b2b6478780acfd4bfe5c65db7c0 100644 (file)
@@ -15,7 +15,11 @@ DESCRIPTION
 ===========
 
 The **config** command can be used to get or set settings in the notmuch
-configuration file.
+configuration file and corresponding database.
+
+Items marked **[STORED IN DATABASE]** are only in the database.  They
+should not be placed in the configuration file, and should be accessed
+programmatically as described in the SYNOPSIS above.
 
     **get**
         The value of the specified configuration item is printed to
@@ -134,6 +138,19 @@ The available configuration items are described below.
 
         Default: ``gpg``.
 
+    **index.try_decrypt**
+
+        **[STORED IN DATABASE]**
+        When indexing an encrypted e-mail message, if this variable is
+        set to true, notmuch will try to decrypt the message and index
+        the cleartext.  Be aware that the index is likely sufficient
+        to reconstruct the cleartext of the message itself, so please
+        ensure that the notmuch message index is adequately protected.
+        DO NOT USE ``index.try_decrypt=true`` without considering the
+        security of your index.
+
+        Default: ``false``.
+
     **built_with.<name>**
 
         Compile time feature <name>. Current possibilities include
@@ -142,6 +159,7 @@ The available configuration items are described below.
 
     **query.<name>**
 
+        **[STORED IN DATABASE]**
         Expansion for named query called <name>. See
         **notmuch-search-terms(7)** for more information about named
         queries.
@@ -159,7 +177,15 @@ of notmuch.
 SEE ALSO
 ========
 
-**notmuch(1)**, **notmuch-count(1)**, **notmuch-dump(1)**,
-**notmuch-hooks(5)**, **notmuch-insert(1)**, **notmuch-new(1)**,
-**notmuch-reply(1)**, **notmuch-restore(1)**, **notmuch-search(1)**,
-**notmuch-search-terms(7)**, **notmuch-show(1)**, **notmuch-tag(1)**
+**notmuch(1)**,
+**notmuch-count(1)**,
+**notmuch-dump(1)**,
+**notmuch-hooks(5)**,
+**notmuch-insert(1)**,
+**notmuch-new(1)**,
+**notmuch-reply(1)**,
+**notmuch-restore(1)**,
+**notmuch-search(1)**,
+**notmuch-search-terms(7)**,
+**notmuch-show(1)**,
+**notmuch-tag(1)**
index 90d852ae3adfc0f95971734dd5bf236dc7b2e9c6..35a2e5e870143a470e262434ec770dc4532b399c 100644 (file)
@@ -59,7 +59,15 @@ Supported options for **count** include
 SEE ALSO
 ========
 
-**notmuch(1)**, **notmuch-config(1)**, **notmuch-dump(1)**,
-**notmuch-hooks(5)**, **notmuch-insert(1)**, **notmuch-new(1)**,
-**notmuch-reply(1)**, **notmuch-restore(1)**, **notmuch-search(1)**,
-**notmuch-search-terms(7)**, **notmuch-show(1)**, **notmuch-tag(1)**
+**notmuch(1)**,
+**notmuch-config(1)**,
+**notmuch-dump(1)**,
+**notmuch-hooks(5)**,
+**notmuch-insert(1)**,
+**notmuch-new(1)**,
+**notmuch-reply(1)**,
+**notmuch-restore(1)**,
+**notmuch-search(1)**,
+**notmuch-search-terms(7)**,
+**notmuch-show(1)**,
+**notmuch-tag(1)**
index f3f2b3942fafd82bcd842a067be93f71c205ff63..7bc57d29468250e9a11e51ea5e7ce49a5ecde7d7 100644 (file)
@@ -85,8 +85,8 @@ Supported options for **dump** include
 
         Output per-message (key,value) metadata.  Each line starts
         with "#= ", followed by a message id, and a space separated
-        list of key=value pairs.  pair.  Ids, keys and values are hex
-        encoded if needed.
+        list of key=value pairs.  Ids, keys and values are hex encoded
+        if needed.  See **notmuch-properties(7)** for more details.
 
       **tags**
 
@@ -110,7 +110,16 @@ Supported options for **dump** include
 SEE ALSO
 ========
 
-**notmuch(1)**, **notmuch-config(1)**, **notmuch-count(1)**,
-**notmuch-hooks(5)**, **notmuch-insert(1)**, **notmuch-new(1)**,
-**notmuch-reply(1)**, **notmuch-restore(1)**, **notmuch-search(1)**,
-**notmuch-search-terms(7)**, **notmuch-show(1)**, **notmuch-tag(1)**
+**notmuch(1)**,
+**notmuch-config(1)**,
+**notmuch-count(1)**,
+**notmuch-hooks(5)**,
+**notmuch-insert(1)**,
+**notmuch-new(1)**,
+**notmuch-properties(7)**,
+**notmuch-reply(1)**,
+**notmuch-restore(1)**,
+**notmuch-search(1)**,
+**notmuch-search-terms(7)**,
+**notmuch-show(1)**,
+**notmuch-tag(1)**
index f79600d6571f02f44951eff7eb20cf74fc2b7173..e2bf37d04c79e49f6a8127bf36949a84470d2f86 100644 (file)
@@ -50,6 +50,20 @@ Supported options for **insert** include
     ``--no-hooks``
         Prevent hooks from being run.
 
+    ``--try-decrypt=(true|false)``
+
+        If true and the message is encrypted, try to decrypt the
+        message while indexing.  If decryption is successful, index
+        the cleartext itself.  Either way, the message is always
+        stored to disk in its original form (ciphertext).  Be aware
+        that the index is likely sufficient to reconstruct the
+        cleartext of the message itself, so please ensure that the
+        notmuch message index is adequately protected. DO NOT USE
+        ``--try-decrypt=true`` without considering the security of
+        your index.
+
+        See also ``index.try_decrypt`` in **notmuch-config(1)**.
+
 EXIT STATUS
 ===========
 
@@ -73,7 +87,14 @@ status of the **insert** command.
 SEE ALSO
 ========
 
-**notmuch(1)**, **notmuch-config(1)**, **notmuch-count(1)**,
-**notmuch-dump(1)**, **notmuch-hooks(5)**, **notmuch-reply(1)**,
-**notmuch-restore(1)**, **notmuch-search(1)**,
-**notmuch-search-terms(7)**, **notmuch-show(1)**, **notmuch-tag(1)**
+**notmuch(1)**,
+**notmuch-config(1)**,
+**notmuch-count(1)**,
+**notmuch-dump(1)**,
+**notmuch-hooks(5)**,
+**notmuch-reply(1)**,
+**notmuch-restore(1)**,
+**notmuch-search(1)**,
+**notmuch-search-terms(7)**,
+**notmuch-show(1)**,
+**notmuch-tag(1)**
index 6acfa112ce5357f3d705cc7412d8c38a41fddd6d..bc26aa4806a7e07400708a9f35b8f832009302be 100644 (file)
@@ -43,6 +43,18 @@ Supported options for **new** include
     ``--quiet``
         Do not print progress or results.
 
+    ``--try-decrypt=(true|false)``
+
+        If true, when encountering an encrypted message, try to
+        decrypt it while indexing.  If decryption is successful, index
+        the cleartext itself.  Be aware that the index is likely
+        sufficient to reconstruct the cleartext of the message itself,
+        so please ensure that the notmuch message index is adequately
+        protected.  DO NOT USE ``--try-decrypt=true`` without
+        considering the security of your index.
+
+        See also ``index.try_decrypt`` in **notmuch-config(1)**.
+
 EXIT STATUS
 ===========
 
@@ -54,7 +66,15 @@ This command supports the following special exit status code
 SEE ALSO
 ========
 
-**notmuch(1)**, **notmuch-config(1)**, **notmuch-count(1)**,
-**notmuch-dump(1)**, **notmuch-hooks(5)**, **notmuch-insert(1)**,
-**notmuch-reply(1)**, **notmuch-restore(1)**, **notmuch-search(1)**,
-**notmuch-search-terms(7)**, **notmuch-show(1)**, **notmuch-tag(1)**
+**notmuch(1)**,
+**notmuch-config(1)**,
+**notmuch-count(1)**,
+**notmuch-dump(1)**,
+**notmuch-hooks(5)**,
+**notmuch-insert(1)**,
+**notmuch-reply(1)**,
+**notmuch-restore(1)**,
+**notmuch-search(1)**,
+**notmuch-search-terms(7)**,
+**notmuch-show(1)**,
+**notmuch-tag(1)**
diff --git a/doc/man1/notmuch-reindex.rst b/doc/man1/notmuch-reindex.rst
new file mode 100644 (file)
index 0000000..21f6c7a
--- /dev/null
@@ -0,0 +1,51 @@
+===============
+notmuch-reindex
+===============
+
+SYNOPSIS
+========
+
+**notmuch** **reindex** [*option* ...] <*search-term*> ...
+
+DESCRIPTION
+===========
+
+Re-index all messages matching the search terms.
+
+See **notmuch-search-terms(7)** for details of the supported syntax for
+<*search-term*\ >.
+
+The **reindex** command searches for all messages matching the
+supplied search terms, and re-creates the full-text index on these
+messages using the supplied options.
+
+Supported options for **reindex** include
+
+    ``--try-decrypt=(true|false)``
+
+        If true, when encountering an encrypted message, try to
+        decrypt it while reindexing.  If decryption is successful,
+        index the cleartext itself.  Be aware that the index is likely
+        sufficient to reconstruct the cleartext of the message itself,
+        so please ensure that the notmuch message index is adequately
+        protected. DO NOT USE ``--try-decrypt=true`` without
+        considering the security of your index.
+
+        See also ``index.try_decrypt`` in **notmuch-config(1)**.
+
+SEE ALSO
+========
+
+**notmuch(1)**,
+**notmuch-config(1)**,
+**notmuch-count(1)**,
+**notmuch-dump(1)**,
+**notmuch-hooks(5)**,
+**notmuch-insert(1)**,
+**notmuch-new(1)**,
+**notmuch-reply(1)**,
+**notmuch-restore(1)**,
+**notmuch-search(1)**,
+**notmuch-search-terms(7)**,
+**notmuch-show(1)**,
+**notmuch-tag(1)**
index d73f8f1c4ffd03da5889f1cbf9f17c48e5591c9b..b6aec3c88f787819a4c66da89e1279aa29080a97 100644 (file)
@@ -108,7 +108,15 @@ This command supports the following special exit status codes
 SEE ALSO
 ========
 
-**notmuch(1)**, **notmuch-config(1)**, **notmuch-count(1)**,
-**notmuch-dump(1)**, **notmuch-hooks(5)**, **notmuch-insert(1)**,
-**notmuch-new(1)**, **notmuch-restore(1)**, **notmuch-search(1)**,
-**notmuch-search-terms(7)**, **notmuch-show(1)**, **notmuch-tag(1)**
+**notmuch(1)**,
+**notmuch-config(1)**,
+**notmuch-count(1)**,
+**notmuch-dump(1)**,
+**notmuch-hooks(5)**,
+**notmuch-insert(1)**,
+**notmuch-new(1)**,
+**notmuch-restore(1)**,
+**notmuch-search(1)**,
+**notmuch-search-terms(7)**,
+**notmuch-show(1)**,
+**notmuch-tag(1)**
index cb68bc8a72b2dd4e673934c4c196c75760dd7238..b578af1fbf19ca6b5afdb479cac1ecdf2645a214 100644 (file)
@@ -62,14 +62,15 @@ Supported options for **restore** include
 
         **properties**
 
-          Output per-message (key,value) metadata.  Each line starts
+          Restore per-message (key,value) metadata.  Each line starts
           with "#= ", followed by a message id, and a space separated
-          list of key=value pairs.  pair.  Ids, keys and values are
-          hex encoded if needed.
+          list of key=value pairs.  Ids, keys and values are hex
+          encoded if needed.  See **notmuch-properties(7)** for more
+          details.
 
         **tags**
 
-          Output per-message metadata, namely tags. See *format* above
+          Restore per-message metadata, namely tags. See *format* above
           for more details.
 
       The default is to restore all available types of data.  The
@@ -89,7 +90,16 @@ standard input.
 SEE ALSO
 ========
 
-**notmuch(1)**, **notmuch-config(1)**, **notmuch-count(1)**,
-**notmuch-dump(1)**, **notmuch-hooks(5)**, **notmuch-insert(1)**,
-**notmuch-new(1)**, **notmuch-reply(1)**, **notmuch-search(1)**,
-**notmuch-search-terms(7)**, **notmuch-show(1)**, **notmuch-tag(1)**
+**notmuch(1)**,
+**notmuch-config(1)**,
+**notmuch-count(1)**,
+**notmuch-dump(1)**,
+**notmuch-hooks(5)**,
+**notmuch-insert(1)**,
+**notmuch-new(1)**,
+**notmuch-properties(7)**,
+**notmuch-reply(1)**,
+**notmuch-search(1)**,
+**notmuch-search-terms(7)**,
+**notmuch-show(1)**,
+**notmuch-tag(1)**
index 4e660a6facbbb25295688795a28c8b7991627809..67c1ce904c0152d22a53b9b46ace5c6df67df84f 100644 (file)
@@ -42,7 +42,9 @@ Supported options for **search** include
             the search terms. The summary includes the thread ID, date,
             the number of messages in the thread (both the number
             matched and the total number), the authors of the thread and
-            the subject.
+           the subject. In the case where a thread contains multiple files for
+           some messages, the total number of files is printed in parentheses
+           (see below for an example).
 
         **threads**
             Output the thread IDs of all threads with any message
@@ -135,6 +137,19 @@ Supported options for **search** include
         prefix. The prefix matches messages based on filenames. This
         option filters filenames of the matching messages.
 
+EXAMPLE
+=======
+
+The following shows an example of the summary output format, with one
+message having multiple filenames.
+
+::
+
+  % notmuch search date:today.. and tag:bad-news
+  thread:0000000000063c10 Today [1/1] Some Persun; To the bone (inbox unread)
+  thread:0000000000063c25 Today [1/1(2)] Ann Other; Bears (inbox unread)
+  thread:0000000000063c00 Today [1/1] A Thurd; Bites, stings, sad feelings (inbox unread)
+
 EXIT STATUS
 ===========
 
@@ -149,8 +164,16 @@ This command supports the following special exit status codes
 SEE ALSO
 ========
 
-**notmuch(1)**, **notmuch-config(1)**, **notmuch-count(1)**,
-**notmuch-dump(1)**, **notmuch-hooks(5)**, **notmuch-insert(1)**,
-**notmuch-new(1)**, **notmuch-reply(1)**, **notmuch-restore(1)**,
-**notmuch-search-terms(7)**, **notmuch-show(1)**, **notmuch-tag(1)**
+**notmuch(1)**,
+**notmuch-config(1)**,
+**notmuch-count(1)**,
+**notmuch-dump(1)**,
+**notmuch-hooks(5)**,
+**notmuch-insert(1)**,
+**notmuch-new(1)**,
+**notmuch-reply(1)**,
+**notmuch-restore(1)**,
+**notmuch-search-terms(7)**,
+**notmuch-show(1)**,
+**notmuch-tag(1)**
 **notmuch-address(1)**
index 9eb5198daf9046064d84391e8ecb791acce2334f..7ba091cf57c3ce3a76784b7b663c73cc5914d774 100644 (file)
@@ -176,7 +176,15 @@ This command supports the following special exit status codes
 SEE ALSO
 ========
 
-**notmuch(1)**, **notmuch-config(1)**, **notmuch-count(1)**,
-**notmuch-dump(1)**, **notmuch-hooks(5)**, **notmuch-insert(1)**,
-**notmuch-new(1)**, **notmuch-reply(1)**, **notmuch-restore(1)**,
-**notmuch-search(1)**, **notmuch-search-terms(7)**, **notmuch-tag(1)**
+**notmuch(1)**,
+**notmuch-config(1)**,
+**notmuch-count(1)**,
+**notmuch-dump(1)**,
+**notmuch-hooks(5)**,
+**notmuch-insert(1)**,
+**notmuch-new(1)**,
+**notmuch-reply(1)**,
+**notmuch-restore(1)**,
+**notmuch-search(1)**,
+**notmuch-search-terms(7)**,
+**notmuch-tag(1)**
index 2e7e1d325949a3f868410d4f21fdbb2a45ba44ef..88e454ba5c5b75c55f2e0c4d78e22c06de03b78b 100644 (file)
@@ -101,7 +101,15 @@ of the tag **space in tags**
 SEE ALSO
 ========
 
-**notmuch(1)**, **notmuch-config(1)**, **notmuch-count(1)**,
-**notmuch-dump(1)**, **notmuch-hooks(5)**, **notmuch-insert(1)**,
-**notmuch-new(1)**, **notmuch-reply(1)**, **notmuch-restore(1)**,
-**notmuch-search(1)**, **notmuch-search-terms(7)**, **notmuch-show(1)**,
+**notmuch(1)**,
+**notmuch-config(1)**,
+**notmuch-count(1)**,
+**notmuch-dump(1)**,
+**notmuch-hooks(5)**,
+**notmuch-insert(1)**,
+**notmuch-new(1)**,
+**notmuch-reply(1)**,
+**notmuch-restore(1)**,
+**notmuch-search(1)**,
+**notmuch-search-terms(7)**,
+**notmuch-show(1)**,
index cb350d1a19767e4ac354925b525f8fc2c0aa25f9..358f42e8d5bef9335a9649839b0e73289ddadc61 100644 (file)
@@ -161,11 +161,22 @@ of notmuch.
 SEE ALSO
 ========
 
-**notmuch-address(1)**, **notmuch-compact(1)**, **notmuch-config(1)**,
-**notmuch-count(1)**, **notmuch-dump(1)**, **notmuch-hooks(5)**,
-**notmuch-insert(1)**, **notmuch-new(1)**, **notmuch-reply(1)**,
-**notmuch-restore(1)**, **notmuch-search(1)**,
-**notmuch-search-terms(7)**, **notmuch-show(1)**, **notmuch-tag(1)**
+**notmuch-address(1)**,
+**notmuch-compact(1)**,
+**notmuch-config(1)**,
+**notmuch-count(1)**,
+**notmuch-dump(1)**,
+**notmuch-hooks(5)**,
+**notmuch-insert(1)**,
+**notmuch-new(1)**,
+**notmuch-properties(7)**,
+**notmuch-reindex(1)**,
+**notmuch-reply(1)**,
+**notmuch-restore(1)**,
+**notmuch-search(1)**,
+**notmuch-search-terms(7)**,
+**notmuch-show(1)**,
+**notmuch-tag(1)**
 
 The notmuch website: **https://notmuchmail.org**
 
index 3a72a80b62829c68add26bbffe824f2dfa0d343d..f07e4dabf21ab5dc608eb989d3e4c94218391fbe 100644 (file)
@@ -49,7 +49,15 @@ The currently available hooks are described below.
 SEE ALSO
 ========
 
-**notmuch(1)**, **notmuch-config(1)**, **notmuch-count(1)**,
-**notmuch-dump(1)**, **notmuch-insert(1)**, **notmuch-new(1)**,
-**notmuch-reply(1)**, **notmuch-restore(1)**, **notmuch-search(1)**,
-**notmuch-search-terms(7)**, **notmuch-show(1)**, **notmuch-tag(1)**
+**notmuch(1)**,
+**notmuch-config(1)**,
+**notmuch-count(1)**,
+**notmuch-dump(1)**,
+**notmuch-insert(1)**,
+**notmuch-new(1)**,
+**notmuch-reply(1)**,
+**notmuch-restore(1)**,
+**notmuch-search(1)**,
+**notmuch-search-terms(7)**,
+**notmuch-show(1)**,
+**notmuch-tag(1)**
diff --git a/doc/man7/notmuch-properties.rst b/doc/man7/notmuch-properties.rst
new file mode 100644 (file)
index 0000000..6812135
--- /dev/null
@@ -0,0 +1,87 @@
+==================
+notmuch-properties
+==================
+
+SYNOPSIS
+========
+
+**notmuch** **count** **property:**\ <*key*>=<*value*>
+
+**notmuch** **search** **property:**\ <*key*>=<*value*>
+
+**notmuch** **show** **property:**\ <*key*>=<*value*>
+
+**notmuch** **reindex** **property:**\ <*key*>=<*value*>
+
+**notmuch** **tag** +<*tag*> **property:**\ <*key*>=<*value*>
+
+
+**notmuch** **dump** **--include=properties**
+
+**notmuch** **restore** **--include=properties**
+
+DESCRIPTION
+===========
+
+Several notmuch commands can search for, modify, add or remove
+properties associated with specific messages.  Properties are
+key/value pairs, and a message can have more than one key/value pair
+for the same key.
+
+While users can select based on a specific property in their search
+terms with the prefix **property:**, the notmuch command-line
+interface does not provide mechanisms for modifying properties
+directly to the user.
+
+Instead, message properties are expected to be set and used
+programmatically, according to logic in notmuch itself, or in
+extensions to it.
+
+Extensions to notmuch which make use of properties are encouraged to
+report the specific properties used to the upstream notmuch project,
+as a way of avoiding collisions in the property namespace.
+
+CONVENTIONS
+===========
+
+Any property with a key that starts with "index." will be removed (and
+possibly re-set) upon reindexing (see **notmuch-reindex(1)**).
+
+MESSAGE PROPERTIES
+==================
+
+The following properties are set by notmuch internally in the course
+of its normal activity.
+
+**index.decryption**
+
+    If a message contains encrypted content, and notmuch tries to
+    decrypt that content during indexing, it will add the property
+    ``index.decryption=success`` when the cleartext was successfully
+    indexed.  If notmuch attempts to decrypt any part of a message
+    during indexing and that decryption attempt fails, it will add the
+    property ``index.decryption=failure`` to the message.
+
+    Note that it's possible for a single message to have both
+    ``index.decryption=success`` and ``index.decryption=failure``.
+    Consider an encrypted e-mail message that contains another
+    encrypted e-mail message as an attachment -- if the outer message
+    can be decrypted, but the attached part cannot, then both
+    properties will be set on the message as a whole.
+
+    If notmuch never tried to decrypt an encrypted message during
+    indexing (which is the default, see ``index.try_decrypt`` in
+    **notmuch-config(1)**), then this property will not be set on that
+    message.
+
+SEE ALSO
+========
+
+**notmuch(1)**,
+**notmuch-config(1)**,
+**notmuch-dump(1)**,
+**notmuch-insert(1)**,
+**notmuch-new(1)**,
+**notmuch-reindex(1)**,
+**notmuch-restore(1)**,
+***notmuch-search-terms(7)**
index 47cab48d3ee97df0d9db80028a586c40fd1a3b2f..637f7777dd932f6c027f966797654f09f4538e04 100644 (file)
@@ -9,6 +9,8 @@ SYNOPSIS
 
 **notmuch** **dump** [--format=(batch-tag|sup)] [--] [--output=<*file*>] [--] [<*search-term*> ...]
 
+**notmuch** **reindex** [option ...] <*search-term*> ...
+
 **notmuch** **search** [option ...] <*search-term*> ...
 
 **notmuch** **show** [option ...] <*search-term*> ...
@@ -157,7 +159,8 @@ below).
 The **property:** prefix searches for messages with a particular
 <key>=<value> property pair. Properties are used internally by notmuch
 (and extensions) to add metadata to messages. A given key can be
-present on a given message with several different values.
+present on a given message with several different values.  See
+**notmuch-properties(7)** for more details.
 
 Operators
 ---------
@@ -235,7 +238,7 @@ Boolean and Probabilistic Prefixes
 Xapian (and hence notmuch) prefixes are either **boolean**, supporting
 exact matches like "tag:inbox" or **probabilistic**, supporting a more
 flexible **term** based searching. Certain **special** prefixes are
-processed by notmuch in a way not stricly fitting either of Xapian's
+processed by notmuch in a way not strictly fitting either of Xapian's
 built in styles. The prefixes currently supported by notmuch are as
 follows.
 
@@ -253,7 +256,7 @@ In general Xapian distinguishes between lists of terms and
 **phrases**. Phrases are indicated by double quotes (but beware you
 probably need to protect those from your shell) and insist that those
 unstemmed words occur in that order. One useful, but initially
-surprising feature is that the following are equivalant ways to write
+surprising feature is that the following are equivalent ways to write
 the same phrase.
 
 - "a list of words"
@@ -262,7 +265,7 @@ the same phrase.
 - a.list.of.words
 
 Both parenthesised lists of terms and quoted phrases are ok with
-probabilisitic prefixes such as **to:**, **from:**, and **subject:**. In particular
+probabilistic prefixes such as **to:**, **from:**, and **subject:**. In particular
 
 ::
 
@@ -419,7 +422,17 @@ Currently the following features require field processor support:
 SEE ALSO
 ========
 
-**notmuch(1)**, **notmuch-config(1)**, **notmuch-count(1)**,
-**notmuch-dump(1)**, **notmuch-hooks(5)**, **notmuch-insert(1)**,
-**notmuch-new(1)**, **notmuch-reply(1)**, **notmuch-restore(1)**,
-**notmuch-search(1)**, **notmuch-show(1)**, **notmuch-tag(1)**
+**notmuch(1)**,
+**notmuch-config(1)**,
+**notmuch-count(1)**,
+**notmuch-dump(1)**,
+**notmuch-hooks(5)**,
+**notmuch-insert(1)**,
+**notmuch-new(1)**,
+**notmuch-reindex(1)**,
+**notmuch-properties(1)**,
+***notmuch-reply(1)**,
+**notmuch-restore(1)**,
+**notmuch-search(1)**,
+***notmuch-show(1)**,
+**notmuch-tag(1)**
index 8e15eed7111806cad24eaa0185d73cea6b23e83a..fbf8dde6f1760c409036d6b9b473a59d3c943ae6 100644 (file)
@@ -1,4 +1,4 @@
-.eldeps*
-*.elc
-notmuch-version.el
-notmuch-pkg.el
+/.eldeps*
+/*.elc
+/notmuch-version.el
+/notmuch-pkg.el
index f937e708406bbb73c3c9f164a904385c2e5cf91b..64887a43fe652b04332e0e3ed9b15577656cc9f8 100644 (file)
@@ -159,7 +159,7 @@ matching `notmuch-address-completion-headers-regexp'.
                       #'notmuch-address-expand-name)))
       (when setup-company
        (notmuch-company-setup))
-      (unless (memq pair message-completion-alist)
+      (unless (member pair message-completion-alist)
        (setq message-completion-alist
              (push pair message-completion-alist)))))
 
index 337b20ace74f36b91b5ce76614f7fc3552a54ac5..010be454e77420c2f945fdfc3a7895283a8d7209 100644 (file)
@@ -768,23 +768,23 @@ signaled error.  This function does not return."
   (error "%s" (concat msg (when extra
                            " (see *Notmuch errors* for more details)"))))
 
-(defun notmuch-check-async-exit-status (proc msg &optional command err-file)
+(defun notmuch-check-async-exit-status (proc msg &optional command err)
   "If PROC exited abnormally, pop up an error buffer and signal an error.
 
 This is a wrapper around `notmuch-check-exit-status' for
 asynchronous process sentinels.  PROC and MSG must be the
-arguments passed to the sentinel.  COMMAND and ERR-FILE, if
-provided, are passed to `notmuch-check-exit-status'.  If COMMAND
-is not provided, it is taken from `process-command'."
+arguments passed to the sentinel.  COMMAND and ERR, if provided,
+are passed to `notmuch-check-exit-status'.  If COMMAND is not
+provided, it is taken from `process-command'."
   (let ((exit-status
         (case (process-status proc)
           ((exit) (process-exit-status proc))
           ((signal) msg))))
     (when exit-status
       (notmuch-check-exit-status exit-status (or command (process-command proc))
-                                nil err-file))))
+                                nil err))))
 
-(defun notmuch-check-exit-status (exit-status command &optional output err-file)
+(defun notmuch-check-exit-status (exit-status command &optional output err)
   "If EXIT-STATUS is non-zero, pop up an error buffer and signal an error.
 
 If EXIT-STATUS is non-zero, pop up a notmuch error buffer
@@ -793,9 +793,9 @@ be a number indicating the exit status code of a process or a
 string describing the signal that terminated the process (such as
 returned by `call-process').  COMMAND must be a list giving the
 command and its arguments.  OUTPUT, if provided, is a string
-giving the output of command.  ERR-FILE, if provided, is the name
-of a file containing the error output of command.  OUTPUT and the
-contents of ERR-FILE will be included in the error message."
+giving the output of command.  ERR, if provided, is the error
+output of command.  OUTPUT and ERR will be included in the error
+message."
 
   (cond
    ((eq exit-status 0) t)
@@ -808,12 +808,7 @@ You may need to restart Emacs or upgrade your notmuch Emacs package."))
 Emacs requested a newer output format than supported by the notmuch CLI.
 You may need to restart Emacs or upgrade your notmuch package."))
    (t
-    (let* ((err (when err-file
-                 (with-temp-buffer
-                   (insert-file-contents err-file)
-                   (unless (eobp)
-                     (buffer-string)))))
-          (command-string
+    (let* ((command-string
            (mapconcat (lambda (arg)
                         (shell-quote-argument
                          (cond ((stringp arg) arg)
@@ -889,9 +884,13 @@ error."
   (with-temp-buffer
     (let ((err-file (make-temp-file "nmerr")))
       (unwind-protect
-         (let ((status (notmuch-call-notmuch--helper (list t err-file) args)))
+         (let ((status (notmuch-call-notmuch--helper (list t err-file) args))
+               (err (with-temp-buffer
+                      (insert-file-contents err-file)
+                      (unless (eobp)
+                        (buffer-string)))))
            (notmuch-check-exit-status status (cons notmuch-command args)
-                                      (buffer-string) err-file)
+                                      (buffer-string) err)
            (goto-char (point-min))
            (read (current-buffer)))
        (delete-file err-file)))))
@@ -910,30 +909,56 @@ invoke `set-process-sentinel' directly on the returned process,
 as that will interfere with the handling of stderr and the exit
 status."
 
-  ;; There is no way (as of Emacs 24.3) to capture stdout and stderr
-  ;; separately for asynchronous processes, or even to redirect stderr
-  ;; to a file, so we use a trivial shell wrapper to send stderr to a
-  ;; temporary file and clean things up in the sentinel.
-  (let* ((err-file (make-temp-file "nmerr"))
-        ;; Use a pipe
-        (process-connection-type nil)
-        ;; Find notmuch using Emacs' `exec-path'
-        (command (or (executable-find notmuch-command)
-                     (error "command not found: %s" notmuch-command)))
-        (proc (apply #'start-process name buffer
-                     "/bin/sh" "-c"
-                     "exec 2>\"$1\"; shift; exec \"$0\" \"$@\""
-                     command err-file args)))
-    (process-put proc 'err-file err-file)
+  (let (err-file err-buffer proc
+       ;; Find notmuch using Emacs' `exec-path'
+       (command (or (executable-find notmuch-command)
+                    (error "Command not found: %s" notmuch-command))))
+    (if (fboundp 'make-process)
+       (progn
+         (setq err-buffer (generate-new-buffer " *notmuch-stderr*"))
+         ;; Emacs 25 and newer has `make-process', which allows
+         ;; redirecting stderr independently from stdout to a
+         ;; separate buffer. As this allows us to avoid using a
+         ;; temporary file and shell invocation, use it when
+         ;; available.
+         (setq proc (make-process
+                     :name name
+                     :buffer buffer
+                     :command (cons command args)
+                     :connection-type 'pipe
+                     :stderr err-buffer))
+         (process-put proc 'err-buffer err-buffer)
+         ;; Silence "Process NAME stderr finished" in stderr by adding a
+         ;; no-op sentinel to the fake stderr process object
+         (set-process-sentinel (get-buffer-process err-buffer) #'ignore))
+
+      ;; On Emacs versions before 25, there is no way to capture
+      ;; stdout and stderr separately for asynchronous processes, or
+      ;; even to redirect stderr to a file, so we use a trivial shell
+      ;; wrapper to send stderr to a temporary file and clean things
+      ;; up in the sentinel.
+      (setq err-file (make-temp-file "nmerr"))
+      (let ((process-connection-type nil)) ;; Use a pipe
+       (setq proc (apply #'start-process name buffer
+                         "/bin/sh" "-c"
+                         "exec 2>\"$1\"; shift; exec \"$0\" \"$@\""
+                         command err-file args)))
+      (process-put proc 'err-file err-file))
+
     (process-put proc 'sub-sentinel sentinel)
     (process-put proc 'real-command (cons notmuch-command args))
     (set-process-sentinel proc #'notmuch-start-notmuch-sentinel)
     proc))
 
 (defun notmuch-start-notmuch-sentinel (proc event)
-  (let ((err-file (process-get proc 'err-file))
-       (sub-sentinel (process-get proc 'sub-sentinel))
-       (real-command (process-get proc 'real-command)))
+  "Process sentinel function used by `notmuch-start-notmuch'."
+  (let* ((err-file (process-get proc 'err-file))
+        (err-buffer (or (process-get proc 'err-buffer)
+                        (find-file-noselect err-file)))
+        (err (when (not (zerop (buffer-size err-buffer)))
+               (with-current-buffer err-buffer (buffer-string))))
+        (sub-sentinel (process-get proc 'sub-sentinel))
+        (real-command (process-get proc 'real-command)))
     (condition-case err
        (progn
          ;; Invoke the sub-sentinel, if any
@@ -945,12 +970,13 @@ status."
          ;; and there's no point in telling the user that (but we
          ;; still check for and report stderr output below).
          (when (buffer-live-p (process-buffer proc))
-           (notmuch-check-async-exit-status proc event real-command err-file))
+           (notmuch-check-async-exit-status proc event real-command err))
          ;; If that didn't signal an error, then any error output was
          ;; really warning output.  Show warnings, if any.
          (let ((warnings
-                (with-temp-buffer
-                  (unless (= (second (insert-file-contents err-file)) 0)
+                (when err
+                  (with-current-buffer err-buffer
+                    (goto-char (point-min))
                     (end-of-line)
                     ;; Show first line; stuff remaining lines in the
                     ;; errors buffer.
@@ -964,7 +990,8 @@ status."
        ;; Emacs behaves strangely if an error escapes from a sentinel,
        ;; so turn errors into messages.
        (message "%s" (error-message-string err))))
-    (ignore-errors (delete-file err-file))))
+    (when err-buffer (kill-buffer err-buffer))
+    (when err-file (ignore-errors (delete-file err-file)))))
 
 ;; This variable is used only buffer local, but it needs to be
 ;; declared globally first to avoid compiler warnings.
index fd64b362b542b9bef5c169c29fb52ba2128c4f4e..7a341ebf0588a3e91ee3666b119701be2ca91c8f 100644 (file)
@@ -218,7 +218,7 @@ mutiple parts get a header."
                     else
                     collect pair)))
          (notmuch-mua-mail (plist-get reply-headers :To)
-                           (plist-get reply-headers :Subject)
+                           (notmuch-sanitize (plist-get reply-headers :Subject))
                            (notmuch-headers-plist-to-alist reply-headers)
                            nil (notmuch-mua-get-switch-function))))
 
index dd423765d7139cc2004c5a3b03c6989cdca57ff1..9939027700952c0f5255700cc9be5a274ae4f99b 100644 (file)
@@ -1269,7 +1269,9 @@ matched."
     ;; aren't wiped out.
     (setq notmuch-show-thread-id thread-id
          notmuch-show-parent-buffer parent-buffer
-         notmuch-show-query-context query-context
+         notmuch-show-query-context (if (or (string= query-context "")
+                                            (string= query-context "*"))
+                                        nil query-context)
 
          notmuch-show-process-crypto notmuch-crypto-process-mime
          ;; If `elide-toggle', invert the default value.
index 09d182dfb818d6844efdab4ca3d626e9f2dd7d89..0500927d37ce2b1e0173fb0f6ff7bc159a85018e 100644 (file)
@@ -457,7 +457,7 @@ QUERY should be a string containing the search-terms.
 TAG-CHANGES is a list of strings of the form \"+tag\" or
 \"-tag\" to add or remove tags, respectively.
 
-Note: Other code should always use this function alter tags of
+Note: Other code should always use this function to alter tags of
 messages instead of running (notmuch-call-notmuch-process \"tag\" ..)
 directly, so that hooks specified in notmuch-before-tag-hook and
 notmuch-after-tag-hook will be run."
index 022525aec0f5d45c97dbb1079016fab58d1ab17d..c00315e8dd40edaadfc369a95b2545fce49affbf 100644 (file)
@@ -598,6 +598,8 @@ message will be \"unarchived\", i.e. the tag changes in
 (defun notmuch-tree-refresh-view ()
   "Refresh view."
   (interactive)
+  (when (get-buffer-process (current-buffer))
+    (error "notmuch tree process already running for current buffer"))
   (let ((inhibit-read-only t)
        (basic-query notmuch-tree-basic-query)
        (query-context notmuch-tree-query-context)
@@ -897,7 +899,9 @@ the same as for the function notmuch-tree."
   (notmuch-tree-mode)
   (add-hook 'post-command-hook #'notmuch-tree-command-hook t t)
   (setq notmuch-tree-basic-query basic-query)
-  (setq notmuch-tree-query-context query-context)
+  (setq notmuch-tree-query-context (if (or (string= query-context "")
+                                          (string= query-context "*"))
+                                      nil 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
index 0aeff560fdfc5de08630be749d4d7e4a1689f89a..44402f8aa8256b55816b9a256b6b7f7468e54e9d 100644 (file)
 ;; Have fun, and let us know if you have any comment, questions, or
 ;; kudos: Notmuch list <notmuch@notmuchmail.org> (subscription is not
 ;; required, but is available from https://notmuchmail.org).
-
+;;
+;; Note for MELPA users (and others tracking the development version
+;; of notmuch-emacs):
+;;
+;; This emacs package needs a fairly closely matched version of the
+;; notmuch program. If you use the MELPA version of notmuch.el (as
+;; opposed to MELPA stable), you should be prepared to track the
+;; master development branch (i.e. build from git) for the notmuch
+;; program as well. Upgrading notmuch-emacs too far beyond the notmuch
+;; program can CAUSE YOUR EMAIL TO STOP WORKING.
+;;
+;; TL;DR: notmuch-emacs from MELPA and notmuch from distro packages is
+;; NOT SUPPORTED.
+;;
 ;;; Code:
 
 (eval-when-compile (require 'cl))
index b269db4e821265a065989a9b00c5f714c6b96c43..a1ba4b45411be8c26f28f1906c71a6428c2cdada 100644 (file)
@@ -16,6 +16,8 @@
  * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
  */
 
+#include <stdbool.h>
+
 #include "gmime-filter-reply.h"
 
 /**
@@ -87,8 +89,8 @@ static void
 g_mime_filter_reply_init (GMimeFilterReply *filter, GMimeFilterReplyClass *klass)
 {
        (void) klass;
-       filter->saw_nl = TRUE;
-       filter->saw_angle = FALSE;
+       filter->saw_nl = true;
+       filter->saw_angle = false;
 }
 
 static void
@@ -117,43 +119,43 @@ filter_filter (GMimeFilter *filter, char *inbuf, size_t inlen, size_t prespace,
 
        (void) prespace;
        if (reply->encode) {
-               g_mime_filter_set_size (filter, 3 * inlen, FALSE);
+               g_mime_filter_set_size (filter, 3 * inlen, false);
 
                outptr = filter->outbuf;
                while (inptr < inend) {
                        if (reply->saw_nl) {
                                *outptr++ = '>';
                                *outptr++ = ' ';
-                               reply->saw_nl = FALSE;
+                               reply->saw_nl = false;
                        }
                        if (*inptr == '\n')
-                               reply->saw_nl = TRUE;
+                               reply->saw_nl = true;
                        else
-                               reply->saw_nl = FALSE;
+                               reply->saw_nl = false;
                        if (*inptr != '\r')
                                *outptr++ = *inptr;
                        inptr++;
                }
        } else {
-               g_mime_filter_set_size (filter, inlen + 1, FALSE);
+               g_mime_filter_set_size (filter, inlen + 1, false);
 
                outptr = filter->outbuf;
                while (inptr < inend) {
                        if (reply->saw_nl) {
                                if (*inptr == '>')
-                                       reply->saw_angle = TRUE;
+                                       reply->saw_angle = true;
                                else
                                        *outptr++ = *inptr;
-                               reply->saw_nl = FALSE;
+                               reply->saw_nl = false;
                        } else if (reply->saw_angle) {
                                if (*inptr == ' ')
                                        ;
                                else
                                        *outptr++ = *inptr;
-                               reply->saw_angle = FALSE;
+                               reply->saw_angle = false;
                        } else if (*inptr != '\r') {
                                if (*inptr == '\n')
-                                       reply->saw_nl = TRUE;
+                                       reply->saw_nl = true;
                                *outptr++ = *inptr;
                        }
 
@@ -179,19 +181,19 @@ filter_reset (GMimeFilter *filter)
 {
        GMimeFilterReply *reply = (GMimeFilterReply *) filter;
 
-       reply->saw_nl = TRUE;
-       reply->saw_angle = FALSE;
+       reply->saw_nl = true;
+       reply->saw_angle = false;
 }
 
 
 /**
  * g_mime_filter_reply_new:
- * @encode: %TRUE if the filter should encode or %FALSE otherwise
+ * @encode: %true if the filter should encode or %false otherwise
  * @dots: encode/decode dots (as for SMTP)
  *
  * Creates a new #GMimeFilterReply filter.
  *
- * If @encode is %TRUE, then all lines will be prefixed by "> ",
+ * If @encode is %true, then all lines will be prefixed by "> ",
  * otherwise any lines starting with "> " will have that removed
  *
  * Returns: a new #GMimeFilterReply filter.
@@ -201,7 +203,7 @@ g_mime_filter_reply_new (gboolean encode)
 {
        GMimeFilterReply *new_reply;
 
-       new_reply = (GMimeFilterReply *) g_object_newv (GMIME_TYPE_FILTER_REPLY, 0, NULL);
+       new_reply = (GMimeFilterReply *) g_object_new (GMIME_TYPE_FILTER_REPLY, NULL);
        new_reply->encode = encode;
 
        return (GMimeFilter *) new_reply;
index bf6e06494748d965a67dc33643b38a81f817e1a1..8aa03891d775b7591e3f67a5a66fe8cd9c98ca9b 100644 (file)
@@ -19,7 +19,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=$(lib)/notmuch.sym,-soname=$(SONAME) $(NO_UNDEFINED_LDFLAGS)
+LIBRARY_LINK_FLAG = -shared -Wl,--version-script=$(srcdir)/$(lib)/notmuch.sym,-soname=$(SONAME) $(NO_UNDEFINED_LDFLAGS)
 ifeq ($(PLATFORM),OPENBSD)
 LIBRARY_LINK_FLAG += -lc
 endif
@@ -38,10 +38,12 @@ libnotmuch_c_srcs =         \
        $(dir)/filenames.c      \
        $(dir)/string-list.c    \
        $(dir)/message-file.c   \
+       $(dir)/message-id.c     \
        $(dir)/messages.c       \
        $(dir)/sha1.c           \
        $(dir)/built-with.c     \
        $(dir)/string-map.c     \
+       $(dir)/indexopts.c      \
        $(dir)/tags.c
 
 libnotmuch_cxx_srcs =          \
@@ -50,6 +52,7 @@ libnotmuch_cxx_srcs =         \
        $(dir)/directory.cc     \
        $(dir)/index.cc         \
        $(dir)/message.cc       \
+       $(dir)/add-message.cc   \
        $(dir)/message-property.cc \
        $(dir)/query.cc         \
        $(dir)/query-fp.cc      \
diff --git a/lib/add-message.cc b/lib/add-message.cc
new file mode 100644 (file)
index 0000000..f5fac8b
--- /dev/null
@@ -0,0 +1,598 @@
+#include "database-private.h"
+
+/* Parse a References header value, putting a (talloc'ed under 'ctx')
+ * copy of each referenced message-id into 'hash'.
+ *
+ * We explicitly avoid including any reference identical to
+ * 'message_id' in the result (to avoid mass confusion when a single
+ * message references itself cyclically---and yes, mail messages are
+ * not infrequent in the wild that do this---don't ask me why).
+ *
+ * Return the last reference parsed, if it is not equal to message_id.
+ */
+static char *
+parse_references (void *ctx,
+                 const char *message_id,
+                 GHashTable *hash,
+                 const char *refs)
+{
+    char *ref, *last_ref = NULL;
+
+    if (refs == NULL || *refs == '\0')
+       return NULL;
+
+    while (*refs) {
+       ref = _notmuch_message_id_parse (ctx, refs, &refs);
+
+       if (ref && strcmp (ref, message_id)) {
+           g_hash_table_add (hash, ref);
+           last_ref = ref;
+       }
+    }
+
+    /* The return value of this function is used to add a parent
+     * reference to the database.  We should avoid making a message
+     * its own parent, thus the above check.
+     */
+    return talloc_strdup(ctx, last_ref);
+}
+
+static const char *
+_notmuch_database_generate_thread_id (notmuch_database_t *notmuch)
+{
+    /* 16 bytes (+ terminator) for hexadecimal representation of
+     * a 64-bit integer. */
+    static char thread_id[17];
+    Xapian::WritableDatabase *db;
+
+    db = static_cast <Xapian::WritableDatabase *> (notmuch->xapian_db);
+
+    notmuch->last_thread_id++;
+
+    sprintf (thread_id, "%016" PRIx64, notmuch->last_thread_id);
+
+    db->set_metadata ("last_thread_id", thread_id);
+
+    return thread_id;
+}
+
+static char *
+_get_metadata_thread_id_key (void *ctx, const char *message_id)
+{
+    if (strlen (message_id) > NOTMUCH_MESSAGE_ID_MAX)
+       message_id = _notmuch_message_id_compressed (ctx, message_id);
+
+    return talloc_asprintf (ctx, NOTMUCH_METADATA_THREAD_ID_PREFIX "%s",
+                           message_id);
+}
+
+
+static notmuch_status_t
+_resolve_message_id_to_thread_id_old (notmuch_database_t *notmuch,
+                                     void *ctx,
+                                     const char *message_id,
+                                     const char **thread_id_ret);
+
+
+/* Find the thread ID to which the message with 'message_id' belongs.
+ *
+ * Note: 'thread_id_ret' must not be NULL!
+ * On success '*thread_id_ret' is set to a newly talloced string belonging to
+ * 'ctx'.
+ *
+ * Note: If there is no message in the database with the given
+ * 'message_id' then a new thread_id will be allocated for this
+ * message ID and stored in the database metadata so that the
+ * thread ID can be looked up if the message is added to the database
+ * later.
+ */
+static notmuch_status_t
+_resolve_message_id_to_thread_id (notmuch_database_t *notmuch,
+                                 void *ctx,
+                                 const char *message_id,
+                                 const char **thread_id_ret)
+{
+    notmuch_private_status_t status;
+    notmuch_message_t *message;
+
+    if (! (notmuch->features & NOTMUCH_FEATURE_GHOSTS))
+       return _resolve_message_id_to_thread_id_old (notmuch, ctx, message_id,
+                                                    thread_id_ret);
+
+    /* Look for this message (regular or ghost) */
+    message = _notmuch_message_create_for_message_id (
+       notmuch, message_id, &status);
+    if (status == NOTMUCH_PRIVATE_STATUS_SUCCESS) {
+       /* Message exists */
+       *thread_id_ret = talloc_steal (
+           ctx, notmuch_message_get_thread_id (message));
+    } else if (status == NOTMUCH_PRIVATE_STATUS_NO_DOCUMENT_FOUND) {
+       /* Message did not exist.  Give it a fresh thread ID and
+        * populate this message as a ghost message. */
+       *thread_id_ret = talloc_strdup (
+           ctx, _notmuch_database_generate_thread_id (notmuch));
+       if (! *thread_id_ret) {
+           status = NOTMUCH_PRIVATE_STATUS_OUT_OF_MEMORY;
+       } else {
+           status = _notmuch_message_initialize_ghost (message, *thread_id_ret);
+           if (status == 0)
+               /* Commit the new ghost message */
+               _notmuch_message_sync (message);
+       }
+    } else {
+       /* Create failed. Fall through. */
+    }
+
+    notmuch_message_destroy (message);
+
+    return COERCE_STATUS (status, "Error creating ghost message");
+}
+
+/* Pre-ghost messages _resolve_message_id_to_thread_id */
+static notmuch_status_t
+_resolve_message_id_to_thread_id_old (notmuch_database_t *notmuch,
+                                     void *ctx,
+                                     const char *message_id,
+                                     const char **thread_id_ret)
+{
+    notmuch_status_t status;
+    notmuch_message_t *message;
+    std::string thread_id_string;
+    char *metadata_key;
+    Xapian::WritableDatabase *db;
+
+    status = notmuch_database_find_message (notmuch, message_id, &message);
+
+    if (status)
+       return status;
+
+    if (message) {
+       *thread_id_ret = talloc_steal (ctx,
+                                      notmuch_message_get_thread_id (message));
+
+       notmuch_message_destroy (message);
+
+       return NOTMUCH_STATUS_SUCCESS;
+    }
+
+    /* Message has not been seen yet.
+     *
+     * We may have seen a reference to it already, in which case, we
+     * can return the thread ID stored in the metadata. Otherwise, we
+     * generate a new thread ID and store it there.
+     */
+    db = static_cast <Xapian::WritableDatabase *> (notmuch->xapian_db);
+    metadata_key = _get_metadata_thread_id_key (ctx, message_id);
+    thread_id_string = notmuch->xapian_db->get_metadata (metadata_key);
+
+    if (thread_id_string.empty()) {
+       *thread_id_ret = talloc_strdup (ctx,
+                                       _notmuch_database_generate_thread_id (notmuch));
+       db->set_metadata (metadata_key, *thread_id_ret);
+    } else {
+       *thread_id_ret = talloc_strdup (ctx, thread_id_string.c_str());
+    }
+
+    talloc_free (metadata_key);
+
+    return NOTMUCH_STATUS_SUCCESS;
+}
+
+static notmuch_status_t
+_merge_threads (notmuch_database_t *notmuch,
+               const char *winner_thread_id,
+               const char *loser_thread_id)
+{
+    Xapian::PostingIterator loser, loser_end;
+    notmuch_message_t *message = NULL;
+    notmuch_private_status_t private_status;
+    notmuch_status_t ret = NOTMUCH_STATUS_SUCCESS;
+
+    _notmuch_database_find_doc_ids (notmuch, "thread", loser_thread_id, &loser, &loser_end);
+
+    for ( ; loser != loser_end; loser++) {
+       message = _notmuch_message_create (notmuch, notmuch,
+                                          *loser, &private_status);
+       if (message == NULL) {
+           ret = COERCE_STATUS (private_status,
+                                "Cannot find document for doc_id from query");
+           goto DONE;
+       }
+
+       _notmuch_message_remove_term (message, "thread", loser_thread_id);
+       _notmuch_message_add_term (message, "thread", winner_thread_id);
+       _notmuch_message_sync (message);
+
+       notmuch_message_destroy (message);
+       message = NULL;
+    }
+
+  DONE:
+    if (message)
+       notmuch_message_destroy (message);
+
+    return ret;
+}
+
+static void
+_my_talloc_free_for_g_hash (void *ptr)
+{
+    talloc_free (ptr);
+}
+
+notmuch_status_t
+_notmuch_database_link_message_to_parents (notmuch_database_t *notmuch,
+                                          notmuch_message_t *message,
+                                          notmuch_message_file_t *message_file,
+                                          const char **thread_id)
+{
+    GHashTable *parents = NULL;
+    const char *refs, *in_reply_to, *in_reply_to_message_id;
+    const char *last_ref_message_id, *this_message_id;
+    GList *l, *keys = NULL;
+    notmuch_status_t ret = NOTMUCH_STATUS_SUCCESS;
+
+    parents = g_hash_table_new_full (g_str_hash, g_str_equal,
+                                    _my_talloc_free_for_g_hash, NULL);
+    this_message_id = notmuch_message_get_message_id (message);
+
+    refs = _notmuch_message_file_get_header (message_file, "references");
+    last_ref_message_id = parse_references (message,
+                                           this_message_id,
+                                           parents, refs);
+
+    in_reply_to = _notmuch_message_file_get_header (message_file, "in-reply-to");
+    in_reply_to_message_id = parse_references (message,
+                                              this_message_id,
+                                              parents, in_reply_to);
+
+    /* For the parent of this message, use the last message ID of the
+     * References header, if available.  If not, fall back to the
+     * first message ID in the In-Reply-To header. */
+    if (last_ref_message_id) {
+       _notmuch_message_add_term (message, "replyto",
+                                  last_ref_message_id);
+    } else if (in_reply_to_message_id) {
+       _notmuch_message_add_term (message, "replyto",
+                            in_reply_to_message_id);
+    }
+
+    keys = g_hash_table_get_keys (parents);
+    for (l = keys; l; l = l->next) {
+       char *parent_message_id;
+       const char *parent_thread_id = NULL;
+
+       parent_message_id = (char *) l->data;
+
+       _notmuch_message_add_term (message, "reference",
+                                  parent_message_id);
+
+       ret = _resolve_message_id_to_thread_id (notmuch,
+                                               message,
+                                               parent_message_id,
+                                               &parent_thread_id);
+       if (ret)
+           goto DONE;
+
+       if (*thread_id == NULL) {
+           *thread_id = talloc_strdup (message, parent_thread_id);
+           _notmuch_message_add_term (message, "thread", *thread_id);
+       } else if (strcmp (*thread_id, parent_thread_id)) {
+           ret = _merge_threads (notmuch, *thread_id, parent_thread_id);
+           if (ret)
+               goto DONE;
+       }
+    }
+
+  DONE:
+    if (keys)
+       g_list_free (keys);
+    if (parents)
+       g_hash_table_unref (parents);
+
+    return ret;
+}
+
+static notmuch_status_t
+_notmuch_database_link_message_to_children (notmuch_database_t *notmuch,
+                                           notmuch_message_t *message,
+                                           const char **thread_id)
+{
+    const char *message_id = notmuch_message_get_message_id (message);
+    Xapian::PostingIterator child, children_end;
+    notmuch_message_t *child_message = NULL;
+    const char *child_thread_id;
+    notmuch_status_t ret = NOTMUCH_STATUS_SUCCESS;
+    notmuch_private_status_t private_status;
+
+    _notmuch_database_find_doc_ids (notmuch, "reference", message_id, &child, &children_end);
+
+    for ( ; child != children_end; child++) {
+
+       child_message = _notmuch_message_create (message, notmuch,
+                                                *child, &private_status);
+       if (child_message == NULL) {
+           ret = COERCE_STATUS (private_status,
+                                "Cannot find document for doc_id from query");
+           goto DONE;
+       }
+
+       child_thread_id = notmuch_message_get_thread_id (child_message);
+       if (*thread_id == NULL) {
+           *thread_id = talloc_strdup (message, child_thread_id);
+           _notmuch_message_add_term (message, "thread", *thread_id);
+       } else if (strcmp (*thread_id, child_thread_id)) {
+           _notmuch_message_remove_term (child_message, "reference",
+                                         message_id);
+           _notmuch_message_sync (child_message);
+           ret = _merge_threads (notmuch, *thread_id, child_thread_id);
+           if (ret)
+               goto DONE;
+       }
+
+       notmuch_message_destroy (child_message);
+       child_message = NULL;
+    }
+
+  DONE:
+    if (child_message)
+       notmuch_message_destroy (child_message);
+
+    return ret;
+}
+
+/* Fetch and clear the stored thread_id for message, or NULL if none. */
+static char *
+_consume_metadata_thread_id (void *ctx, notmuch_database_t *notmuch,
+                            notmuch_message_t *message)
+{
+    const char *message_id;
+    std::string stored_id;
+    char *metadata_key;
+
+    message_id = notmuch_message_get_message_id (message);
+    metadata_key = _get_metadata_thread_id_key (ctx, message_id);
+
+    /* Check if we have already seen related messages to this one.
+     * If we have then use the thread_id that we stored at that time.
+     */
+    stored_id = notmuch->xapian_db->get_metadata (metadata_key);
+    if (stored_id.empty ()) {
+       return NULL;
+    } else {
+       Xapian::WritableDatabase *db;
+
+       db = static_cast <Xapian::WritableDatabase *> (notmuch->xapian_db);
+
+       /* Clear the metadata for this message ID. We don't need it
+        * anymore. */
+       db->set_metadata (metadata_key, "");
+
+       return talloc_strdup (ctx, stored_id.c_str ());
+    }
+}
+
+/* Given a blank or ghost 'message' and its corresponding
+ * 'message_file' link it to existing threads in the database.
+ *
+ * First, if is_ghost, this retrieves the thread ID already stored in
+ * the message (which will be the case if a message was previously
+ * added that referenced this one).  If the message is blank
+ * (!is_ghost), it doesn't have a thread ID yet (we'll generate one
+ * later in this function).  If the database does not support ghost
+ * messages, this checks for a thread ID stored in database metadata
+ * for this message ID.
+ *
+ * Second, we look at 'message_file' and its link-relevant headers
+ * (References and In-Reply-To) for message IDs.
+ *
+ * Finally, we look in the database for existing message that
+ * reference 'message'.
+ *
+ * In all cases, we assign to the current message the first thread ID
+ * found. We will also merge any existing, distinct threads where this
+ * message belongs to both, (which is not uncommon when messages are
+ * processed out of order).
+ *
+ * Finally, if no thread ID has been found through referenced messages, we
+ * call _notmuch_message_generate_thread_id to generate a new thread
+ * ID. This should only happen for new, top-level messages, (no
+ * References or In-Reply-To header in this message, and no previously
+ * added message refers to this message).
+ */
+static notmuch_status_t
+_notmuch_database_link_message (notmuch_database_t *notmuch,
+                               notmuch_message_t *message,
+                               notmuch_message_file_t *message_file,
+                               bool is_ghost)
+{
+    void *local = talloc_new (NULL);
+    notmuch_status_t status;
+    const char *thread_id = NULL;
+
+    /* Check if the message already had a thread ID */
+    if (notmuch->features & NOTMUCH_FEATURE_GHOSTS) {
+       if (is_ghost)
+           thread_id = notmuch_message_get_thread_id (message);
+    } else {
+       thread_id = _consume_metadata_thread_id (local, notmuch, message);
+       if (thread_id)
+           _notmuch_message_add_term (message, "thread", thread_id);
+    }
+
+    status = _notmuch_database_link_message_to_parents (notmuch, message,
+                                                       message_file,
+                                                       &thread_id);
+    if (status)
+       goto DONE;
+
+    if (! (notmuch->features & NOTMUCH_FEATURE_GHOSTS)) {
+       /* In general, it shouldn't be necessary to link children,
+        * since the earlier indexing of those children will have
+        * stored a thread ID for the missing parent.  However, prior
+        * to ghost messages, these stored thread IDs were NOT
+        * rewritten during thread merging (and there was no
+        * performant way to do so), so if indexed children were
+        * pulled into a different thread ID by a merge, it was
+        * necessary to pull them *back* into the stored thread ID of
+        * the parent.  With ghost messages, we just rewrite the
+        * stored thread IDs during merging, so this workaround isn't
+        * necessary. */
+       status = _notmuch_database_link_message_to_children (notmuch, message,
+                                                            &thread_id);
+       if (status)
+           goto DONE;
+    }
+
+    /* If not part of any existing thread, generate a new thread ID. */
+    if (thread_id == NULL) {
+       thread_id = _notmuch_database_generate_thread_id (notmuch);
+
+       _notmuch_message_add_term (message, "thread", thread_id);
+    }
+
+ DONE:
+    talloc_free (local);
+
+    return status;
+}
+
+notmuch_status_t
+notmuch_database_index_file (notmuch_database_t *notmuch,
+                            const char *filename,
+                            notmuch_indexopts_t *indexopts,
+                            notmuch_message_t **message_ret)
+{
+    notmuch_message_file_t *message_file;
+    notmuch_message_t *message = NULL;
+    notmuch_status_t ret = NOTMUCH_STATUS_SUCCESS, ret2;
+    notmuch_private_status_t private_status;
+    bool is_ghost = false, is_new = false;
+    notmuch_indexopts_t *def_indexopts = NULL;
+
+    const char *date;
+    const char *from, *to, *subject;
+    char *message_id = NULL;
+
+    if (message_ret)
+       *message_ret = NULL;
+
+    ret = _notmuch_database_ensure_writable (notmuch);
+    if (ret)
+       return ret;
+
+    message_file = _notmuch_message_file_open (notmuch, filename);
+    if (message_file == NULL)
+       return NOTMUCH_STATUS_FILE_ERROR;
+
+    /* Adding a message may change many documents.  Do this all
+     * atomically. */
+    ret = notmuch_database_begin_atomic (notmuch);
+    if (ret)
+       goto DONE;
+
+    ret = _notmuch_message_file_get_headers (message_file,
+                                            &from, &subject, &to, &date,
+                                            &message_id);
+    if (ret)
+       goto DONE;
+
+    try {
+       /* Now that we have a message ID, we get a message object,
+        * (which may or may not reference an existing document in the
+        * database). */
+
+       message = _notmuch_message_create_for_message_id (notmuch,
+                                                         message_id,
+                                                         &private_status);
+
+       talloc_free (message_id);
+
+       /* We cannot call notmuch_message_get_flag for a new message */
+       switch (private_status) {
+       case NOTMUCH_PRIVATE_STATUS_NO_DOCUMENT_FOUND:
+           is_ghost = false;
+           is_new = true;
+           break;
+       case NOTMUCH_PRIVATE_STATUS_SUCCESS:
+           is_ghost = notmuch_message_get_flag (message, NOTMUCH_MESSAGE_FLAG_GHOST);
+           is_new = false;
+           break;
+       default:
+           ret = COERCE_STATUS (private_status,
+                                "Unexpected status value from _notmuch_message_create_for_message_id");
+           goto DONE;
+       }
+
+       _notmuch_message_add_filename (message, filename);
+
+       if (is_new || is_ghost) {
+           _notmuch_message_add_term (message, "type", "mail");
+           if (is_ghost)
+               /* Convert ghost message to a regular message */
+               _notmuch_message_remove_term (message, "type", "ghost");
+       }
+
+       ret = _notmuch_database_link_message (notmuch, message,
+                                                 message_file, is_ghost);
+       if (ret)
+           goto DONE;
+
+       if (is_new || is_ghost)
+           _notmuch_message_set_header_values (message, date, from, subject);
+
+       if (!indexopts) {
+           def_indexopts = notmuch_database_get_default_indexopts (notmuch);
+           indexopts = def_indexopts;
+       }
+
+       ret = _notmuch_message_index_file (message, indexopts, message_file);
+       if (ret)
+           goto DONE;
+
+       if (! is_new && !is_ghost)
+           ret = NOTMUCH_STATUS_DUPLICATE_MESSAGE_ID;
+
+       _notmuch_message_sync (message);
+    } catch (const Xapian::Error &error) {
+       _notmuch_database_log (notmuch, "A Xapian exception occurred adding message: %s.\n",
+                error.get_msg().c_str());
+       notmuch->exception_reported = true;
+       ret = NOTMUCH_STATUS_XAPIAN_EXCEPTION;
+       goto DONE;
+    }
+
+  DONE:
+    if (def_indexopts)
+       notmuch_indexopts_destroy (def_indexopts);
+
+    if (message) {
+       if ((ret == NOTMUCH_STATUS_SUCCESS ||
+            ret == NOTMUCH_STATUS_DUPLICATE_MESSAGE_ID) && message_ret)
+           *message_ret = message;
+       else
+           notmuch_message_destroy (message);
+    }
+
+    if (message_file)
+       _notmuch_message_file_close (message_file);
+
+    ret2 = notmuch_database_end_atomic (notmuch);
+    if ((ret == NOTMUCH_STATUS_SUCCESS ||
+        ret == NOTMUCH_STATUS_DUPLICATE_MESSAGE_ID) &&
+       ret2 != NOTMUCH_STATUS_SUCCESS)
+       ret = ret2;
+
+    return ret;
+}
+
+notmuch_status_t
+notmuch_database_add_message (notmuch_database_t *notmuch,
+                             const char *filename,
+                             notmuch_message_t **message_ret)
+{
+    return notmuch_database_index_file (notmuch, filename,
+                                       NULL,
+                                       message_ret);
+
+}
index 2f1f0b5c1bf33237ec6aca7a44d83f84f515e223..27384bd044bc16999b6ddccf686433c983209c76 100644 (file)
@@ -31,6 +31,6 @@ notmuch_built_with (const char *name)
     } else if (STRNCMP_LITERAL (name, "retry_lock") == 0) {
        return HAVE_XAPIAN_DB_RETRY_LOCK;
     } else {
-       return FALSE;
+       return false;
     }
 }
index 0703b9bb8fde96a64c0b1c66030ec156ab5d481f..da71c16e5adcea711bfa7ed6eab264caf7379a78 100644 (file)
@@ -56,7 +56,7 @@ notmuch_database_set_config (notmuch_database_t *notmuch,
        db->set_metadata (CONFIG_PREFIX + key, value);
     } catch (const Xapian::Error &error) {
        status = NOTMUCH_STATUS_XAPIAN_EXCEPTION;
-       notmuch->exception_reported = TRUE;
+       notmuch->exception_reported = true;
        _notmuch_database_log (notmuch, "Error: A Xapian exception occurred setting metadata: %s\n",
                               error.get_msg().c_str());
     }
@@ -74,7 +74,7 @@ _metadata_value (notmuch_database_t *notmuch,
        value = notmuch->xapian_db->get_metadata (CONFIG_PREFIX + key);
     } catch (const Xapian::Error &error) {
        status = NOTMUCH_STATUS_XAPIAN_EXCEPTION;
-       notmuch->exception_reported = TRUE;
+       notmuch->exception_reported = true;
        _notmuch_database_log (notmuch, "Error: A Xapian exception occurred getting metadata: %s\n",
                               error.get_msg().c_str());
     }
@@ -128,7 +128,7 @@ notmuch_database_get_config_list (notmuch_database_t *notmuch,
     } catch (const Xapian::Error &error) {
        _notmuch_database_log (notmuch, "A Xapian exception occurred getting metadata iterator: %s.\n",
                               error.get_msg().c_str());
-       notmuch->exception_reported = TRUE;
+       notmuch->exception_reported = true;
        status = NOTMUCH_STATUS_XAPIAN_EXCEPTION;
     }
 
@@ -145,9 +145,9 @@ notmuch_bool_t
 notmuch_config_list_valid (notmuch_config_list_t *metadata)
 {
     if (metadata->iterator == metadata->notmuch->xapian_db->metadata_keys_end ())
-       return FALSE;
+       return false;
 
-    return TRUE;
+    return true;
 }
 
 const char *
index 727b1d616f248f6aa3c5662985e0f69048a3c700..a499b2594be44d5a12ae671e668f5ab1f6b9c905 100644 (file)
@@ -179,14 +179,14 @@ operator&(notmuch_field_flag_t a, notmuch_field_flag_t b)
                                    Xapian::QueryParser::FLAG_PURE_NOT)
 
 struct _notmuch_database {
-    notmuch_bool_t exception_reported;
+    bool exception_reported;
 
     char *path;
 
     notmuch_database_mode_t mode;
     int atomic_nesting;
-    /* TRUE if changes have been made in this atomic section */
-    notmuch_bool_t atomic_dirty;
+    /* true if changes have been made in this atomic section */
+    bool atomic_dirty;
     Xapian::Database *xapian_db;
 
     /* Bit mask of features used by this database.  This is a
@@ -246,4 +246,10 @@ _notmuch_database_get_terms_with_prefix (void *ctx, Xapian::TermIterator &i,
                                         Xapian::TermIterator &end,
                                         const char *prefix);
 
+void
+_notmuch_database_find_doc_ids (notmuch_database_t *notmuch,
+                               const char *prefix_name,
+                               const char *value,
+                               Xapian::PostingIterator *begin,
+                               Xapian::PostingIterator *end);
 #endif
index 5b13f5417118ce5282b5b148941e65cd22637cde..02444e09e965c4ce0dbc396d5ed48b5abce5ba0a 100644 (file)
@@ -413,6 +413,12 @@ notmuch_status_to_string (notmuch_status_t status)
        return "Operation requires a database upgrade";
     case NOTMUCH_STATUS_PATH_ERROR:
        return "Path supplied is illegal for this function";
+    case NOTMUCH_STATUS_MALFORMED_CRYPTO_PROTOCOL:
+       return "Crypto protocol missing, malformed, or unintelligible";
+    case NOTMUCH_STATUS_FAILED_CRYPTO_CONTEXT_CREATION:
+       return "Crypto engine initialization failure";
+    case NOTMUCH_STATUS_UNKNOWN_CRYPTO_PROTOCOL:
+       return "Unknown crypto protocol";
     default:
     case NOTMUCH_STATUS_LAST_STATUS:
        return "Unknown error status value";
@@ -463,12 +469,12 @@ find_doc_ids_for_term (notmuch_database_t *notmuch,
     *end = notmuch->xapian_db->postlist_end (term);
 }
 
-static void
-find_doc_ids (notmuch_database_t *notmuch,
-             const char *prefix_name,
-             const char *value,
-             Xapian::PostingIterator *begin,
-             Xapian::PostingIterator *end)
+void
+_notmuch_database_find_doc_ids (notmuch_database_t *notmuch,
+                               const char *prefix_name,
+                               const char *value,
+                               Xapian::PostingIterator *begin,
+                               Xapian::PostingIterator *end)
 {
     char *term;
 
@@ -488,7 +494,7 @@ _notmuch_database_find_unique_doc_id (notmuch_database_t *notmuch,
 {
     Xapian::PostingIterator i, end;
 
-    find_doc_ids (notmuch, prefix_name, value, &i, &end);
+    _notmuch_database_find_doc_ids (notmuch, prefix_name, value, &i, &end);
 
     if (i == end) {
        *doc_id = 0;
@@ -562,153 +568,12 @@ notmuch_database_find_message (notmuch_database_t *notmuch,
     } catch (const Xapian::Error &error) {
        _notmuch_database_log (notmuch, "A Xapian exception occurred finding message: %s.\n",
                 error.get_msg().c_str());
-       notmuch->exception_reported = TRUE;
+       notmuch->exception_reported = true;
        *message_ret = NULL;
        return NOTMUCH_STATUS_XAPIAN_EXCEPTION;
     }
 }
 
-/* Advance 'str' past any whitespace or RFC 822 comments. A comment is
- * a (potentially nested) parenthesized sequence with '\' used to
- * escape any character (including parentheses).
- *
- * If the sequence to be skipped continues to the end of the string,
- * then 'str' will be left pointing at the final terminating '\0'
- * character.
- */
-static void
-skip_space_and_comments (const char **str)
-{
-    const char *s;
-
-    s = *str;
-    while (*s && (isspace (*s) || *s == '(')) {
-       while (*s && isspace (*s))
-           s++;
-       if (*s == '(') {
-           int nesting = 1;
-           s++;
-           while (*s && nesting) {
-               if (*s == '(') {
-                   nesting++;
-               } else if (*s == ')') {
-                   nesting--;
-               } else if (*s == '\\') {
-                   if (*(s+1))
-                       s++;
-               }
-               s++;
-           }
-       }
-    }
-
-    *str = s;
-}
-
-/* Parse an RFC 822 message-id, discarding whitespace, any RFC 822
- * comments, and the '<' and '>' delimiters.
- *
- * If not NULL, then *next will be made to point to the first character
- * not parsed, (possibly pointing to the final '\0' terminator.
- *
- * Returns a newly talloc'ed string belonging to 'ctx'.
- *
- * Returns NULL if there is any error parsing the message-id. */
-static char *
-_parse_message_id (void *ctx, const char *message_id, const char **next)
-{
-    const char *s, *end;
-    char *result;
-
-    if (message_id == NULL || *message_id == '\0')
-       return NULL;
-
-    s = message_id;
-
-    skip_space_and_comments (&s);
-
-    /* Skip any unstructured text as well. */
-    while (*s && *s != '<')
-       s++;
-
-    if (*s == '<') {
-       s++;
-    } else {
-       if (next)
-           *next = s;
-       return NULL;
-    }
-
-    skip_space_and_comments (&s);
-
-    end = s;
-    while (*end && *end != '>')
-       end++;
-    if (next) {
-       if (*end)
-           *next = end + 1;
-       else
-           *next = end;
-    }
-
-    if (end > s && *end == '>')
-       end--;
-    if (end <= s)
-       return NULL;
-
-    result = talloc_strndup (ctx, s, end - s + 1);
-
-    /* Finally, collapse any whitespace that is within the message-id
-     * itself. */
-    {
-       char *r;
-       int len;
-
-       for (r = result, len = strlen (r); *r; r++, len--)
-           if (*r == ' ' || *r == '\t')
-               memmove (r, r+1, len);
-    }
-
-    return result;
-}
-
-/* Parse a References header value, putting a (talloc'ed under 'ctx')
- * copy of each referenced message-id into 'hash'.
- *
- * We explicitly avoid including any reference identical to
- * 'message_id' in the result (to avoid mass confusion when a single
- * message references itself cyclically---and yes, mail messages are
- * not infrequent in the wild that do this---don't ask me why).
- *
- * Return the last reference parsed, if it is not equal to message_id.
- */
-static char *
-parse_references (void *ctx,
-                 const char *message_id,
-                 GHashTable *hash,
-                 const char *refs)
-{
-    char *ref, *last_ref = NULL;
-
-    if (refs == NULL || *refs == '\0')
-       return NULL;
-
-    while (*refs) {
-       ref = _parse_message_id (ctx, refs, &refs);
-
-       if (ref && strcmp (ref, message_id)) {
-           g_hash_table_add (hash, ref);
-           last_ref = ref;
-       }
-    }
-
-    /* The return value of this function is used to add a parent
-     * reference to the database.  We should avoid making a message
-     * its own parent, thus the above check.
-     */
-    return talloc_strdup(ctx, last_ref);
-}
-
 notmuch_status_t
 notmuch_database_create (const char *path, notmuch_database_t **database)
 {
@@ -832,7 +697,7 @@ _notmuch_database_new_revision (notmuch_database_t *notmuch)
      * committed revision number until we commit the atomic section.
      */
     if (notmuch->atomic_nesting)
-       notmuch->atomic_dirty = TRUE;
+       notmuch->atomic_dirty = true;
     else
        notmuch->revision = new_revision;
 
@@ -995,12 +860,11 @@ notmuch_database_open_verbose (const char *path,
     }
 
     notmuch = talloc_zero (NULL, notmuch_database_t);
-    notmuch->exception_reported = FALSE;
+    notmuch->exception_reported = false;
     notmuch->status_string = NULL;
     notmuch->path = talloc_strdup (notmuch, path);
 
-    if (notmuch->path[strlen (notmuch->path) - 1] == '/')
-       notmuch->path[strlen (notmuch->path) - 1] = '\0';
+    strip_trailing(notmuch->path, '/');
 
     notmuch->mode = mode;
     notmuch->atomic_nesting = 0;
@@ -1182,7 +1046,7 @@ _notmuch_database_reopen (notmuch_database_t *notmuch)
        if (! notmuch->exception_reported) {
            _notmuch_database_log (notmuch, "Error: A Xapian exception reopening database: %s\n",
                                   error.get_msg ().c_str ());
-           notmuch->exception_reported = TRUE;
+           notmuch->exception_reported = true;
        }
        return NOTMUCH_STATUS_XAPIAN_EXCEPTION;
     }
@@ -1260,7 +1124,7 @@ notmuch_database_compact (const char *path,
     notmuch_status_t ret = NOTMUCH_STATUS_SUCCESS;
     notmuch_database_t *notmuch = NULL;
     struct stat statbuf;
-    notmuch_bool_t keep_backup;
+    bool keep_backup;
     char *message = NULL;
 
     local = talloc_new (NULL);
@@ -1296,10 +1160,10 @@ notmuch_database_compact (const char *path,
            ret = NOTMUCH_STATUS_OUT_OF_MEMORY;
            goto DONE;
        }
-       keep_backup = FALSE;
+       keep_backup = false;
     }
     else {
-       keep_backup = TRUE;
+       keep_backup = true;
     }
 
     if (stat (backup_path, &statbuf) != -1) {
@@ -1455,7 +1319,7 @@ notmuch_database_upgrade (notmuch_database_t *notmuch,
     Xapian::WritableDatabase *db;
     struct sigaction action;
     struct itimerval timerval;
-    notmuch_bool_t timer_is_active = FALSE;
+    bool timer_is_active = false;
     enum _notmuch_features target_features, new_features;
     notmuch_status_t status;
     notmuch_private_status_t private_status;
@@ -1489,7 +1353,7 @@ notmuch_database_upgrade (notmuch_database_t *notmuch,
        timerval.it_value.tv_usec = 0;
        setitimer (ITIMER_REAL, &timerval, NULL);
 
-       timer_is_active = TRUE;
+       timer_is_active = true;
     }
 
     /* Figure out how much total work we need to do. */
@@ -1730,7 +1594,7 @@ notmuch_database_begin_atomic (notmuch_database_t *notmuch)
     } catch (const Xapian::Error &error) {
        _notmuch_database_log (notmuch, "A Xapian exception occurred beginning transaction: %s.\n",
                 error.get_msg().c_str());
-       notmuch->exception_reported = TRUE;
+       notmuch->exception_reported = true;
        return NOTMUCH_STATUS_XAPIAN_EXCEPTION;
     }
 
@@ -1764,13 +1628,13 @@ notmuch_database_end_atomic (notmuch_database_t *notmuch)
     } catch (const Xapian::Error &error) {
        _notmuch_database_log (notmuch, "A Xapian exception occurred committing transaction: %s.\n",
                 error.get_msg().c_str());
-       notmuch->exception_reported = TRUE;
+       notmuch->exception_reported = true;
        return NOTMUCH_STATUS_XAPIAN_EXCEPTION;
     }
 
     if (notmuch->atomic_dirty) {
        ++notmuch->revision;
-       notmuch->atomic_dirty = FALSE;
+       notmuch->atomic_dirty = false;
     }
 
 DONE:
@@ -2013,7 +1877,7 @@ notmuch_database_get_directory (notmuch_database_t *notmuch,
     } catch (const Xapian::Error &error) {
        _notmuch_database_log (notmuch, "A Xapian exception occurred getting directory: %s.\n",
                 error.get_msg().c_str());
-       notmuch->exception_reported = TRUE;
+       notmuch->exception_reported = true;
        status = NOTMUCH_STATUS_XAPIAN_EXCEPTION;
     }
     return status;
@@ -2044,583 +1908,6 @@ _notmuch_database_generate_doc_id (notmuch_database_t *notmuch)
     return notmuch->last_doc_id;
 }
 
-static const char *
-_notmuch_database_generate_thread_id (notmuch_database_t *notmuch)
-{
-    /* 16 bytes (+ terminator) for hexadecimal representation of
-     * a 64-bit integer. */
-    static char thread_id[17];
-    Xapian::WritableDatabase *db;
-
-    db = static_cast <Xapian::WritableDatabase *> (notmuch->xapian_db);
-
-    notmuch->last_thread_id++;
-
-    sprintf (thread_id, "%016" PRIx64, notmuch->last_thread_id);
-
-    db->set_metadata ("last_thread_id", thread_id);
-
-    return thread_id;
-}
-
-static char *
-_get_metadata_thread_id_key (void *ctx, const char *message_id)
-{
-    if (strlen (message_id) > NOTMUCH_MESSAGE_ID_MAX)
-       message_id = _notmuch_message_id_compressed (ctx, message_id);
-
-    return talloc_asprintf (ctx, NOTMUCH_METADATA_THREAD_ID_PREFIX "%s",
-                           message_id);
-}
-
-static notmuch_status_t
-_resolve_message_id_to_thread_id_old (notmuch_database_t *notmuch,
-                                     void *ctx,
-                                     const char *message_id,
-                                     const char **thread_id_ret);
-
-/* Find the thread ID to which the message with 'message_id' belongs.
- *
- * Note: 'thread_id_ret' must not be NULL!
- * On success '*thread_id_ret' is set to a newly talloced string belonging to
- * 'ctx'.
- *
- * Note: If there is no message in the database with the given
- * 'message_id' then a new thread_id will be allocated for this
- * message ID and stored in the database metadata so that the
- * thread ID can be looked up if the message is added to the database
- * later.
- */
-static notmuch_status_t
-_resolve_message_id_to_thread_id (notmuch_database_t *notmuch,
-                                 void *ctx,
-                                 const char *message_id,
-                                 const char **thread_id_ret)
-{
-    notmuch_private_status_t status;
-    notmuch_message_t *message;
-
-    if (! (notmuch->features & NOTMUCH_FEATURE_GHOSTS))
-       return _resolve_message_id_to_thread_id_old (notmuch, ctx, message_id,
-                                                    thread_id_ret);
-
-    /* Look for this message (regular or ghost) */
-    message = _notmuch_message_create_for_message_id (
-       notmuch, message_id, &status);
-    if (status == NOTMUCH_PRIVATE_STATUS_SUCCESS) {
-       /* Message exists */
-       *thread_id_ret = talloc_steal (
-           ctx, notmuch_message_get_thread_id (message));
-    } else if (status == NOTMUCH_PRIVATE_STATUS_NO_DOCUMENT_FOUND) {
-       /* Message did not exist.  Give it a fresh thread ID and
-        * populate this message as a ghost message. */
-       *thread_id_ret = talloc_strdup (
-           ctx, _notmuch_database_generate_thread_id (notmuch));
-       if (! *thread_id_ret) {
-           status = NOTMUCH_PRIVATE_STATUS_OUT_OF_MEMORY;
-       } else {
-           status = _notmuch_message_initialize_ghost (message, *thread_id_ret);
-           if (status == 0)
-               /* Commit the new ghost message */
-               _notmuch_message_sync (message);
-       }
-    } else {
-       /* Create failed. Fall through. */
-    }
-
-    notmuch_message_destroy (message);
-
-    return COERCE_STATUS (status, "Error creating ghost message");
-}
-
-/* Pre-ghost messages _resolve_message_id_to_thread_id */
-static notmuch_status_t
-_resolve_message_id_to_thread_id_old (notmuch_database_t *notmuch,
-                                     void *ctx,
-                                     const char *message_id,
-                                     const char **thread_id_ret)
-{
-    notmuch_status_t status;
-    notmuch_message_t *message;
-    string thread_id_string;
-    char *metadata_key;
-    Xapian::WritableDatabase *db;
-
-    status = notmuch_database_find_message (notmuch, message_id, &message);
-
-    if (status)
-       return status;
-
-    if (message) {
-       *thread_id_ret = talloc_steal (ctx,
-                                      notmuch_message_get_thread_id (message));
-
-       notmuch_message_destroy (message);
-
-       return NOTMUCH_STATUS_SUCCESS;
-    }
-
-    /* Message has not been seen yet.
-     *
-     * We may have seen a reference to it already, in which case, we
-     * can return the thread ID stored in the metadata. Otherwise, we
-     * generate a new thread ID and store it there.
-     */
-    db = static_cast <Xapian::WritableDatabase *> (notmuch->xapian_db);
-    metadata_key = _get_metadata_thread_id_key (ctx, message_id);
-    thread_id_string = notmuch->xapian_db->get_metadata (metadata_key);
-
-    if (thread_id_string.empty()) {
-       *thread_id_ret = talloc_strdup (ctx,
-                                       _notmuch_database_generate_thread_id (notmuch));
-       db->set_metadata (metadata_key, *thread_id_ret);
-    } else {
-       *thread_id_ret = talloc_strdup (ctx, thread_id_string.c_str());
-    }
-
-    talloc_free (metadata_key);
-
-    return NOTMUCH_STATUS_SUCCESS;
-}
-
-static notmuch_status_t
-_merge_threads (notmuch_database_t *notmuch,
-               const char *winner_thread_id,
-               const char *loser_thread_id)
-{
-    Xapian::PostingIterator loser, loser_end;
-    notmuch_message_t *message = NULL;
-    notmuch_private_status_t private_status;
-    notmuch_status_t ret = NOTMUCH_STATUS_SUCCESS;
-
-    find_doc_ids (notmuch, "thread", loser_thread_id, &loser, &loser_end);
-
-    for ( ; loser != loser_end; loser++) {
-       message = _notmuch_message_create (notmuch, notmuch,
-                                          *loser, &private_status);
-       if (message == NULL) {
-           ret = COERCE_STATUS (private_status,
-                                "Cannot find document for doc_id from query");
-           goto DONE;
-       }
-
-       _notmuch_message_remove_term (message, "thread", loser_thread_id);
-       _notmuch_message_add_term (message, "thread", winner_thread_id);
-       _notmuch_message_sync (message);
-
-       notmuch_message_destroy (message);
-       message = NULL;
-    }
-
-  DONE:
-    if (message)
-       notmuch_message_destroy (message);
-
-    return ret;
-}
-
-static void
-_my_talloc_free_for_g_hash (void *ptr)
-{
-    talloc_free (ptr);
-}
-
-static notmuch_status_t
-_notmuch_database_link_message_to_parents (notmuch_database_t *notmuch,
-                                          notmuch_message_t *message,
-                                          notmuch_message_file_t *message_file,
-                                          const char **thread_id)
-{
-    GHashTable *parents = NULL;
-    const char *refs, *in_reply_to, *in_reply_to_message_id;
-    const char *last_ref_message_id, *this_message_id;
-    GList *l, *keys = NULL;
-    notmuch_status_t ret = NOTMUCH_STATUS_SUCCESS;
-
-    parents = g_hash_table_new_full (g_str_hash, g_str_equal,
-                                    _my_talloc_free_for_g_hash, NULL);
-    this_message_id = notmuch_message_get_message_id (message);
-
-    refs = _notmuch_message_file_get_header (message_file, "references");
-    last_ref_message_id = parse_references (message,
-                                           this_message_id,
-                                           parents, refs);
-
-    in_reply_to = _notmuch_message_file_get_header (message_file, "in-reply-to");
-    in_reply_to_message_id = parse_references (message,
-                                              this_message_id,
-                                              parents, in_reply_to);
-
-    /* For the parent of this message, use the last message ID of the
-     * References header, if available.  If not, fall back to the
-     * first message ID in the In-Reply-To header. */
-    if (last_ref_message_id) {
-       _notmuch_message_add_term (message, "replyto",
-                                  last_ref_message_id);
-    } else if (in_reply_to_message_id) {
-       _notmuch_message_add_term (message, "replyto",
-                            in_reply_to_message_id);
-    }
-
-    keys = g_hash_table_get_keys (parents);
-    for (l = keys; l; l = l->next) {
-       char *parent_message_id;
-       const char *parent_thread_id = NULL;
-
-       parent_message_id = (char *) l->data;
-
-       _notmuch_message_add_term (message, "reference",
-                                  parent_message_id);
-
-       ret = _resolve_message_id_to_thread_id (notmuch,
-                                               message,
-                                               parent_message_id,
-                                               &parent_thread_id);
-       if (ret)
-           goto DONE;
-
-       if (*thread_id == NULL) {
-           *thread_id = talloc_strdup (message, parent_thread_id);
-           _notmuch_message_add_term (message, "thread", *thread_id);
-       } else if (strcmp (*thread_id, parent_thread_id)) {
-           ret = _merge_threads (notmuch, *thread_id, parent_thread_id);
-           if (ret)
-               goto DONE;
-       }
-    }
-
-  DONE:
-    if (keys)
-       g_list_free (keys);
-    if (parents)
-       g_hash_table_unref (parents);
-
-    return ret;
-}
-
-static notmuch_status_t
-_notmuch_database_link_message_to_children (notmuch_database_t *notmuch,
-                                           notmuch_message_t *message,
-                                           const char **thread_id)
-{
-    const char *message_id = notmuch_message_get_message_id (message);
-    Xapian::PostingIterator child, children_end;
-    notmuch_message_t *child_message = NULL;
-    const char *child_thread_id;
-    notmuch_status_t ret = NOTMUCH_STATUS_SUCCESS;
-    notmuch_private_status_t private_status;
-
-    find_doc_ids (notmuch, "reference", message_id, &child, &children_end);
-
-    for ( ; child != children_end; child++) {
-
-       child_message = _notmuch_message_create (message, notmuch,
-                                                *child, &private_status);
-       if (child_message == NULL) {
-           ret = COERCE_STATUS (private_status,
-                                "Cannot find document for doc_id from query");
-           goto DONE;
-       }
-
-       child_thread_id = notmuch_message_get_thread_id (child_message);
-       if (*thread_id == NULL) {
-           *thread_id = talloc_strdup (message, child_thread_id);
-           _notmuch_message_add_term (message, "thread", *thread_id);
-       } else if (strcmp (*thread_id, child_thread_id)) {
-           _notmuch_message_remove_term (child_message, "reference",
-                                         message_id);
-           _notmuch_message_sync (child_message);
-           ret = _merge_threads (notmuch, *thread_id, child_thread_id);
-           if (ret)
-               goto DONE;
-       }
-
-       notmuch_message_destroy (child_message);
-       child_message = NULL;
-    }
-
-  DONE:
-    if (child_message)
-       notmuch_message_destroy (child_message);
-
-    return ret;
-}
-
-/* Fetch and clear the stored thread_id for message, or NULL if none. */
-static char *
-_consume_metadata_thread_id (void *ctx, notmuch_database_t *notmuch,
-                            notmuch_message_t *message)
-{
-    const char *message_id;
-    string stored_id;
-    char *metadata_key;
-
-    message_id = notmuch_message_get_message_id (message);
-    metadata_key = _get_metadata_thread_id_key (ctx, message_id);
-
-    /* Check if we have already seen related messages to this one.
-     * If we have then use the thread_id that we stored at that time.
-     */
-    stored_id = notmuch->xapian_db->get_metadata (metadata_key);
-    if (stored_id.empty ()) {
-       return NULL;
-    } else {
-       Xapian::WritableDatabase *db;
-
-       db = static_cast <Xapian::WritableDatabase *> (notmuch->xapian_db);
-
-       /* Clear the metadata for this message ID. We don't need it
-        * anymore. */
-       db->set_metadata (metadata_key, "");
-
-       return talloc_strdup (ctx, stored_id.c_str ());
-    }
-}
-
-/* Given a blank or ghost 'message' and its corresponding
- * 'message_file' link it to existing threads in the database.
- *
- * First, if is_ghost, this retrieves the thread ID already stored in
- * the message (which will be the case if a message was previously
- * added that referenced this one).  If the message is blank
- * (!is_ghost), it doesn't have a thread ID yet (we'll generate one
- * later in this function).  If the database does not support ghost
- * messages, this checks for a thread ID stored in database metadata
- * for this message ID.
- *
- * Second, we look at 'message_file' and its link-relevant headers
- * (References and In-Reply-To) for message IDs.
- *
- * Finally, we look in the database for existing message that
- * reference 'message'.
- *
- * In all cases, we assign to the current message the first thread ID
- * found. We will also merge any existing, distinct threads where this
- * message belongs to both, (which is not uncommon when messages are
- * processed out of order).
- *
- * Finally, if no thread ID has been found through referenced messages, we
- * call _notmuch_message_generate_thread_id to generate a new thread
- * ID. This should only happen for new, top-level messages, (no
- * References or In-Reply-To header in this message, and no previously
- * added message refers to this message).
- */
-static notmuch_status_t
-_notmuch_database_link_message (notmuch_database_t *notmuch,
-                               notmuch_message_t *message,
-                               notmuch_message_file_t *message_file,
-                               notmuch_bool_t is_ghost)
-{
-    void *local = talloc_new (NULL);
-    notmuch_status_t status;
-    const char *thread_id = NULL;
-
-    /* Check if the message already had a thread ID */
-    if (notmuch->features & NOTMUCH_FEATURE_GHOSTS) {
-       if (is_ghost)
-           thread_id = notmuch_message_get_thread_id (message);
-    } else {
-       thread_id = _consume_metadata_thread_id (local, notmuch, message);
-       if (thread_id)
-           _notmuch_message_add_term (message, "thread", thread_id);
-    }
-
-    status = _notmuch_database_link_message_to_parents (notmuch, message,
-                                                       message_file,
-                                                       &thread_id);
-    if (status)
-       goto DONE;
-
-    if (! (notmuch->features & NOTMUCH_FEATURE_GHOSTS)) {
-       /* In general, it shouldn't be necessary to link children,
-        * since the earlier indexing of those children will have
-        * stored a thread ID for the missing parent.  However, prior
-        * to ghost messages, these stored thread IDs were NOT
-        * rewritten during thread merging (and there was no
-        * performant way to do so), so if indexed children were
-        * pulled into a different thread ID by a merge, it was
-        * necessary to pull them *back* into the stored thread ID of
-        * the parent.  With ghost messages, we just rewrite the
-        * stored thread IDs during merging, so this workaround isn't
-        * necessary. */
-       status = _notmuch_database_link_message_to_children (notmuch, message,
-                                                            &thread_id);
-       if (status)
-           goto DONE;
-    }
-
-    /* If not part of any existing thread, generate a new thread ID. */
-    if (thread_id == NULL) {
-       thread_id = _notmuch_database_generate_thread_id (notmuch);
-
-       _notmuch_message_add_term (message, "thread", thread_id);
-    }
-
- DONE:
-    talloc_free (local);
-
-    return status;
-}
-
-notmuch_status_t
-notmuch_database_add_message (notmuch_database_t *notmuch,
-                             const char *filename,
-                             notmuch_message_t **message_ret)
-{
-    notmuch_message_file_t *message_file;
-    notmuch_message_t *message = NULL;
-    notmuch_status_t ret = NOTMUCH_STATUS_SUCCESS, ret2;
-    notmuch_private_status_t private_status;
-    notmuch_bool_t is_ghost = false;
-
-    const char *date, *header;
-    const char *from, *to, *subject;
-    char *message_id = NULL;
-
-    if (message_ret)
-       *message_ret = NULL;
-
-    ret = _notmuch_database_ensure_writable (notmuch);
-    if (ret)
-       return ret;
-
-    message_file = _notmuch_message_file_open (notmuch, filename);
-    if (message_file == NULL)
-       return NOTMUCH_STATUS_FILE_ERROR;
-
-    /* Adding a message may change many documents.  Do this all
-     * atomically. */
-    ret = notmuch_database_begin_atomic (notmuch);
-    if (ret)
-       goto DONE;
-
-    /* Parse message up front to get better error status. */
-    ret = _notmuch_message_file_parse (message_file);
-    if (ret)
-       goto DONE;
-
-    /* Before we do any real work, (especially before doing a
-     * potential SHA-1 computation on the entire file's contents),
-     * let's make sure that what we're looking at looks like an
-     * actual email message.
-     */
-    from = _notmuch_message_file_get_header (message_file, "from");
-    subject = _notmuch_message_file_get_header (message_file, "subject");
-    to = _notmuch_message_file_get_header (message_file, "to");
-
-    if ((from == NULL || *from == '\0') &&
-       (subject == NULL || *subject == '\0') &&
-       (to == NULL || *to == '\0')) {
-       ret = NOTMUCH_STATUS_FILE_NOT_EMAIL;
-       goto DONE;
-    }
-
-    /* Now that we're sure it's mail, the first order of business
-     * is to find a message ID (or else create one ourselves).
-     */
-    header = _notmuch_message_file_get_header (message_file, "message-id");
-    if (header && *header != '\0') {
-       message_id = _parse_message_id (message_file, header, NULL);
-
-       /* So the header value isn't RFC-compliant, but it's
-        * better than no message-id at all.
-        */
-       if (message_id == NULL)
-           message_id = talloc_strdup (message_file, header);
-    }
-
-    if (message_id == NULL ) {
-       /* No message-id at all, let's generate one by taking a
-        * hash over the file's contents.
-        */
-       char *sha1 = _notmuch_sha1_of_file (filename);
-
-       /* If that failed too, something is really wrong. Give up. */
-       if (sha1 == NULL) {
-           ret = NOTMUCH_STATUS_FILE_ERROR;
-           goto DONE;
-       }
-
-       message_id = talloc_asprintf (message_file, "notmuch-sha1-%s", sha1);
-       free (sha1);
-    }
-
-    try {
-       /* Now that we have a message ID, we get a message object,
-        * (which may or may not reference an existing document in the
-        * database). */
-
-       message = _notmuch_message_create_for_message_id (notmuch,
-                                                         message_id,
-                                                         &private_status);
-
-       talloc_free (message_id);
-
-       if (message == NULL) {
-           ret = COERCE_STATUS (private_status,
-                                "Unexpected status value from _notmuch_message_create_for_message_id");
-           goto DONE;
-       }
-
-       _notmuch_message_add_filename (message, filename);
-
-       /* Is this a newly created message object or a ghost
-        * message?  We have to be slightly careful: if this is a
-        * blank message, it's not safe to call
-        * notmuch_message_get_flag yet. */
-       if (private_status == NOTMUCH_PRIVATE_STATUS_NO_DOCUMENT_FOUND ||
-           (is_ghost = notmuch_message_get_flag (
-               message, NOTMUCH_MESSAGE_FLAG_GHOST))) {
-           _notmuch_message_add_term (message, "type", "mail");
-           if (is_ghost)
-               /* Convert ghost message to a regular message */
-               _notmuch_message_remove_term (message, "type", "ghost");
-
-           ret = _notmuch_database_link_message (notmuch, message,
-                                                 message_file, is_ghost);
-           if (ret)
-               goto DONE;
-
-           date = _notmuch_message_file_get_header (message_file, "date");
-           _notmuch_message_set_header_values (message, date, from, subject);
-
-           ret = _notmuch_message_index_file (message, message_file);
-           if (ret)
-               goto DONE;
-       } else {
-           ret = NOTMUCH_STATUS_DUPLICATE_MESSAGE_ID;
-       }
-
-       _notmuch_message_sync (message);
-    } catch (const Xapian::Error &error) {
-       _notmuch_database_log (notmuch, "A Xapian exception occurred adding message: %s.\n",
-                error.get_msg().c_str());
-       notmuch->exception_reported = TRUE;
-       ret = NOTMUCH_STATUS_XAPIAN_EXCEPTION;
-       goto DONE;
-    }
-
-  DONE:
-    if (message) {
-       if ((ret == NOTMUCH_STATUS_SUCCESS ||
-            ret == NOTMUCH_STATUS_DUPLICATE_MESSAGE_ID) && message_ret)
-           *message_ret = message;
-       else
-           notmuch_message_destroy (message);
-    }
-
-    if (message_file)
-       _notmuch_message_file_close (message_file);
-
-    ret2 = notmuch_database_end_atomic (notmuch);
-    if ((ret == NOTMUCH_STATUS_SUCCESS ||
-        ret == NOTMUCH_STATUS_DUPLICATE_MESSAGE_ID) &&
-       ret2 != NOTMUCH_STATUS_SUCCESS)
-       ret = ret2;
-
-    return ret;
-}
-
 notmuch_status_t
 notmuch_database_remove_message (notmuch_database_t *notmuch,
                                 const char *filename)
@@ -2687,7 +1974,7 @@ notmuch_database_find_message_by_filename (notmuch_database_t *notmuch,
     } catch (const Xapian::Error &error) {
        _notmuch_database_log (notmuch, "Error: A Xapian exception occurred finding message by filename: %s\n",
                 error.get_msg().c_str());
-       notmuch->exception_reported = TRUE;
+       notmuch->exception_reported = true;
        status = NOTMUCH_STATUS_XAPIAN_EXCEPTION;
     }
 
@@ -2740,7 +2027,7 @@ notmuch_database_get_all_tags (notmuch_database_t *db)
     } catch (const Xapian::Error &error) {
        _notmuch_database_log (db, "A Xapian exception occurred getting tags: %s.\n",
                 error.get_msg().c_str());
-       db->exception_reported = TRUE;
+       db->exception_reported = true;
        return NULL;
     }
 }
index 5de3319c47d1457f92da38f00717507e67684d9c..4fcb017712f6c9c505a35326dc807f84b3e09d6d 100644 (file)
@@ -103,7 +103,7 @@ _notmuch_directory_create (notmuch_database_t *notmuch,
     notmuch_directory_t *directory;
     notmuch_private_status_t private_status;
     const char *db_path;
-    notmuch_bool_t create = (flags & NOTMUCH_FIND_CREATE);
+    bool create = (flags & NOTMUCH_FIND_CREATE);
 
     if (! (notmuch->features & NOTMUCH_FEATURE_DIRECTORY_DOCS)) {
        *status_ret = NOTMUCH_STATUS_UPGRADE_REQUIRED;
@@ -189,7 +189,7 @@ _notmuch_directory_create (notmuch_database_t *notmuch,
        _notmuch_database_log (notmuch,
                 "A Xapian exception occurred creating a directory: %s.\n",
                 error.get_msg().c_str());
-       notmuch->exception_reported = TRUE;
+       notmuch->exception_reported = true;
        notmuch_directory_destroy (directory);
        directory = NULL;
        *status_ret = NOTMUCH_STATUS_XAPIAN_EXCEPTION;
@@ -234,7 +234,7 @@ notmuch_directory_set_mtime (notmuch_directory_t *directory,
        _notmuch_database_log (notmuch,
                 "A Xapian exception occurred setting directory mtime: %s.\n",
                 error.get_msg().c_str());
-       notmuch->exception_reported = TRUE;
+       notmuch->exception_reported = true;
        return NOTMUCH_STATUS_XAPIAN_EXCEPTION;
     }
 
@@ -301,7 +301,7 @@ notmuch_directory_delete (notmuch_directory_t *directory)
        _notmuch_database_log (directory->notmuch,
                               "A Xapian exception occurred deleting directory entry: %s.\n",
                               error.get_msg().c_str());
-       directory->notmuch->exception_reported = TRUE;
+       directory->notmuch->exception_reported = true;
        status = NOTMUCH_STATUS_XAPIAN_EXCEPTION;
     }
     notmuch_directory_destroy (directory);
index 63e737dd4edc2c93d254249f0d5dd807b44b008a..37d631d6ff311ba34c8a60124e30c564622e6072 100644 (file)
@@ -46,7 +46,7 @@ notmuch_bool_t
 notmuch_filenames_valid (notmuch_filenames_t *filenames)
 {
     if (filenames == NULL)
-       return FALSE;
+       return false;
 
     return (filenames->iterator != NULL);
 }
index 10420d84426ca165659cba5118def218d6020cc3..6e684f5fcda76dcc5fe81895e25194d7c1186e45 100644 (file)
@@ -184,7 +184,7 @@ filter_filter (GMimeFilter *gmime_filter, char *inbuf, size_t inlen, size_t pres
 
     int next;
 
-    g_mime_filter_set_size (gmime_filter, inlen, FALSE);
+    g_mime_filter_set_size (gmime_filter, inlen, false);
     outptr = gmime_filter->outbuf;
 
     next = filter->state;
@@ -261,7 +261,7 @@ notmuch_filter_discard_non_term_new (GMimeContentType *content_type)
        type = g_type_register_static (GMIME_TYPE_FILTER, "NotmuchFilterDiscardNonTerm", &info, (GTypeFlags) 0);
     }
 
-    filter = (NotmuchFilterDiscardNonTerm *) g_object_newv (type, 0, NULL);
+    filter = (NotmuchFilterDiscardNonTerm *) g_object_new (type, NULL);
     filter->content_type = content_type;
     filter->state = 0;
     if (g_mime_content_type_is_type (content_type, "text", "html")) {
@@ -351,9 +351,28 @@ _index_address_list (notmuch_message_t *message,
     }
 }
 
+static void
+_index_content_type (notmuch_message_t *message, GMimeObject *part)
+{
+    GMimeContentType *content_type = g_mime_object_get_content_type (part);
+    if (content_type) {
+       char *mime_string = g_mime_content_type_to_string (content_type);
+       if (mime_string) {
+           _notmuch_message_gen_terms (message, "mimetype", mime_string);
+           g_free (mime_string);
+       }
+    }
+}
+
+static void
+_index_encrypted_mime_part (notmuch_message_t *message, notmuch_indexopts_t *indexopts,
+                           GMimeContentType *content_type,
+                           GMimeMultipartEncrypted *part);
+
 /* Callback to generate terms for each mime part of a message. */
 static void
 _index_mime_part (notmuch_message_t *message,
+                 notmuch_indexopts_t *indexopts,
                  GMimeObject *part)
 {
     GMimeStream *stream, *filter;
@@ -361,6 +380,7 @@ _index_mime_part (notmuch_message_t *message,
     GMimeDataWrapper *wrapper;
     GByteArray *byte_array;
     GMimeContentDisposition *disposition;
+    GMimeContentType *content_type;
     char *body;
     const char *charset;
 
@@ -370,15 +390,8 @@ _index_mime_part (notmuch_message_t *message,
        return;
     }
 
-    GMimeContentType *content_type = g_mime_object_get_content_type(part);
-    if (content_type) {
-       char *mime_string = g_mime_content_type_to_string(content_type);
-       if (mime_string)
-       {
-           _notmuch_message_gen_terms (message, "mimetype", mime_string);
-           g_free(mime_string);
-       }
-    }
+    _index_content_type (message, part);
+    content_type = g_mime_object_get_content_type (part);
 
     if (GMIME_IS_MULTIPART (part)) {
        GMimeMultipart *multipart = GMIME_MULTIPART (part);
@@ -392,18 +405,32 @@ _index_mime_part (notmuch_message_t *message,
 
        for (i = 0; i < g_mime_multipart_get_count (multipart); i++) {
            if (GMIME_IS_MULTIPART_SIGNED (multipart)) {
-               /* Don't index the signature. */
-               if (i == 1)
+               /* Don't index the signature, but index its content type. */
+               if (i == GMIME_MULTIPART_SIGNED_SIGNATURE) {
+                   _index_content_type (message,
+                                        g_mime_multipart_get_part (multipart, i));
                    continue;
-               if (i > 1)
+               } else if (i != GMIME_MULTIPART_SIGNED_CONTENT) {
                    _notmuch_database_log (_notmuch_message_database (message),
-                                         "Warning: Unexpected extra parts of multipart/signed. Indexing anyway.\n");
+                                          "Warning: Unexpected extra parts of multipart/signed. Indexing anyway.\n");
+               }
            }
            if (GMIME_IS_MULTIPART_ENCRYPTED (multipart)) {
-               /* Don't index encrypted parts. */
+               _index_content_type (message,
+                                    g_mime_multipart_get_part (multipart, i));
+               if (i == GMIME_MULTIPART_ENCRYPTED_CONTENT) {
+                   _index_encrypted_mime_part(message, indexopts,
+                                              content_type,
+                                              GMIME_MULTIPART_ENCRYPTED (part));
+               } else {
+                   if (i != GMIME_MULTIPART_ENCRYPTED_VERSION) {
+                       _notmuch_database_log (_notmuch_message_database (message),
+                                              "Warning: Unexpected extra parts of multipart/encrypted.\n");
+                   }
+               }
                continue;
            }
-           _index_mime_part (message,
+           _index_mime_part (message, indexopts,
                              g_mime_multipart_get_part (multipart, i));
        }
        return;
@@ -414,7 +441,7 @@ _index_mime_part (notmuch_message_t *message,
 
        mime_message = g_mime_message_part_get_message (GMIME_MESSAGE_PART (part));
 
-       _index_mime_part (message, g_mime_message_get_mime_part (mime_message));
+       _index_mime_part (message, indexopts, g_mime_message_get_mime_part (mime_message));
 
        return;
     }
@@ -444,9 +471,10 @@ _index_mime_part (notmuch_message_t *message,
     byte_array = g_byte_array_new ();
 
     stream = g_mime_stream_mem_new_with_byte_array (byte_array);
-    g_mime_stream_mem_set_owner (GMIME_STREAM_MEM (stream), FALSE);
+    g_mime_stream_mem_set_owner (GMIME_STREAM_MEM (stream), false);
 
     filter = g_mime_stream_filter_new (stream);
+
     discard_non_term_filter = notmuch_filter_discard_non_term_new (content_type);
 
     g_mime_stream_filter_add (GMIME_STREAM_FILTER (filter),
@@ -475,7 +503,7 @@ _index_mime_part (notmuch_message_t *message,
     g_object_unref (discard_non_term_filter);
 
     g_byte_array_append (byte_array, (guint8 *) "\0", 1);
-    body = (char *) g_byte_array_free (byte_array, FALSE);
+    body = (char *) g_byte_array_free (byte_array, false);
 
     if (body) {
        _notmuch_message_gen_terms (message, NULL, body);
@@ -484,8 +512,71 @@ _index_mime_part (notmuch_message_t *message,
     }
 }
 
+/* descend (if desired) into the cleartext part of an encrypted MIME
+ * part while indexing. */
+static void
+_index_encrypted_mime_part (notmuch_message_t *message,
+                           notmuch_indexopts_t *indexopts,
+                           g_mime_3_unused(GMimeContentType *content_type),
+                           GMimeMultipartEncrypted *encrypted_data)
+{
+    notmuch_status_t status;
+    GError *err = NULL;
+    notmuch_database_t * notmuch = NULL;
+    GMimeObject *clear = NULL;
+
+    if (!indexopts || !notmuch_indexopts_get_try_decrypt (indexopts))
+       return;
+
+    notmuch = _notmuch_message_database (message);
+
+#if (GMIME_MAJOR_VERSION < 3)
+    {
+       GMimeCryptoContext* crypto_ctx = NULL;
+       const char *protocol = NULL;
+       protocol = g_mime_content_type_get_parameter (content_type, "protocol");
+       status = _notmuch_crypto_get_gmime_ctx_for_protocol (&(indexopts->crypto),
+                                                        protocol, &crypto_ctx);
+       if (status) {
+           _notmuch_database_log (notmuch, "Warning: setup failed for decrypting "
+                                  "during indexing. (%d)\n", status);
+           status = notmuch_message_add_property (message, "index.decryption", "failure");
+           if (status)
+               _notmuch_database_log_append (notmuch, "failed to add index.decryption "
+                                             "property (%d)\n", status);
+           return;
+       }
+       clear = g_mime_multipart_encrypted_decrypt(encrypted_data, crypto_ctx,
+                                                  NULL, &err);
+    }
+#else
+    clear = g_mime_multipart_encrypted_decrypt(encrypted_data, GMIME_DECRYPT_NONE, NULL,
+                                              NULL, &err);
+#endif
+    if (err) {
+       _notmuch_database_log (notmuch, "Failed to decrypt during indexing. (%d:%d) [%s]\n",
+                              err->domain, err->code, err->message);
+       g_error_free(err);
+       /* Indicate that we failed to decrypt during indexing */
+       status = notmuch_message_add_property (message, "index.decryption", "failure");
+       if (status)
+           _notmuch_database_log_append (notmuch, "failed to add index.decryption "
+                                         "property (%d)\n", status);
+       return;
+    }
+    _index_mime_part (message, indexopts, clear);
+    g_object_unref (clear);
+
+    status = notmuch_message_add_property (message, "index.decryption", "success");
+    if (status)
+       _notmuch_database_log (notmuch, "failed to add index.decryption "
+                              "property (%d)\n", status);
+
+}
+
 notmuch_status_t
 _notmuch_message_index_file (notmuch_message_t *message,
+                            notmuch_indexopts_t *indexopts,
                             notmuch_message_file_t *message_file)
 {
     GMimeMessage *mime_message;
@@ -513,7 +604,7 @@ _notmuch_message_index_file (notmuch_message_t *message,
     subject = g_mime_message_get_subject (mime_message);
     _notmuch_message_gen_terms (message, "subject", subject);
 
-    _index_mime_part (message, g_mime_message_get_mime_part (mime_message));
+    _index_mime_part (message, indexopts, g_mime_message_get_mime_part (mime_message));
 
     return NOTMUCH_STATUS_SUCCESS;
 }
diff --git a/lib/indexopts.c b/lib/indexopts.c
new file mode 100644 (file)
index 0000000..15c31d2
--- /dev/null
@@ -0,0 +1,67 @@
+/* indexopts.c - options for indexing messages (currently a stub)
+ *
+ * Copyright © 2017 Daniel Kahn Gillmor
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see https://www.gnu.org/licenses/ .
+ *
+ * Author: Daniel Kahn Gillmor <dkg@fifthhorseman.net>
+ */
+
+#include "notmuch-private.h"
+
+notmuch_indexopts_t *
+notmuch_database_get_default_indexopts (notmuch_database_t *db)
+{
+    notmuch_indexopts_t *ret = talloc_zero (db, notmuch_indexopts_t);
+    if (!ret)
+       return ret;
+
+    char * try_decrypt;
+    notmuch_status_t err = notmuch_database_get_config (db, "index.try_decrypt", &try_decrypt);
+    if (err)
+       return ret;
+
+    if (try_decrypt &&
+       ((!(strcasecmp(try_decrypt, "true"))) ||
+        (!(strcasecmp(try_decrypt, "yes"))) ||
+        (!(strcasecmp(try_decrypt, "1")))))
+       notmuch_indexopts_set_try_decrypt (ret, true);
+
+    free (try_decrypt);
+    return ret;
+}
+
+notmuch_status_t
+notmuch_indexopts_set_try_decrypt (notmuch_indexopts_t *indexopts,
+                                  notmuch_bool_t try_decrypt)
+{
+    if (!indexopts)
+       return NOTMUCH_STATUS_NULL_POINTER;
+    indexopts->crypto.decrypt = try_decrypt;
+    return NOTMUCH_STATUS_SUCCESS;
+}
+
+notmuch_bool_t
+notmuch_indexopts_get_try_decrypt (const notmuch_indexopts_t *indexopts)
+{
+    if (!indexopts)
+       return false;
+    return indexopts->crypto.decrypt;
+}
+
+void
+notmuch_indexopts_destroy (notmuch_indexopts_t *indexopts)
+{
+    talloc_free (indexopts);
+}
index d7acf0d5b4e2739eac56756c1ca35f30eeacf424..8f0dbbda872570e184e2e4f8dc1b8800f04fc4e5 100644 (file)
@@ -92,22 +92,28 @@ _notmuch_message_file_open (notmuch_database_t *notmuch,
     return _notmuch_message_file_open_ctx (notmuch, NULL, filename);
 }
 
+const char *
+_notmuch_message_file_get_filename (notmuch_message_file_t *message_file)
+{
+    return message_file->filename;
+}
+
 void
 _notmuch_message_file_close (notmuch_message_file_t *message)
 {
     talloc_free (message);
 }
 
-static notmuch_bool_t
+static bool
 _is_mbox (FILE *file)
 {
     char from_buf[5];
-    notmuch_bool_t ret = FALSE;
+    bool ret = false;
 
     /* Is this mbox? */
     if (fread (from_buf, sizeof (from_buf), 1, file) == 1 &&
        strncmp (from_buf, "From ", 5) == 0)
-       ret = TRUE;
+       ret = true;
 
     rewind (file);
 
@@ -121,7 +127,7 @@ _notmuch_message_file_parse (notmuch_message_file_t *message)
     GMimeParser *parser;
     notmuch_status_t status = NOTMUCH_STATUS_SUCCESS;
     static int initialized = 0;
-    notmuch_bool_t is_mbox;
+    bool is_mbox;
 
     if (message->message)
        return NOTMUCH_STATUS_SUCCESS;
@@ -141,7 +147,7 @@ _notmuch_message_file_parse (notmuch_message_file_t *message)
     stream = g_mime_stream_file_new (message->file);
 
     /* We'll own and fclose the FILE* ourselves. */
-    g_mime_stream_file_set_owner (GMIME_STREAM_FILE (stream), FALSE);
+    g_mime_stream_file_set_owner (GMIME_STREAM_FILE (stream), false);
 
     parser = g_mime_parser_new_with_stream (stream);
     g_mime_parser_set_scan_from (parser, is_mbox);
@@ -345,3 +351,83 @@ _notmuch_message_file_get_header (notmuch_message_file_t *message,
 
     return decoded;
 }
+
+notmuch_status_t
+_notmuch_message_file_get_headers (notmuch_message_file_t *message_file,
+                                  const char **from_out,
+                                  const char **subject_out,
+                                  const char **to_out,
+                                  const char **date_out,
+                                  char **message_id_out)
+{
+    notmuch_status_t ret;
+    const char *header;
+    const char *from, *to, *subject, *date;
+    char *message_id = NULL;
+
+    /* Parse message up front to get better error status. */
+    ret = _notmuch_message_file_parse (message_file);
+    if (ret)
+       goto DONE;
+
+    /* Before we do any real work, (especially before doing a
+     * potential SHA-1 computation on the entire file's contents),
+     * let's make sure that what we're looking at looks like an
+     * actual email message.
+     */
+    from = _notmuch_message_file_get_header (message_file, "from");
+    subject = _notmuch_message_file_get_header (message_file, "subject");
+    to = _notmuch_message_file_get_header (message_file, "to");
+    date = _notmuch_message_file_get_header (message_file, "date");
+
+    if ((from == NULL || *from == '\0') &&
+       (subject == NULL || *subject == '\0') &&
+       (to == NULL || *to == '\0')) {
+       ret = NOTMUCH_STATUS_FILE_NOT_EMAIL;
+       goto DONE;
+    }
+
+    /* Now that we're sure it's mail, the first order of business
+     * is to find a message ID (or else create one ourselves).
+     */
+    header = _notmuch_message_file_get_header (message_file, "message-id");
+    if (header && *header != '\0') {
+       message_id = _notmuch_message_id_parse (message_file, header, NULL);
+
+       /* So the header value isn't RFC-compliant, but it's
+        * better than no message-id at all.
+        */
+       if (message_id == NULL)
+           message_id = talloc_strdup (message_file, header);
+    }
+
+    if (message_id == NULL ) {
+       /* No message-id at all, let's generate one by taking a
+        * hash over the file's contents.
+        */
+       char *sha1 = _notmuch_sha1_of_file (_notmuch_message_file_get_filename (message_file));
+
+       /* If that failed too, something is really wrong. Give up. */
+       if (sha1 == NULL) {
+           ret = NOTMUCH_STATUS_FILE_ERROR;
+           goto DONE;
+       }
+
+       message_id = talloc_asprintf (message_file, "notmuch-sha1-%s", sha1);
+       free (sha1);
+    }
+ DONE:
+    if (ret == NOTMUCH_STATUS_SUCCESS) {
+       if (from_out)
+           *from_out = from;
+       if (subject_out)
+           *subject_out = subject;
+       if (to_out)
+           *to_out = to;
+       if (date_out)
+           *date_out = date;
+       if (message_id_out)
+           *message_id_out = message_id;
+    }
+    return ret;
+}
diff --git a/lib/message-id.c b/lib/message-id.c
new file mode 100644 (file)
index 0000000..d7541d5
--- /dev/null
@@ -0,0 +1,96 @@
+#include "notmuch-private.h"
+
+/* Advance 'str' past any whitespace or RFC 822 comments. A comment is
+ * a (potentially nested) parenthesized sequence with '\' used to
+ * escape any character (including parentheses).
+ *
+ * If the sequence to be skipped continues to the end of the string,
+ * then 'str' will be left pointing at the final terminating '\0'
+ * character.
+ */
+static void
+skip_space_and_comments (const char **str)
+{
+    const char *s;
+
+    s = *str;
+    while (*s && (isspace (*s) || *s == '(')) {
+       while (*s && isspace (*s))
+           s++;
+       if (*s == '(') {
+           int nesting = 1;
+           s++;
+           while (*s && nesting) {
+               if (*s == '(') {
+                   nesting++;
+               } else if (*s == ')') {
+                   nesting--;
+               } else if (*s == '\\') {
+                   if (*(s+1))
+                       s++;
+               }
+               s++;
+           }
+       }
+    }
+
+    *str = s;
+}
+
+char *
+_notmuch_message_id_parse (void *ctx, const char *message_id, const char **next)
+{
+    const char *s, *end;
+    char *result;
+
+    if (message_id == NULL || *message_id == '\0')
+       return NULL;
+
+    s = message_id;
+
+    skip_space_and_comments (&s);
+
+    /* Skip any unstructured text as well. */
+    while (*s && *s != '<')
+       s++;
+
+    if (*s == '<') {
+       s++;
+    } else {
+       if (next)
+           *next = s;
+       return NULL;
+    }
+
+    skip_space_and_comments (&s);
+
+    end = s;
+    while (*end && *end != '>')
+       end++;
+    if (next) {
+       if (*end)
+           *next = end + 1;
+       else
+           *next = end;
+    }
+
+    if (end > s && *end == '>')
+       end--;
+    if (end <= s)
+       return NULL;
+
+    result = talloc_strndup (ctx, s, end - s + 1);
+
+    /* Finally, collapse any whitespace that is within the message-id
+     * itself. */
+    {
+       char *r;
+       int len;
+
+       for (r = result, len = strlen (r); *r; r++, len--)
+           if (*r == ' ' || *r == '\t')
+               memmove (r, r+1, len);
+    }
+
+    return result;
+}
index 74199256cc5de94f729d908bc2ccce8b00054684..73b080e4843b9dec07a1c005e8abd0bbab57281b 100644 (file)
@@ -4,7 +4,7 @@
 notmuch_string_map_t *
 _notmuch_message_property_map (notmuch_message_t *message);
 
-notmuch_bool_t
+bool
 _notmuch_message_frozen (notmuch_message_t *message);
 
 void
index f32d555062d2bce0decf7cd5f9b33894655e7924..35eaf3c60f3f43f72c0cae818a77bc09a5e260dd 100644 (file)
@@ -38,7 +38,7 @@ notmuch_message_get_property (notmuch_message_t *message, const char *key, const
 
 static notmuch_status_t
 _notmuch_message_modify_property (notmuch_message_t *message, const char *key, const char *value,
-                                 notmuch_bool_t delete_it)
+                                 bool delete_it)
 {
     notmuch_private_status_t private_status;
     notmuch_status_t status;
@@ -76,17 +76,18 @@ _notmuch_message_modify_property (notmuch_message_t *message, const char *key, c
 notmuch_status_t
 notmuch_message_add_property (notmuch_message_t *message, const char *key, const char *value)
 {
-    return _notmuch_message_modify_property (message, key, value, FALSE);
+    return _notmuch_message_modify_property (message, key, value, false);
 }
 
 notmuch_status_t
 notmuch_message_remove_property (notmuch_message_t *message, const char *key, const char *value)
 {
-    return _notmuch_message_modify_property (message, key, value, TRUE);
+    return _notmuch_message_modify_property (message, key, value, true);
 }
 
+static
 notmuch_status_t
-notmuch_message_remove_all_properties (notmuch_message_t *message, const char *key)
+_notmuch_message_remove_all_properties (notmuch_message_t *message, const char *key, bool prefix)
 {
     notmuch_status_t status;
     const char * term_prefix;
@@ -97,7 +98,8 @@ notmuch_message_remove_all_properties (notmuch_message_t *message, const char *k
 
     _notmuch_message_invalidate_metadata (message, "property");
     if (key)
-       term_prefix = talloc_asprintf (message, "%s%s=", _find_prefix ("property"), key);
+       term_prefix = talloc_asprintf (message, "%s%s%s", _find_prefix ("property"), key,
+                                      prefix ? "" : "=");
     else
        term_prefix = _find_prefix ("property");
 
@@ -107,6 +109,18 @@ notmuch_message_remove_all_properties (notmuch_message_t *message, const char *k
     return NOTMUCH_STATUS_SUCCESS;
 }
 
+notmuch_status_t
+notmuch_message_remove_all_properties (notmuch_message_t *message, const char *key)
+{
+    return _notmuch_message_remove_all_properties (message, key, false);
+}
+
+notmuch_status_t
+notmuch_message_remove_all_properties_with_prefix (notmuch_message_t *message, const char *prefix)
+{
+    return _notmuch_message_remove_all_properties (message, prefix, true);
+}
+
 notmuch_message_properties_t *
 notmuch_message_get_properties (notmuch_message_t *message, const char *key, notmuch_bool_t exact)
 {
index f78e5a9d302fd62c09e41dfc59966b57e3056c3b..12743460a4895e1acfc1849f6ac19ac2979ca698 100644 (file)
@@ -36,6 +36,7 @@ struct _notmuch_message {
     notmuch_string_list_t *tag_list;
     notmuch_string_list_t *filename_term_list;
     notmuch_string_list_t *filename_list;
+    char *maildir_flags;
     char *author;
     notmuch_message_file_t *message_file;
     notmuch_string_list_t *property_term_list;
@@ -47,7 +48,7 @@ struct _notmuch_message {
     unsigned long lazy_flags;
 
     /* Message document modified since last sync */
-    notmuch_bool_t modified;
+    bool modified;
 
     /* last view of database the struct is synced with */
     unsigned long last_view;
@@ -61,16 +62,16 @@ struct _notmuch_message {
 struct maildir_flag_tag {
     char flag;
     const char *tag;
-    notmuch_bool_t inverse;
+    bool inverse;
 };
 
 /* ASCII ordered table of Maildir flags and associated tags */
 static struct maildir_flag_tag flag2tag[] = {
-    { 'D', "draft",   FALSE},
-    { 'F', "flagged", FALSE},
-    { 'P', "passed",  FALSE},
-    { 'R', "replied", FALSE},
-    { 'S', "unread",  TRUE }
+    { 'D', "draft",   false},
+    { 'F', "flagged", false},
+    { 'P', "passed",  false},
+    { 'R', "replied", false},
+    { 'S', "unread",  true }
 };
 
 /* We end up having to call the destructor explicitly because we had
@@ -123,6 +124,7 @@ _notmuch_message_create_for_document (const void *talloc_owner,
     message->tag_list = NULL;
     message->filename_term_list = NULL;
     message->filename_list = NULL;
+    message->maildir_flags = NULL;
     message->message_file = NULL;
     message->author = NULL;
     message->property_term_list = NULL;
@@ -268,7 +270,7 @@ _notmuch_message_create_for_message_id (notmuch_database_t *notmuch,
     } catch (const Xapian::Error &error) {
        _notmuch_database_log(_notmuch_message_database (message), "A Xapian exception occurred creating message: %s\n",
                 error.get_msg().c_str());
-       notmuch->exception_reported = TRUE;
+       notmuch->exception_reported = true;
        *status_ret = NOTMUCH_PRIVATE_STATUS_XAPIAN_EXCEPTION;
        return NULL;
     }
@@ -525,7 +527,7 @@ notmuch_message_get_header (notmuch_message_t *message, const char *header)
        } catch (Xapian::Error &error) {
            _notmuch_database_log(_notmuch_message_database (message), "A Xapian exception occurred when reading header: %s\n",
                     error.get_msg().c_str());
-           message->notmuch->exception_reported = TRUE;
+           message->notmuch->exception_reported = true;
            return NULL;
        }
     }
@@ -579,7 +581,9 @@ void
 _notmuch_message_remove_terms (notmuch_message_t *message, const char *prefix)
 {
     Xapian::TermIterator i;
-    size_t prefix_len = strlen (prefix);
+    size_t prefix_len = 0;
+
+    prefix_len = strlen (prefix);
 
     while (1) {
        i = message->doc.termlist_begin ();
@@ -592,13 +596,66 @@ _notmuch_message_remove_terms (notmuch_message_t *message, const char *prefix)
 
        try {
            message->doc.remove_term ((*i));
-           message->modified = TRUE;
+           message->modified = true;
        } catch (const Xapian::InvalidArgumentError) {
            /* Ignore failure to remove non-existent term. */
        }
     }
 }
 
+
+/* Remove all terms generated by indexing, i.e. not tags or
+ * properties, along with any automatic tags*/
+notmuch_private_status_t
+_notmuch_message_remove_indexed_terms (notmuch_message_t *message)
+{
+    Xapian::TermIterator i;
+
+    const std::string
+       id_prefix = _find_prefix ("id"),
+       property_prefix = _find_prefix ("property"),
+       tag_prefix = _find_prefix ("tag"),
+       type_prefix = _find_prefix ("type");
+
+    for (i = message->doc.termlist_begin ();
+        i != message->doc.termlist_end (); i++) {
+
+       const std::string term = *i;
+
+       if (term.compare (0, type_prefix.size (), type_prefix) == 0)
+           continue;
+
+       if (term.compare (0, id_prefix.size (), id_prefix) == 0)
+           continue;
+
+       if (term.compare (0, property_prefix.size (), property_prefix) == 0)
+           continue;
+
+       if (term.compare (0, tag_prefix.size (), tag_prefix) == 0 &&
+           term.compare (1, strlen("encrypted"), "encrypted") != 0 &&
+           term.compare (1, strlen("signed"), "signed") != 0 &&
+           term.compare (1, strlen("attachment"), "attachment") != 0)
+           continue;
+
+       try {
+           message->doc.remove_term ((*i));
+           message->modified = true;
+       } catch (const Xapian::InvalidArgumentError) {
+           /* Ignore failure to remove non-existent term. */
+       } catch (const Xapian::Error &error) {
+           notmuch_database_t *notmuch = message->notmuch;
+
+           if (!notmuch->exception_reported) {
+               _notmuch_database_log(_notmuch_message_database (message), "A Xapian exception occurred creating message: %s\n",
+                                     error.get_msg().c_str());
+               notmuch->exception_reported = true;
+           }
+           return NOTMUCH_PRIVATE_STATUS_XAPIAN_EXCEPTION;
+       }
+    }
+    return NOTMUCH_PRIVATE_STATUS_SUCCESS;
+}
+
 /* Return true if p points at "new" or "cur". */
 static bool is_maildir (const char *p)
 {
@@ -646,6 +703,7 @@ _notmuch_message_add_folder_terms (notmuch_message_t *message,
 
     talloc_free (folder);
 
+    message->modified = true;
     return NOTMUCH_STATUS_SUCCESS;
 }
 
@@ -847,7 +905,7 @@ void
 _notmuch_message_clear_data (notmuch_message_t *message)
 {
     message->doc.set_data ("");
-    message->modified = TRUE;
+    message->modified = true;
 }
 
 static void
@@ -946,6 +1004,14 @@ notmuch_message_get_filenames (notmuch_message_t *message)
     return _notmuch_filenames_create (message, message->filename_list);
 }
 
+int
+notmuch_message_count_files (notmuch_message_t *message)
+{
+    _notmuch_message_ensure_filename_list (message);
+
+    return _notmuch_string_list_length (message->filename_list);
+}
+
 notmuch_bool_t
 notmuch_message_get_flag (notmuch_message_t *message,
                          notmuch_message_flag_t flag)
@@ -978,7 +1044,7 @@ notmuch_message_get_date (notmuch_message_t *message)
     } catch (Xapian::Error &error) {
        _notmuch_database_log(_notmuch_message_database (message), "A Xapian exception occurred when reading date: %s\n",
                 error.get_msg().c_str());
-       message->notmuch->exception_reported = TRUE;
+       message->notmuch->exception_reported = true;
        return 0;
     }
 
@@ -1049,7 +1115,7 @@ _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;
+    message->modified = true;
 }
 
 /* Upgrade a message to support NOTMUCH_FEATURE_LAST_MOD.  The caller
@@ -1059,7 +1125,7 @@ _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;
+    message->modified = true;
 }
 
 /* Synchronize changes made to message->doc out into the database. */
@@ -1088,7 +1154,7 @@ _notmuch_message_sync (notmuch_message_t *message)
 
     db = static_cast <Xapian::WritableDatabase *> (message->notmuch->xapian_db);
     db->replace_document (message->doc_id, message->doc);
-    message->modified = FALSE;
+    message->modified = false;
 }
 
 /* Delete a message document from the database, leaving a ghost
@@ -1104,7 +1170,7 @@ _notmuch_message_delete (notmuch_message_t *message)
     notmuch_database_t *notmuch;
     notmuch_query_t *query;
     unsigned int count = 0;
-    notmuch_bool_t is_ghost;
+    bool is_ghost;
 
     mid = notmuch_message_get_message_id (message);
     tid = notmuch_message_get_thread_id (message);
@@ -1233,7 +1299,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;
+    message->modified = true;
 
     talloc_free (term);
 
@@ -1302,7 +1368,7 @@ _notmuch_message_remove_term (notmuch_message_t *message,
 
     try {
        message->doc.remove_term (term);
-       message->modified = TRUE;
+       message->modified = true;
     } catch (const Xapian::InvalidArgumentError) {
        /* We'll let the philosophers try to wrestle with the
         * question of whether failing to remove that which was not
@@ -1321,10 +1387,10 @@ notmuch_private_status_t
 _notmuch_message_has_term (notmuch_message_t *message,
                           const char *prefix_name,
                           const char *value,
-                          notmuch_bool_t *result)
+                          bool *result)
 {
     char *term;
-    notmuch_bool_t out = FALSE;
+    bool out = false;
     notmuch_private_status_t status = NOTMUCH_PRIVATE_STATUS_SUCCESS;
 
     if (value == NULL)
@@ -1342,7 +1408,7 @@ _notmuch_message_has_term (notmuch_message_t *message,
        i.skip_to (term);
        if (i != message->doc.termlist_end () &&
            !strcmp ((*i).c_str (), term))
-           out = TRUE;
+           out = true;
     } catch (Xapian::Error &error) {
        status = NOTMUCH_PRIVATE_STATUS_XAPIAN_EXCEPTION;
     }
@@ -1449,17 +1515,22 @@ _filename_is_in_maildir (const char *filename)
     return NULL;
 }
 
-notmuch_status_t
-notmuch_message_maildir_flags_to_tags (notmuch_message_t *message)
+static void
+_ensure_maildir_flags (notmuch_message_t *message, bool force)
 {
     const char *flags;
-    notmuch_status_t status;
     notmuch_filenames_t *filenames;
     const char *filename, *dir;
     char *combined_flags = talloc_strdup (message, "");
-    unsigned i;
     int seen_maildir_info = 0;
 
+    if (message->maildir_flags) {
+       if (force) {
+           talloc_free (message->maildir_flags);
+           message->maildir_flags = NULL;
+       }
+    }
+
     for (filenames = notmuch_message_get_filenames (message);
         notmuch_filenames_valid (filenames);
         notmuch_filenames_move_to_next (filenames))
@@ -1485,11 +1556,28 @@ notmuch_message_maildir_flags_to_tags (notmuch_message_t *message)
            seen_maildir_info = 1;
        }
     }
+    if (seen_maildir_info)
+       message->maildir_flags = combined_flags;
+}
+
+notmuch_bool_t
+notmuch_message_has_maildir_flag (notmuch_message_t *message, char flag)
+{
+    _ensure_maildir_flags (message, false);
+    return message->maildir_flags && (strchr (message->maildir_flags, flag) != NULL);
+}
 
+notmuch_status_t
+notmuch_message_maildir_flags_to_tags (notmuch_message_t *message)
+{
+    notmuch_status_t status;
+    unsigned i;
+
+    _ensure_maildir_flags (message, true);
     /* If none of the filenames have any maildir info field (not even
      * an empty info with no flags set) then there's no information to
      * go on, so do nothing. */
-    if (! seen_maildir_info)
+    if (! message->maildir_flags)
        return NOTMUCH_STATUS_SUCCESS;
 
     status = notmuch_message_freeze (message);
@@ -1497,7 +1585,7 @@ notmuch_message_maildir_flags_to_tags (notmuch_message_t *message)
        return status;
 
     for (i = 0; i < ARRAY_SIZE(flag2tag); i++) {
-       if ((strchr (combined_flags, flag2tag[i].flag) != NULL)
+       if ((strchr (message->maildir_flags, flag2tag[i].flag) != NULL)
            ^
            flag2tag[i].inverse)
        {
@@ -1510,8 +1598,6 @@ notmuch_message_maildir_flags_to_tags (notmuch_message_t *message)
     }
     status = notmuch_message_thaw (message);
 
-    talloc_free (combined_flags);
-
     return status;
 }
 
@@ -1599,7 +1685,7 @@ _new_maildir_filename (void *ctx,
     char *filename_new, *dir;
     char flag_map[128];
     int flags_in_map = 0;
-    notmuch_bool_t flags_changed = FALSE;
+    bool flags_changed = false;
     unsigned int i;
     char *s;
 
@@ -1640,7 +1726,7 @@ _new_maildir_filename (void *ctx,
        if (flag_map[flag] == 0) {
            flag_map[flag] = 1;
            flags_in_map++;
-           flags_changed = TRUE;
+           flags_changed = true;
        }
     }
 
@@ -1649,7 +1735,7 @@ _new_maildir_filename (void *ctx,
        if (flag_map[flag]) {
            flag_map[flag] = 0;
            flags_in_map--;
-           flags_changed = TRUE;
+           flags_changed = true;
        }
     }
 
@@ -1867,8 +1953,118 @@ _notmuch_message_property_map (notmuch_message_t *message)
     return message->property_map;
 }
 
-notmuch_bool_t
+bool
 _notmuch_message_frozen (notmuch_message_t *message)
 {
     return message->frozen;
 }
+
+notmuch_status_t
+notmuch_message_reindex (notmuch_message_t *message,
+                        notmuch_indexopts_t *indexopts)
+{
+    notmuch_database_t *notmuch = NULL;
+    notmuch_status_t ret = NOTMUCH_STATUS_SUCCESS;
+    notmuch_private_status_t private_status;
+    notmuch_filenames_t *orig_filenames = NULL;
+    const char *orig_thread_id = NULL;
+    notmuch_message_file_t *message_file = NULL;
+
+    int found = 0;
+
+    if (message == NULL)
+       return NOTMUCH_STATUS_NULL_POINTER;
+
+    /* Save in case we need to delete message */
+    orig_thread_id = notmuch_message_get_thread_id (message);
+    if (!orig_thread_id) {
+       /* XXX TODO: make up new error return? */
+       INTERNAL_ERROR ("message without thread-id");
+    }
+
+    /* strdup it because the metadata may be invalidated */
+    orig_thread_id = talloc_strdup (message, orig_thread_id);
+
+    notmuch = _notmuch_message_database (message);
+
+    ret = _notmuch_database_ensure_writable (notmuch);
+    if (ret)
+       return ret;
+
+    orig_filenames = notmuch_message_get_filenames (message);
+
+    private_status = _notmuch_message_remove_indexed_terms (message);
+    if (private_status) {
+       ret = COERCE_STATUS(private_status, "error removing terms");
+       goto DONE;
+    }
+
+    ret = notmuch_message_remove_all_properties_with_prefix (message, "index.");
+    if (ret)
+       goto DONE; /* XXX TODO: distinguish from other error returns above? */
+
+    /* re-add the filenames with the associated indexopts */
+    for (; notmuch_filenames_valid (orig_filenames);
+        notmuch_filenames_move_to_next (orig_filenames)) {
+
+       const char *date;
+       const char *from, *to, *subject;
+       char *message_id = NULL;
+       const char *thread_id = NULL;
+
+       const char *filename = notmuch_filenames_get (orig_filenames);
+
+       message_file = _notmuch_message_file_open (notmuch, filename);
+       if (message_file == NULL)
+           continue;
+
+       ret = _notmuch_message_file_get_headers (message_file,
+                                                &from, &subject, &to, &date,
+                                                &message_id);
+       if (ret)
+           goto DONE;
+
+       /* XXX TODO: deal with changing message id? */
+
+       _notmuch_message_add_filename (message, filename);
+
+       ret = _notmuch_database_link_message_to_parents (notmuch, message,
+                                                        message_file,
+                                                        &thread_id);
+       if (ret)
+           goto DONE;
+
+       if (thread_id == NULL)
+           thread_id = orig_thread_id;
+
+       _notmuch_message_add_term (message, "thread", thread_id);
+       /* Take header values only from first filename */
+       if (found == 0)
+           _notmuch_message_set_header_values (message, date, from, subject);
+
+       ret = _notmuch_message_index_file (message, indexopts, message_file);
+
+       if (ret == NOTMUCH_STATUS_FILE_ERROR)
+           continue;
+       if (ret)
+           goto DONE;
+
+       found++;
+       _notmuch_message_file_close (message_file);
+       message_file = NULL;
+    }
+    if (found == 0) {
+       /* put back thread id to help cleanup */
+       _notmuch_message_add_term (message, "thread", orig_thread_id);
+       ret = _notmuch_message_delete (message);
+    } else {
+       _notmuch_message_sync (message);
+    }
+
+ DONE:
+    if (message_file)
+       _notmuch_message_file_close (message_file);
+
+    /* XXX TODO destroy orig_filenames? */
+    return ret;
+}
index b5363bb8f6750d18efac91afdb9b5be0d6922670..a88f974ff5949bd8d54b886c10949e538ff81909 100644 (file)
@@ -68,7 +68,7 @@ _notmuch_messages_create (notmuch_message_list_t *list)
     if (unlikely (messages == NULL))
        return NULL;
 
-    messages->is_of_list_type = TRUE;
+    messages->is_of_list_type = true;
     messages->iterator = list->head;
 
     return messages;
@@ -93,7 +93,7 @@ notmuch_bool_t
 notmuch_messages_valid (notmuch_messages_t *messages)
 {
     if (messages == NULL)
-       return FALSE;
+       return false;
 
     if (! messages->is_of_list_type)
        return _notmuch_mset_messages_valid (messages);
index 5dfebf5dfd0d3a34f2953bbec90aca1dc74e3a29..1093429cfc77026289f49754c7ea5c84330c71e6 100644 (file)
@@ -24,6 +24,7 @@
 #ifndef _GNU_SOURCE
 #define _GNU_SOURCE /* For getline and asprintf */
 #endif
+#include <stdbool.h>
 #include <stdio.h>
 
 #include "compat.h"
@@ -51,6 +52,7 @@ NOTMUCH_BEGIN_DECLS
 #include "xutil.h"
 #include "error_util.h"
 #include "string-util.h"
+#include "crypto.h"
 
 #ifdef DEBUG
 # define DEBUG_DATABASE_SANITY 1
@@ -282,7 +284,7 @@ notmuch_private_status_t
 _notmuch_message_has_term (notmuch_message_t *message,
                           const char *prefix_name,
                           const char *value,
-                          notmuch_bool_t *result);
+                          bool *result);
 
 notmuch_private_status_t
 _notmuch_message_gen_terms (notmuch_message_t *message,
@@ -425,10 +427,28 @@ const char *
 _notmuch_message_file_get_header (notmuch_message_file_t *message,
                                 const char *header);
 
+notmuch_status_t
+_notmuch_message_file_get_headers (notmuch_message_file_t *message_file,
+                                  const char **from_out,
+                                  const char **subject_out,
+                                  const char **to_out,
+                                  const char **date_out,
+                                  char **message_id_out);
+
+const char *
+_notmuch_message_file_get_filename (notmuch_message_file_t *message);
+
+/* add-message.cc */
+notmuch_status_t
+_notmuch_database_link_message_to_parents (notmuch_database_t *notmuch,
+                                          notmuch_message_t *message,
+                                          notmuch_message_file_t *message_file,
+                                          const char **thread_id);
 /* index.cc */
 
 notmuch_status_t
 _notmuch_message_index_file (notmuch_message_t *message,
+                            notmuch_indexopts_t *indexopts,
                             notmuch_message_file_t *message_file);
 
 /* messages.c */
@@ -448,7 +468,7 @@ typedef struct _notmuch_message_list {
  * ignorance of that here. (See notmuch_mset_messages_t in query.cc)
  */
 struct _notmuch_messages {
-    notmuch_bool_t is_of_list_type;
+    bool is_of_list_type;
     notmuch_doc_id_set_t *excluded_doc_ids;
     notmuch_message_node_t *iterator;
 };
@@ -465,7 +485,7 @@ _notmuch_messages_create (notmuch_message_list_t *list);
 
 /* query.cc */
 
-notmuch_bool_t
+bool
 _notmuch_mset_messages_valid (notmuch_messages_t *messages);
 
 notmuch_message_t *
@@ -474,7 +494,7 @@ _notmuch_mset_messages_get (notmuch_messages_t *messages);
 void
 _notmuch_mset_messages_move_to_next (notmuch_messages_t *messages);
 
-notmuch_bool_t
+bool
 _notmuch_doc_id_set_contains (notmuch_doc_id_set_t *doc_ids,
                              unsigned int doc_id);
 
@@ -492,6 +512,20 @@ notmuch_status_t
 _notmuch_query_count_documents (notmuch_query_t *query,
                                const char *type,
                                unsigned *count_out);
+/* message-id.c */
+
+/* Parse an RFC 822 message-id, discarding whitespace, any RFC 822
+ * comments, and the '<' and '>' delimiters.
+ *
+ * If not NULL, then *next will be made to point to the first character
+ * not parsed, (possibly pointing to the final '\0' terminator.
+ *
+ * Returns a newly talloc'ed string belonging to 'ctx'.
+ *
+ * Returns NULL if there is any error parsing the message-id. */
+char *
+_notmuch_message_id_parse (void *ctx, const char *message_id, const char **next);
+
 
 /* message.cc */
 
@@ -501,6 +535,8 @@ _notmuch_message_add_reply (notmuch_message_t *message,
 notmuch_database_t *
 _notmuch_message_database (notmuch_message_t *message);
 
+void
+_notmuch_message_remove_unprefixed_terms (notmuch_message_t *message);
 /* sha1.c */
 
 char *
@@ -525,6 +561,12 @@ typedef struct _notmuch_string_list {
 notmuch_string_list_t *
 _notmuch_string_list_create (const void *ctx);
 
+/*
+ * return the number of strings in 'list'
+ */
+int
+_notmuch_string_list_length (notmuch_string_list_t *list);
+
 /* Add 'string' to 'list'.
  *
  * The list will create its own talloced copy of 'string'.
@@ -552,9 +594,9 @@ _notmuch_string_map_get (notmuch_string_map_t *map, const char *key);
 
 notmuch_string_map_iterator_t *
 _notmuch_string_map_iterator_create (notmuch_string_map_t *map, const char *key,
-                                    notmuch_bool_t exact);
+                                    bool exact);
 
-notmuch_bool_t
+bool
 _notmuch_string_map_iterator_valid (notmuch_string_map_iterator_t *iter);
 
 void
@@ -593,6 +635,12 @@ _notmuch_thread_create (void *ctx,
                        notmuch_exclude_t omit_exclude,
                        notmuch_sort_t sort);
 
+/* indexopts.c */
+
+struct _notmuch_indexopts {
+    _notmuch_crypto_t crypto;
+};
+
 NOTMUCH_END_DECLS
 
 #ifdef __cplusplus
index 17f0872e9edf80189602712a5d5be652de8a8e2d..2c5dcab5e6a0b00f2cf48e4f69d37a7c289c2484 100644 (file)
@@ -191,6 +191,23 @@ typedef enum _notmuch_status {
      * function, in a way not covered by a more specific argument.
      */
     NOTMUCH_STATUS_ILLEGAL_ARGUMENT,
+    /**
+     * A MIME object claimed to have cryptographic protection which
+     * notmuch tried to handle, but the protocol was not specified in
+     * an intelligible way.
+     */
+    NOTMUCH_STATUS_MALFORMED_CRYPTO_PROTOCOL,
+    /**
+     * Notmuch attempted to do crypto processing, but could not
+     * initialize the engine needed to do so.
+     */
+    NOTMUCH_STATUS_FAILED_CRYPTO_CONTEXT_CREATION,
+    /**
+     * A MIME object claimed to have cryptographic protection, and
+     * notmuch attempted to process it, but the specific protocol was
+     * something that notmuch doesn't know how to handle.
+     */
+    NOTMUCH_STATUS_UNKNOWN_CRYPTO_PROTOCOL,
     /**
      * Not an actual status value. Just a way to find out how many
      * valid status values there are.
@@ -219,6 +236,7 @@ typedef struct _notmuch_tags notmuch_tags_t;
 typedef struct _notmuch_directory notmuch_directory_t;
 typedef struct _notmuch_filenames notmuch_filenames_t;
 typedef struct _notmuch_config_list notmuch_config_list_t;
+typedef struct _notmuch_indexopts notmuch_indexopts_t;
 #endif /* __DOXYGEN__ */
 
 /**
@@ -236,7 +254,7 @@ typedef struct _notmuch_config_list notmuch_config_list_t;
  * The database will not yet have any data in it
  * (notmuch_database_create itself is a very cheap function). Messages
  * contained within 'path' can be added to the database by calling
- * notmuch_database_add_message.
+ * notmuch_database_index_file.
  *
  * In case of any failure, this function returns an error status and
  * sets *database to NULL (after printing an error message on stderr).
@@ -541,8 +559,10 @@ notmuch_database_get_directory (notmuch_database_t *database,
                                notmuch_directory_t **directory);
 
 /**
- * Add a new message to the given notmuch database or associate an
- * additional filename with an existing message.
+ * Add a message file to a database, indexing it for retrieval by
+ * future searches.  If a message already exists with the same message
+ * ID as the specified file, their indexes will be merged, and this
+ * new filename will also be associated with the existing message.
  *
  * Here, 'filename' should be a path relative to the path of
  * 'database' (see notmuch_database_get_path), or else should be an
@@ -555,8 +575,14 @@ notmuch_database_get_directory (notmuch_database_t *database,
  * entire contents of the file.
  *
  * If another message with the same message ID already exists in the
- * database, rather than creating a new message, this adds 'filename'
- * to the list of the filenames for the existing message.
+ * database, rather than creating a new message, this adds the search
+ * terms from the identified file to the existing message's index, and
+ * adds 'filename' to the list of filenames known for the message.
+ *
+ * The 'indexopts' parameter can be NULL (meaning, use the indexing
+ * defaults from the database), or can be an explicit choice of
+ * indexing options that should govern the indexing of this specific
+ * 'filename'.
  *
  * If 'message' is not NULL, then, on successful return
  * (NOTMUCH_STATUS_SUCCESS or NOTMUCH_STATUS_DUPLICATE_MESSAGE_ID) '*message'
@@ -589,8 +615,25 @@ notmuch_database_get_directory (notmuch_database_t *database,
  *
  * NOTMUCH_STATUS_UPGRADE_REQUIRED: The caller must upgrade the
  *     database to use this function.
+ *
+ * @since libnotmuch 5.1 (notmuch 0.26)
  */
 notmuch_status_t
+notmuch_database_index_file (notmuch_database_t *database,
+                            const char *filename,
+                            notmuch_indexopts_t *indexopts,
+                            notmuch_message_t **message);
+
+/**
+ * Deprecated alias for notmuch_database_index_file called with
+ * NULL indexopts.
+ *
+ * @deprecated Deprecated as of libnotmuch 5.1 (notmuch 0.26). Please
+ * use notmuch_database_index_file instead.
+ *
+ */
+NOTMUCH_DEPRECATED(5,1)
+notmuch_status_t
 notmuch_database_add_message (notmuch_database_t *database,
                              const char *filename,
                              notmuch_message_t **message);
@@ -1096,6 +1139,18 @@ notmuch_thread_get_thread_id (notmuch_thread_t *thread);
 int
 notmuch_thread_get_total_messages (notmuch_thread_t *thread);
 
+/**
+ * Get the total number of files in 'thread'.
+ *
+ * This sums notmuch_message_count_files over all messages in the
+ * thread
+ * @returns Non-negative integer
+ * @since libnotmuch 5.0 (notmuch 0.25)
+ */
+
+int
+notmuch_thread_get_total_files (notmuch_thread_t *thread);
+
 /**
  * Get a notmuch_messages_t iterator for the top-level messages in
  * 'thread' in oldest-first order.
@@ -1341,6 +1396,14 @@ notmuch_message_get_thread_id (notmuch_message_t *message);
 notmuch_messages_t *
 notmuch_message_get_replies (notmuch_message_t *message);
 
+/**
+ * Get the total number of files associated with a message.
+ * @returns Non-negative integer
+ * @since libnotmuch 5.0 (notmuch 0.25)
+ */
+int
+notmuch_message_count_files (notmuch_message_t *message);
+
 /**
  * Get a filename for the email corresponding to 'message'.
  *
@@ -1373,6 +1436,20 @@ notmuch_message_get_filename (notmuch_message_t *message);
 notmuch_filenames_t *
 notmuch_message_get_filenames (notmuch_message_t *message);
 
+/**
+ * Re-index the e-mail corresponding to 'message' using the supplied index options
+ *
+ * Returns the status of the re-index operation.  (see the return
+ * codes documented in notmuch_database_index_file)
+ *
+ * After reindexing, the user should discard the message object passed
+ * in here by calling notmuch_message_destroy, since it refers to the
+ * original message, not to the reindexed message.
+ */
+notmuch_status_t
+notmuch_message_reindex (notmuch_message_t *message,
+                        notmuch_indexopts_t *indexopts);
+
 /**
  * Message flags.
  */
@@ -1546,13 +1623,21 @@ notmuch_message_remove_all_tags (notmuch_message_t *message);
  *
  * A client can ensure that notmuch database tags remain synchronized
  * with maildir flags by calling this function after each call to
- * notmuch_database_add_message. See also
+ * notmuch_database_index_file. See also
  * notmuch_message_tags_to_maildir_flags for synchronizing tag changes
  * back to maildir flags.
  */
 notmuch_status_t
 notmuch_message_maildir_flags_to_tags (notmuch_message_t *message);
 
+/**
+ * return TRUE if any filename of 'message' has maildir flag 'flag',
+ * FALSE otherwise.
+ *
+ */
+notmuch_bool_t
+notmuch_message_has_maildir_flag (notmuch_message_t *message, char flag);
+
 /**
  * Rename message filename(s) to encode tags as maildir flags.
  *
@@ -1679,6 +1764,9 @@ notmuch_message_destroy (notmuch_message_t *message);
  * add or delete values for, as other subsystems or extensions may
  * depend on these properties.
  *
+ * Please see notmuch-properties(7) for more details about specific
+ * properties and conventions around their use.
+ *
  */
 /**@{*/
 /**
@@ -1738,6 +1826,22 @@ notmuch_message_remove_property (notmuch_message_t *message, const char *key, co
 notmuch_status_t
 notmuch_message_remove_all_properties (notmuch_message_t *message, const char *key);
 
+/**
+ * Remove all (prefix*,value) pairs from the given message
+ *
+ * @param[in,out] message  message to operate on.
+ * @param[in]     prefix   delete properties with keys that start with prefix.
+ *                        If NULL, delete all properties
+ * @returns
+ * - NOTMUCH_STATUS_READ_ONLY_DATABASE: Database was opened in
+ *   read-only mode so message cannot be modified.
+ * - NOTMUCH_STATUS_SUCCESS: No error occured.
+ *
+ * @since libnotmuch 5.1 (notmuch 0.26)
+ */
+notmuch_status_t
+notmuch_message_remove_all_properties_with_prefix (notmuch_message_t *message, const char *prefix);
+
 /**
  * Opaque message property iterator
  */
@@ -1788,7 +1892,7 @@ notmuch_message_get_properties (notmuch_message_t *message, const char *key, not
  * says. Whereas when this function returns FALSE, calling any of
  * these functions results in undefined behaviour.
  *
- * See the documentation of notmuch_message_properties_get for example
+ * See the documentation of notmuch_message_get_properties for example
  * code showing how to iterate over a notmuch_message_properties_t
  * object.
  *
@@ -1908,7 +2012,7 @@ notmuch_tags_destroy (notmuch_tags_t *tags);
  *
  *   o Read the mtime of a directory from the filesystem
  *
- *   o Call add_message for all mail files in the directory
+ *   o Call index_file for all mail files in the directory
  *
  *   o Call notmuch_directory_set_mtime with the mtime read from the
  *     filesystem.
@@ -2113,6 +2217,54 @@ notmuch_config_list_move_to_next (notmuch_config_list_t *config_list);
 void
 notmuch_config_list_destroy (notmuch_config_list_t *config_list);
 
+
+/**
+ * get the current default indexing options for a given database.
+ *
+ * This object will survive until the database itself is destroyed,
+ * but the caller may also release it earlier with
+ * notmuch_indexopts_destroy.
+ *
+ * This object represents a set of options on how a message can be
+ * added to the index.  At the moment it is a featureless stub.
+ *
+ * @since libnotmuch 5.1 (notmuch 0.26)
+ */
+notmuch_indexopts_t *
+notmuch_database_get_default_indexopts (notmuch_database_t *db);
+
+/**
+ * Specify whether to decrypt encrypted parts while indexing.
+ *
+ * Be aware that the index is likely sufficient to reconstruct the
+ * cleartext of the message itself, so please ensure that the notmuch
+ * message index is adequately protected. DO NOT SET THIS FLAG TO TRUE
+ * without considering the security of your index.
+ *
+ * @since libnotmuch 5.1 (notmuch 0.26)
+ */
+notmuch_status_t
+notmuch_indexopts_set_try_decrypt (notmuch_indexopts_t *indexopts,
+                                  notmuch_bool_t try_decrypt);
+
+/**
+ * Return whether to decrypt encrypted parts while indexing.
+ * see notmuch_indexopts_set_try_decrypt.
+ *
+ * @since libnotmuch 5.1 (notmuch 0.26)
+ */
+notmuch_bool_t
+notmuch_indexopts_get_try_decrypt (const notmuch_indexopts_t *indexopts);
+
+/**
+ * Destroy a notmuch_indexopts_t object.
+ *
+ * @since libnotmuch 5.1 (notmuch 0.26)
+ */
+void
+notmuch_indexopts_destroy (notmuch_indexopts_t *options);
+
+
 /**
  * interrogate the library for compile time features
  *
index 9c6ecc8db5ce0d4e6ecb1d4d5adfc1d65b715bc1..d633fa3d908ae08c1e1493b6caa29b8d3570c409 100644 (file)
@@ -29,7 +29,7 @@ struct _notmuch_query {
     notmuch_sort_t sort;
     notmuch_string_list_t *exclude_terms;
     notmuch_exclude_t omit_excluded;
-    notmuch_bool_t parsed;
+    bool parsed;
     Xapian::Query xapian_query;
     std::set<std::string> terms;
 };
@@ -62,12 +62,12 @@ struct _notmuch_threads {
 };
 
 /* We need this in the message functions so forward declare. */
-static notmuch_bool_t
+static bool
 _notmuch_doc_id_set_init (void *ctx,
                          notmuch_doc_id_set_t *doc_ids,
                          GArray *arr);
 
-static notmuch_bool_t
+static bool
 _debug_query (void)
 {
     char *env = getenv ("NOTMUCH_DEBUG_QUERY");
@@ -97,7 +97,7 @@ notmuch_query_create (notmuch_database_t *notmuch,
 
     new (&query->xapian_query) Xapian::Query ();
     new (&query->terms) std::set<std::string> ();
-    query->parsed = FALSE;
+    query->parsed = false;
 
     talloc_set_destructor (query, _notmuch_query_destructor);
 
@@ -134,7 +134,7 @@ _notmuch_query_ensure_parsed (notmuch_query_t *query)
             t != query->xapian_query.get_terms_end (); ++t)
            query->terms.insert (*t);
 
-       query->parsed = TRUE;
+       query->parsed = true;
 
     } catch (const Xapian::Error &error) {
        if (!query->notmuch->exception_reported) {
@@ -144,7 +144,7 @@ _notmuch_query_ensure_parsed (notmuch_query_t *query)
            _notmuch_database_log_append (query->notmuch,
                                          "Query string was: %s\n",
                                          query->query_string);
-           query->notmuch->exception_reported = TRUE;
+           query->notmuch->exception_reported = true;
        }
 
        return NOTMUCH_STATUS_XAPIAN_EXCEPTION;
@@ -261,7 +261,7 @@ _notmuch_query_search_documents (notmuch_query_t *query,
 
     try {
 
-       messages->base.is_of_list_type = FALSE;
+       messages->base.is_of_list_type = false;
        messages->base.iterator = NULL;
        messages->notmuch = notmuch;
        new (&messages->iterator) Xapian::MSetIterator ();
@@ -304,7 +304,7 @@ _notmuch_query_search_documents (notmuch_query_t *query,
 
                mset = enquire.get_mset (0, notmuch->xapian_db->get_doccount ());
 
-               GArray *excluded_doc_ids = g_array_new (FALSE, FALSE, sizeof (unsigned int));
+               GArray *excluded_doc_ids = g_array_new (false, false, sizeof (unsigned int));
 
                for (iterator = mset.begin (); iterator != mset.end (); iterator++) {
                    unsigned int doc_id = *iterator;
@@ -322,13 +322,13 @@ _notmuch_query_search_documents (notmuch_query_t *query,
 
        switch (query->sort) {
        case NOTMUCH_SORT_OLDEST_FIRST:
-           enquire.set_sort_by_value (NOTMUCH_VALUE_TIMESTAMP, FALSE);
+           enquire.set_sort_by_value (NOTMUCH_VALUE_TIMESTAMP, false);
            break;
        case NOTMUCH_SORT_NEWEST_FIRST:
-           enquire.set_sort_by_value (NOTMUCH_VALUE_TIMESTAMP, TRUE);
+           enquire.set_sort_by_value (NOTMUCH_VALUE_TIMESTAMP, true);
            break;
        case NOTMUCH_SORT_MESSAGE_ID:
-           enquire.set_sort_by_value (NOTMUCH_VALUE_MESSAGE_ID, FALSE);
+           enquire.set_sort_by_value (NOTMUCH_VALUE_MESSAGE_ID, false);
            break;
        case NOTMUCH_SORT_UNSORTED:
            break;
@@ -359,13 +359,13 @@ _notmuch_query_search_documents (notmuch_query_t *query,
                               "Query string was: %s\n",
                               query->query_string);
 
-       notmuch->exception_reported = TRUE;
+       notmuch->exception_reported = true;
        talloc_free (messages);
        return NOTMUCH_STATUS_XAPIAN_EXCEPTION;
     }
 }
 
-notmuch_bool_t
+bool
 _notmuch_mset_messages_valid (notmuch_messages_t *messages)
 {
     notmuch_mset_messages_t *mset_messages;
@@ -415,7 +415,7 @@ _notmuch_mset_messages_get (notmuch_messages_t *messages)
 
     if (messages->excluded_doc_ids &&
        _notmuch_doc_id_set_contains (messages->excluded_doc_ids, doc_id))
-       notmuch_message_set_flag (message, NOTMUCH_MESSAGE_FLAG_EXCLUDED, TRUE);
+       notmuch_message_set_flag (message, NOTMUCH_MESSAGE_FLAG_EXCLUDED, true);
 
     return message;
 }
@@ -430,7 +430,7 @@ _notmuch_mset_messages_move_to_next (notmuch_messages_t *messages)
     mset_messages->iterator++;
 }
 
-static notmuch_bool_t
+static bool
 _notmuch_doc_id_set_init (void *ctx,
                          notmuch_doc_id_set_t *doc_ids,
                          GArray *arr)
@@ -443,7 +443,7 @@ _notmuch_doc_id_set_init (void *ctx,
     bitmap = talloc_zero_array (ctx, unsigned char, DOCIDSET_WORD(max) + 1);
 
     if (bitmap == NULL)
-       return FALSE;
+       return false;
 
     doc_ids->bitmap = bitmap;
     doc_ids->bound = max + 1;
@@ -453,15 +453,15 @@ _notmuch_doc_id_set_init (void *ctx,
        bitmap[DOCIDSET_WORD(doc_id)] |= 1 << DOCIDSET_BIT(doc_id);
     }
 
-    return TRUE;
+    return true;
 }
 
-notmuch_bool_t
+bool
 _notmuch_doc_id_set_contains (notmuch_doc_id_set_t *doc_ids,
                              unsigned int doc_id)
 {
     if (doc_id >= doc_ids->bound)
-       return FALSE;
+       return false;
     return doc_ids->bitmap[DOCIDSET_WORD(doc_id)] & (1 << DOCIDSET_BIT(doc_id));
 }
 
@@ -514,7 +514,7 @@ notmuch_query_search_threads (notmuch_query_t *query,
        return status;
     }
 
-    threads->doc_ids = g_array_new (FALSE, FALSE, sizeof (unsigned int));
+    threads->doc_ids = g_array_new (false, false, sizeof (unsigned int));
     while (notmuch_messages_valid (messages)) {
        unsigned int doc_id = _notmuch_mset_messages_get_doc_id (messages);
        g_array_append_val (threads->doc_ids, doc_id);
@@ -546,7 +546,7 @@ notmuch_threads_valid (notmuch_threads_t *threads)
     unsigned int doc_id;
 
     if (! threads)
-       return FALSE;
+       return false;
 
     while (threads->doc_id_pos < threads->doc_ids->len) {
        doc_id = g_array_index (threads->doc_ids, unsigned int,
index 43ebe4993e8f26db6441f46e7af5dc6b42886d0e..9c3ae7ef2a30eb6127cb3b180f2385ee5da0d5ea 100644 (file)
@@ -42,6 +42,12 @@ _notmuch_string_list_create (const void *ctx)
     return list;
 }
 
+int
+_notmuch_string_list_length (notmuch_string_list_t *list)
+{
+    return list->length;
+}
+
 void
 _notmuch_string_list_append (notmuch_string_list_t *list,
                             const char *string)
index 0bb77e93aaec8e461a4bc27778d17598e116f255..5aac8bcc8a8996b36050287183ca3e0893fe922e 100644 (file)
@@ -33,14 +33,14 @@ typedef struct _notmuch_string_pair_t {
 } notmuch_string_pair_t;
 
 struct _notmuch_string_map {
-    notmuch_bool_t sorted;
+    bool sorted;
     size_t length;
     notmuch_string_pair_t *pairs;
 };
 
 struct _notmuch_string_map_iterator {
     notmuch_string_pair_t *current;
-    notmuch_bool_t exact;
+    bool exact;
     const char *key;
 };
 
@@ -55,7 +55,7 @@ _notmuch_string_map_create (const void *ctx)
 
     map->length = 0;
     map->pairs = NULL;
-    map->sorted = TRUE;
+    map->sorted = true;
 
     return map;
 }
@@ -67,7 +67,7 @@ _notmuch_string_map_append (notmuch_string_map_t *map,
 {
 
     map->length++;
-    map->sorted = FALSE;
+    map->sorted = false;
 
     if (map->pairs)
        map->pairs = talloc_realloc (map, map->pairs, notmuch_string_pair_t, map->length + 1);
@@ -103,11 +103,11 @@ _notmuch_string_map_sort (notmuch_string_map_t *map)
 
     qsort (map->pairs, map->length, sizeof (notmuch_string_pair_t), cmppair);
 
-    map->sorted = TRUE;
+    map->sorted = true;
 }
 
-static notmuch_bool_t
-string_cmp (const char *a, const char *b, notmuch_bool_t exact)
+static bool
+string_cmp (const char *a, const char *b, bool exact)
 {
     if (exact)
        return (strcmp (a, b));
@@ -116,7 +116,7 @@ string_cmp (const char *a, const char *b, notmuch_bool_t exact)
 }
 
 static notmuch_string_pair_t *
-bsearch_first (notmuch_string_pair_t *array, size_t len, const char *key, notmuch_bool_t exact)
+bsearch_first (notmuch_string_pair_t *array, size_t len, const char *key, bool exact)
 {
     size_t first = 0;
     size_t last = len - 1;
@@ -151,7 +151,7 @@ _notmuch_string_map_get (notmuch_string_map_t *map, const char *key)
     /* this means that calling append invalidates iterators */
     _notmuch_string_map_sort (map);
 
-    pair = bsearch_first (map->pairs, map->length, key, TRUE);
+    pair = bsearch_first (map->pairs, map->length, key, true);
     if (! pair)
        return NULL;
 
@@ -160,7 +160,7 @@ _notmuch_string_map_get (notmuch_string_map_t *map, const char *key)
 
 notmuch_string_map_iterator_t *
 _notmuch_string_map_iterator_create (notmuch_string_map_t *map, const char *key,
-                                    notmuch_bool_t exact)
+                                    bool exact)
 {
     notmuch_string_map_iterator_t *iter;
 
@@ -179,15 +179,15 @@ _notmuch_string_map_iterator_create (notmuch_string_map_t *map, const char *key,
     return iter;
 }
 
-notmuch_bool_t
+bool
 _notmuch_string_map_iterator_valid (notmuch_string_map_iterator_t *iterator)
 {
     if (iterator->current == NULL)
-       return FALSE;
+       return false;
 
     /* sentinel */
     if (iterator->current->key == NULL)
-       return FALSE;
+       return false;
 
     return (0 == string_cmp (iterator->key, iterator->current->key, iterator->exact));
 
index 1a1ecfa5507ee8cf35e243eb4b44ddfdcd0526eb..1632da4cd0f4f9398e774b8157004e89914485d9 100644 (file)
@@ -44,6 +44,7 @@ struct _notmuch_thread {
 
     GHashTable *message_hash;
     int total_messages;
+    int total_files;
     int matched_messages;
     time_t oldest;
     time_t newest;
@@ -58,12 +59,12 @@ _notmuch_thread_destructor (notmuch_thread_t *thread)
     g_hash_table_unref (thread->message_hash);
 
     if (thread->authors_array) {
-       g_ptr_array_free (thread->authors_array, TRUE);
+       g_ptr_array_free (thread->authors_array, true);
        thread->authors_array = NULL;
     }
 
     if (thread->matched_authors_array) {
-       g_ptr_array_free (thread->matched_authors_array, TRUE);
+       g_ptr_array_free (thread->matched_authors_array, true);
        thread->matched_authors_array = NULL;
     }
 
@@ -155,9 +156,9 @@ _resolve_thread_authors_string (notmuch_thread_t *thread)
        first_non_matched_author = 0;
     }
 
-    g_ptr_array_free (thread->authors_array, TRUE);
+    g_ptr_array_free (thread->authors_array, true);
     thread->authors_array = NULL;
-    g_ptr_array_free (thread->matched_authors_array, TRUE);
+    g_ptr_array_free (thread->matched_authors_array, true);
     thread->matched_authors_array = NULL;
 }
 
@@ -238,7 +239,7 @@ _thread_add_message (notmuch_thread_t *thread,
     InternetAddress *address;
     const char *from, *author;
     char *clean_author;
-    notmuch_bool_t message_excluded = FALSE;
+    bool message_excluded = false;
 
     if (omit_exclude != NOTMUCH_EXCLUDE_FALSE) {
        for (tags = notmuch_message_get_tags (message);
@@ -253,7 +254,7 @@ _thread_add_message (notmuch_thread_t *thread,
            {
                /* Check for an empty string, and then ignore initial 'K'. */
                if (*(term->string) && strcmp(tag, (term->string + 1)) == 0) {
-                   message_excluded = TRUE;
+                   message_excluded = true;
                    break;
                }
            }
@@ -266,6 +267,7 @@ _thread_add_message (notmuch_thread_t *thread,
     _notmuch_message_list_add_message (thread->message_list,
                                       talloc_steal (thread, message));
     thread->total_messages++;
+    thread->total_files += notmuch_message_count_files (message);
 
     g_hash_table_insert (thread->message_hash,
                         xstrdup (notmuch_message_get_message_id (message)),
@@ -308,7 +310,7 @@ _thread_add_message (notmuch_thread_t *thread,
 
     /* Mark excluded messages. */
     if (message_excluded)
-       notmuch_message_set_flag (message, NOTMUCH_MESSAGE_FLAG_EXCLUDED, TRUE);
+       notmuch_message_set_flag (message, NOTMUCH_MESSAGE_FLAG_EXCLUDED, true);
 }
 
 static void
@@ -495,6 +497,7 @@ _notmuch_thread_create (void *ctx,
                                                  free, NULL);
 
     thread->total_messages = 0;
+    thread->total_files = 0;
     thread->matched_messages = 0;
     thread->oldest = 0;
     thread->newest = 0;
@@ -566,6 +569,12 @@ notmuch_thread_get_total_messages (notmuch_thread_t *thread)
     return thread->total_messages;
 }
 
+int
+notmuch_thread_get_total_files (notmuch_thread_t *thread)
+{
+    return thread->total_files;
+}
+
 int
 notmuch_thread_get_matched_messages (notmuch_thread_t *thread)
 {
index 1609173576e2fa4830e04540231f008bab48f2c8..e1aca969bef775d06659a556b1140e37897c1da9 100644 (file)
@@ -33,7 +33,7 @@ typedef struct mime_node_context {
     GMimeMessage *mime_message;
 
     /* Context provided by the caller. */
-    notmuch_crypto_t *crypto;
+    _notmuch_crypto_t *crypto;
 } mime_node_context_t;
 
 static int
@@ -56,7 +56,7 @@ _mime_node_context_free (mime_node_context_t *res)
 
 notmuch_status_t
 mime_node_open (const void *ctx, notmuch_message_t *message,
-               notmuch_crypto_t *crypto, mime_node_t **root_out)
+               _notmuch_crypto_t *crypto, mime_node_t **root_out)
 {
     const char *filename = notmuch_message_get_filename (message);
     mime_node_context_t *mctx;
@@ -79,12 +79,32 @@ mime_node_open (const void *ctx, notmuch_message_t *message,
     }
     talloc_set_destructor (mctx, _mime_node_context_free);
 
+    /* Fast path */
     mctx->file = fopen (filename, "r");
     if (! mctx->file) {
-       fprintf (stderr, "Error opening %s: %s\n", filename, strerror (errno));
-       status = NOTMUCH_STATUS_FILE_ERROR;
-       goto DONE;
-    }
+       /* Slow path - for some reason the first file in the list is
+        * not available anymore. This is clearly a problem in the
+        * database, but we are not going to let this problem be a
+        * show stopper */
+       notmuch_filenames_t *filenames;
+       for (filenames = notmuch_message_get_filenames (message);
+            notmuch_filenames_valid (filenames);
+            notmuch_filenames_move_to_next (filenames))
+       {
+           filename = notmuch_filenames_get (filenames);
+           mctx->file = fopen (filename, "r");
+           if (mctx->file)
+               break;
+       }
+
+       talloc_free (filenames);
+       if (! mctx->file) {
+           /* Give up */
+           fprintf (stderr, "Error opening %s: %s\n", filename, strerror (errno));
+               status = NOTMUCH_STATUS_FILE_ERROR;
+               goto DONE;
+           }
+       }
 
     mctx->stream = g_mime_stream_file_new (mctx->file);
     if (!mctx->stream) {
@@ -92,7 +112,7 @@ mime_node_open (const void *ctx, notmuch_message_t *message,
        status = NOTMUCH_STATUS_OUT_OF_MEMORY;
        goto DONE;
     }
-    g_mime_stream_file_set_owner (GMIME_STREAM_FILE (mctx->stream), FALSE);
+    g_mime_stream_file_set_owner (GMIME_STREAM_FILE (mctx->stream), false);
 
     mctx->parser = g_mime_parser_new_with_stream (mctx->stream);
     if (!mctx->parser) {
@@ -151,11 +171,11 @@ set_signature_list_destructor (mime_node_t *node)
 /* Verify a signed mime node (GMime 2.6) */
 static void
 node_verify (mime_node_t *node, GMimeObject *part,
-            g_mime_3_unused(notmuch_crypto_context_t *cryptoctx))
+            g_mime_3_unused(GMimeCryptoContext *cryptoctx))
 {
     GError *err = NULL;
 
-    node->verify_attempted = TRUE;
+    node->verify_attempted = true;
     node->sig_list = g_mime_multipart_signed_verify
        (GMIME_MULTIPART_SIGNED (part), cryptoctx, &err);
 
@@ -172,23 +192,27 @@ node_verify (mime_node_t *node, GMimeObject *part,
 /* Decrypt and optionally verify an encrypted mime node (GMime 2.6) */
 static void
 node_decrypt_and_verify (mime_node_t *node, GMimeObject *part,
-                        g_mime_3_unused(notmuch_crypto_context_t *cryptoctx))
+                        g_mime_3_unused(GMimeCryptoContext *cryptoctx))
 {
     GError *err = NULL;
     GMimeDecryptResult *decrypt_result = NULL;
     GMimeMultipartEncrypted *encrypteddata = GMIME_MULTIPART_ENCRYPTED (part);
 
-    node->decrypt_attempted = TRUE;
+    node->decrypt_attempted = true;
     node->decrypted_child = g_mime_multipart_encrypted_decrypt
+#if (GMIME_MAJOR_VERSION < 3)
        (encrypteddata, cryptoctx, &decrypt_result, &err);
+#else
+        (encrypteddata, GMIME_DECRYPT_NONE, NULL, &decrypt_result, &err);
+#endif
     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;
+    node->decrypt_success = true;
+    node->verify_attempted = true;
 
     /* This may be NULL if the part is not signed. */
     node->sig_list = g_mime_decrypt_result_get_signatures (decrypt_result);
@@ -207,7 +231,7 @@ static mime_node_t *
 _mime_node_create (mime_node_t *parent, GMimeObject *part)
 {
     mime_node_t *node = talloc_zero (parent, mime_node_t);
-    notmuch_crypto_context_t *cryptoctx = NULL;
+    GMimeCryptoContext *cryptoctx = NULL;
 
     /* Set basic node properties */
     node->part = part;
@@ -245,7 +269,12 @@ _mime_node_create (mime_node_t *parent, GMimeObject *part)
        || (GMIME_IS_MULTIPART_SIGNED (part) && node->ctx->crypto->verify)) {
        GMimeContentType *content_type = g_mime_object_get_content_type (part);
        const char *protocol = g_mime_content_type_get_parameter (content_type, "protocol");
-       cryptoctx = notmuch_crypto_get_context (node->ctx->crypto, protocol);
+       notmuch_status_t status;
+       status = _notmuch_crypto_get_gmime_ctx_for_protocol (node->ctx->crypto,
+                                                            protocol, &cryptoctx);
+       if (status) /* this is a warning, not an error */
+           fprintf (stderr, "Warning: %s (%s).\n", notmuch_status_to_string (status),
+                    protocol ? protocol : "NULL");
        if (!cryptoctx)
            return node;
     }
index ae37360b83600bf6a2bac76d47caf397cc53901c..f7524e596c8dc16341c52733e61232b6f691d9e8 100644 (file)
@@ -24,6 +24,7 @@
 #ifndef _GNU_SOURCE
 #define _GNU_SOURCE /* for getline */
 #endif
+#include <stdbool.h>
 #include <stdio.h>
 #include <sysexits.h>
 
 
 #include "gmime-extra.h"
 
-typedef GMimeCryptoContext notmuch_crypto_context_t;
-/* This is automatically included only since gmime 2.6.10 */
-#include <gmime/gmime-pkcs7-context.h>
-
 #include "notmuch.h"
 
 /* This is separate from notmuch-private.h because we're trying to
@@ -54,6 +51,7 @@ typedef GMimeCryptoContext notmuch_crypto_context_t;
 #include <ctype.h>
 
 #include "talloc-extra.h"
+#include "crypto.h"
 
 #define unused(x) x __attribute__ ((unused))
 
@@ -71,23 +69,13 @@ typedef struct notmuch_show_format {
                              const struct notmuch_show_params *params);
 } notmuch_show_format_t;
 
-typedef struct notmuch_crypto {
-    notmuch_bool_t verify;
-    notmuch_bool_t decrypt;
-#if (GMIME_MAJOR_VERSION < 3)
-    notmuch_crypto_context_t* gpgctx;
-    notmuch_crypto_context_t* pkcs7ctx;
-    const char *gpgpath;
-#endif
-} notmuch_crypto_t;
-
 typedef struct notmuch_show_params {
-    notmuch_bool_t entire_thread;
-    notmuch_bool_t omit_excluded;
-    notmuch_bool_t output_body;
+    bool entire_thread;
+    bool omit_excluded;
+    bool output_body;
     int part;
-    notmuch_crypto_t crypto;
-    notmuch_bool_t include_html;
+    _notmuch_crypto_t crypto;
+    bool include_html;
     GMimeStream *out_stream;
 } notmuch_show_params_t;
 
@@ -180,14 +168,6 @@ typedef struct _notmuch_config notmuch_config_t;
 void
 notmuch_exit_if_unsupported_format (void);
 
-#if (GMIME_MAJOR_VERSION <3)
-notmuch_crypto_context_t *
-notmuch_crypto_get_context (notmuch_crypto_t *crypto, const char *protocol);
-#endif
-
-int
-notmuch_crypto_cleanup (notmuch_crypto_t *crypto);
-
 int
 notmuch_count_command (notmuch_config_t *config, int argc, char *argv[]);
 
@@ -200,6 +180,9 @@ notmuch_new_command (notmuch_config_t *config, int argc, char *argv[]);
 int
 notmuch_insert_command (notmuch_config_t *config, int argc, char *argv[]);
 
+int
+notmuch_reindex_command (notmuch_config_t *config, int argc, char *argv[]);
+
 int
 notmuch_reply_command (notmuch_config_t *config, int argc, char *argv[]);
 
@@ -244,12 +227,12 @@ show_one_part (const char *filename, int part);
 
 void
 format_part_sprinter (const void *ctx, struct sprinter *sp, mime_node_t *node,
-                     notmuch_bool_t first, notmuch_bool_t output_body,
-                     notmuch_bool_t include_html);
+                     bool output_body,
+                     bool include_html);
 
 void
 format_headers_sprinter (struct sprinter *sp, GMimeMessage *message,
-                        notmuch_bool_t reply);
+                        bool reply);
 
 typedef enum {
     NOTMUCH_SHOW_TEXT_PART_REPLY = 1 << 0,
@@ -283,7 +266,7 @@ notmuch_config_close (notmuch_config_t *config);
 int
 notmuch_config_save (notmuch_config_t *config);
 
-notmuch_bool_t
+bool
 notmuch_config_is_new (notmuch_config_t *config);
 
 const char *
@@ -342,12 +325,12 @@ notmuch_config_set_new_ignore (notmuch_config_t *config,
                               const char *new_ignore[],
                               size_t length);
 
-notmuch_bool_t
+bool
 notmuch_config_get_maildir_synchronize_flags (notmuch_config_t *config);
 
 void
 notmuch_config_set_maildir_synchronize_flags (notmuch_config_t *config,
-                                             notmuch_bool_t synchronize_flags);
+                                             bool synchronize_flags);
 
 const char **
 notmuch_config_get_search_exclude_tags (notmuch_config_t *config, size_t *length);
@@ -360,7 +343,7 @@ notmuch_config_set_search_exclude_tags (notmuch_config_t *config,
 int
 notmuch_run_hook (const char *db_path, const char *hook);
 
-notmuch_bool_t
+bool
 debugger_is_active (void);
 
 /* mime-node.c */
@@ -403,14 +386,14 @@ struct mime_node {
     int part_num;
 
     /* True if decryption of this part was attempted. */
-    notmuch_bool_t decrypt_attempted;
+    bool decrypt_attempted;
     /* True if decryption of this part's child succeeded.  In this
      * case, the decrypted part is substituted for the second child of
      * this part (which would usually be the encrypted data). */
-    notmuch_bool_t decrypt_success;
+    bool decrypt_success;
 
     /* True if signature verification on this part was attempted. */
-    notmuch_bool_t verify_attempted;
+    bool verify_attempted;
 
     /* The list of signatures for signed or encrypted containers. If
      * there are no signatures, this will be NULL. */
@@ -445,7 +428,7 @@ struct mime_node {
  */
 notmuch_status_t
 mime_node_open (const void *ctx, notmuch_message_t *message,
-               notmuch_crypto_t *crypto, mime_node_t **node_out);
+               _notmuch_crypto_t *crypto, mime_node_t **node_out);
 
 /* Return a new MIME node for the requested child part of parent.
  * parent will be used as the talloc context for the returned child
@@ -484,7 +467,7 @@ notmuch_database_dump (notmuch_database_t *notmuch,
                       const char *query_str,
                       dump_format_t output_format,
                       dump_include_t include,
-                      notmuch_bool_t gzip_output);
+                      bool gzip_output);
 
 /* If status is non-zero (i.e. error) print appropriate
    messages to stderr.
@@ -505,11 +488,25 @@ status_to_exit (notmuch_status_t status);
 
 #include "command-line-arguments.h"
 
-extern char *notmuch_requested_db_uuid;
+extern const char *notmuch_requested_db_uuid;
 extern const notmuch_opt_desc_t  notmuch_shared_options [];
 void notmuch_exit_if_unmatched_db_uuid (notmuch_database_t *notmuch);
 
 void notmuch_process_shared_options (const char* subcommand_name);
 int notmuch_minimal_options (const char* subcommand_name,
                             int argc, char **argv);
+
+
+/* the state chosen by the user invoking one of the notmuch
+ * subcommands that does indexing */
+struct _notmuch_client_indexing_cli_choices {
+    bool try_decrypt;
+    bool try_decrypt_set;
+    notmuch_indexopts_t * opts;
+};
+extern struct _notmuch_client_indexing_cli_choices indexing_cli_choices;
+extern const notmuch_opt_desc_t  notmuch_shared_indexing_options [];
+notmuch_status_t
+notmuch_process_shared_indexing_options (notmuch_database_t *notmuch, notmuch_config_t *config);
+
 #endif
index 855545d735769e1269bd5a08ce6c1e99856bb989..f8996cf46039ea05cb2959ed2d153fc374d5f0eb 100644 (file)
@@ -32,14 +32,14 @@ notmuch_compact_command (notmuch_config_t *config, int argc, char *argv[])
     const char *path = notmuch_config_get_database_path (config);
     const char *backup_path = NULL;
     notmuch_status_t ret;
-    notmuch_bool_t quiet = FALSE;
+    bool quiet = false;
     int opt_index;
 
     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_string = &backup_path, .name = "backup" },
+       { .opt_bool =  &quiet, .name = "quiet" },
+       { .opt_inherit = notmuch_shared_options },
+       { }
     };
 
     opt_index = parse_arguments (argc, argv, options, 1);
index cb9529b90912d601f214a1f62147fa7b6265ce91..1cba2661d99d54276b0c6b6c5c2ee9954eb94359 100644 (file)
@@ -122,7 +122,7 @@ static const char crypto_config_comment[] =
 struct _notmuch_config {
     char *filename;
     GKeyFile *key_file;
-    notmuch_bool_t is_new;
+    bool is_new;
 
     char *database_path;
     char *crypto_gpg_path;
@@ -134,7 +134,7 @@ struct _notmuch_config {
     size_t new_tags_length;
     const char **new_ignore;
     size_t new_ignore_length;
-    notmuch_bool_t maildir_synchronize_flags;
+    bool maildir_synchronize_flags;
     const char **search_exclude_tags;
     size_t search_exclude_tags_length;
 };
@@ -212,8 +212,8 @@ get_username_from_passwd_file (void *ctx)
     return name;
 }
 
-static notmuch_bool_t
-get_config_from_file (notmuch_config_t *config, notmuch_bool_t create_new)
+static bool
+get_config_from_file (notmuch_config_t *config, bool create_new)
 {
     #define BUF_SIZE 4096
     char *config_str = NULL;
@@ -221,7 +221,7 @@ get_config_from_file (notmuch_config_t *config, notmuch_bool_t create_new)
     int config_bufsize = BUF_SIZE;
     size_t len;
     GError *error = NULL;
-    notmuch_bool_t ret = FALSE;
+    bool ret = false;
 
     FILE *fp = fopen(config->filename, "r");
     if (fp == NULL) {
@@ -230,8 +230,8 @@ get_config_from_file (notmuch_config_t *config, notmuch_bool_t create_new)
             * default configuration file in the case of FILE NOT FOUND.
             */
            if (create_new) {
-               config->is_new = TRUE;
-               ret = TRUE;
+               config->is_new = true;
+               ret = true;
            } else {
                fprintf (stderr, "Configuration file %s not found.\n"
                         "Try running 'notmuch setup' to create a configuration.\n",
@@ -271,7 +271,7 @@ get_config_from_file (notmuch_config_t *config, notmuch_bool_t create_new)
 
     if (g_key_file_load_from_data (config->key_file, config_str, config_len,
                                   G_KEY_FILE_KEEP_COMMENTS, &error)) {
-       ret = TRUE;
+       ret = true;
        goto out;
     }
 
@@ -352,7 +352,7 @@ notmuch_config_open (void *ctx,
     talloc_set_destructor (config, notmuch_config_destructor);
 
     /* non-zero defaults */
-    config->maildir_synchronize_flags = TRUE;
+    config->maildir_synchronize_flags = true;
 
     if (filename) {
        config->filename = talloc_strdup (config, filename);
@@ -366,7 +366,7 @@ notmuch_config_open (void *ctx,
     config->key_file = g_key_file_new ();
 
     if (config_mode & NOTMUCH_CONFIG_OPEN) {
-       notmuch_bool_t create_new = (config_mode & NOTMUCH_CONFIG_CREATE) != 0;
+       bool create_new = (config_mode & NOTMUCH_CONFIG_CREATE) != 0;
 
        if (! get_config_from_file (config, create_new)) {
            talloc_free (config);
@@ -466,7 +466,7 @@ notmuch_config_open (void *ctx,
        g_key_file_get_boolean (config->key_file,
                                "maildir", "synchronize_flags", &error);
     if (error) {
-       notmuch_config_set_maildir_synchronize_flags (config, TRUE);
+       notmuch_config_set_maildir_synchronize_flags (config, true);
        g_error_free (error);
     }
 
@@ -579,7 +579,7 @@ notmuch_config_save (notmuch_config_t *config)
     return 0;
 }
 
-notmuch_bool_t
+bool
 notmuch_config_is_new (notmuch_config_t *config)
 {
     return config->is_new;
@@ -808,7 +808,20 @@ _item_split (char *item, char **group, char **key)
 }
 
 #define BUILT_WITH_PREFIX "built_with."
-#define QUERY_PREFIX "query."
+
+static bool
+_stored_in_db (const char *item)
+{
+    const char * db_configs[] = {
+       "index.try_decrypt",
+    };
+    if (STRNCMP_LITERAL (item, "query.") == 0)
+       return true;
+    for (size_t i = 0; i < ARRAY_SIZE (db_configs); i++)
+       if (strcmp (item, db_configs[i]) == 0)
+           return true;
+    return false;
+}
 
 static int
 _print_db_config(notmuch_config_t *config, const char *name)
@@ -857,7 +870,7 @@ notmuch_config_command_get (notmuch_config_t *config, char *item)
     } else if (STRNCMP_LITERAL (item, BUILT_WITH_PREFIX) == 0) {
        printf ("%s\n",
                notmuch_built_with (item + strlen (BUILT_WITH_PREFIX)) ? "true" : "false");
-    } else if (STRNCMP_LITERAL (item, QUERY_PREFIX) == 0) {
+    } else if (_stored_in_db (item)) {
        return _print_db_config (config, item);
     } else {
        char **value;
@@ -928,7 +941,7 @@ notmuch_config_command_set (notmuch_config_t *config, char *item, int argc, char
        return 1;
     }
 
-    if (STRNCMP_LITERAL (item, QUERY_PREFIX) == 0) {
+    if (_stored_in_db (item)) {
        return _set_db_config (config, item, argc, argv);
     }
 
@@ -1086,7 +1099,7 @@ notmuch_config_command (notmuch_config_t *config, int argc, char *argv[])
 
 }
 
-notmuch_bool_t
+bool
 notmuch_config_get_maildir_synchronize_flags (notmuch_config_t *config)
 {
     return config->maildir_synchronize_flags;
@@ -1094,7 +1107,7 @@ notmuch_config_get_maildir_synchronize_flags (notmuch_config_t *config)
 
 void
 notmuch_config_set_maildir_synchronize_flags (notmuch_config_t *config,
-                                             notmuch_bool_t synchronize_flags)
+                                             bool synchronize_flags)
 {
     g_key_file_set_boolean (config->key_file,
                            "maildir", "synchronize_flags", synchronize_flags);
index 50b0c193f04625c794b9ab9287ab6bdd9c4f29da..ca05c9793b70efcc2932bc8a046af5fa4d2c5622 100644 (file)
@@ -27,12 +27,6 @@ enum {
     OUTPUT_FILES,
 };
 
-/* The following is to allow future options to be added more easily */
-enum {
-    EXCLUDE_TRUE,
-    EXCLUDE_FALSE,
-};
-
 /* Return the number of files matching the query, or -1 for an error */
 static int
 count_files (notmuch_query_t *query)
@@ -160,30 +154,27 @@ notmuch_count_command (notmuch_config_t *config, int argc, char *argv[])
     char *query_str;
     int opt_index;
     int output = OUTPUT_MESSAGES;
-    int exclude = EXCLUDE_TRUE;
+    bool exclude = true;
     const char **search_exclude_tags = NULL;
     size_t search_exclude_tags_length = 0;
-    notmuch_bool_t batch = FALSE;
-    notmuch_bool_t print_lastmod = FALSE;
+    bool batch = false;
+    bool print_lastmod = false;
     FILE *input = stdin;
-    char *input_file_name = NULL;
+    const char *input_file_name = NULL;
     int ret;
 
     notmuch_opt_desc_t options[] = {
-       { NOTMUCH_OPT_KEYWORD, &output, "output", 'o',
+       { .opt_keyword = &output, .name = "output", .keywords =
          (notmuch_keyword_t []){ { "threads", OUTPUT_THREADS },
                                  { "messages", OUTPUT_MESSAGES },
                                  { "files", OUTPUT_FILES },
                                  { 0, 0 } } },
-       { NOTMUCH_OPT_KEYWORD, &exclude, "exclude", 'x',
-         (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 }
+       { .opt_bool = &exclude, .name = "exclude" },
+       { .opt_bool = &print_lastmod, .name = "lastmod" },
+       { .opt_bool = &batch, .name = "batch" },
+       { .opt_string = &input_file_name, .name = "input" },
+       { .opt_inherit = notmuch_shared_options },
+       { }
     };
 
     opt_index = parse_arguments (argc, argv, options, 1);
@@ -193,7 +184,7 @@ notmuch_count_command (notmuch_config_t *config, int argc, char *argv[])
     notmuch_process_shared_options (argv[0]);
 
     if (input_file_name) {
-       batch = TRUE;
+       batch = true;
        input = fopen (input_file_name, "r");
        if (input == NULL) {
            fprintf (stderr, "Error opening %s for reading: %s\n",
@@ -204,6 +195,8 @@ notmuch_count_command (notmuch_config_t *config, int argc, char *argv[])
 
     if (batch && opt_index != argc) {
        fprintf (stderr, "--batch and query string are not compatible\n");
+       if (input)
+           fclose (input);
        return EXIT_FAILURE;
     }
 
@@ -219,7 +212,7 @@ notmuch_count_command (notmuch_config_t *config, int argc, char *argv[])
        return EXIT_FAILURE;
     }
 
-    if (exclude == EXCLUDE_TRUE) {
+    if (exclude) {
        search_exclude_tags = notmuch_config_get_search_exclude_tags
            (config, &search_exclude_tags_length);
     }
index 5cc3b2f62b46530e5dbb3f63b5d127043fc9c610..ef2f02dfeb5cb5cc470da3462f75d6122f488f95 100644 (file)
@@ -97,7 +97,7 @@ dump_properties_message (void *ctx,
 {
     const char *message_id;
     notmuch_message_properties_t *list;
-    notmuch_bool_t first = TRUE;
+    bool first = true;
 
     message_id = notmuch_message_get_message_id (message);
 
@@ -106,7 +106,7 @@ dump_properties_message (void *ctx,
        return 0;
     }
 
-    for (list = notmuch_message_get_properties (message, "", FALSE);
+    for (list = notmuch_message_get_properties (message, "", false);
         notmuch_message_properties_valid (list); notmuch_message_properties_move_to_next (list)) {
        const char *key, *val;
 
@@ -116,7 +116,7 @@ dump_properties_message (void *ctx,
                return 1;
            }
            gzprintf (output, "#= %s", *buffer_p);
-           first = FALSE;
+           first = false;
        }
 
        key = notmuch_message_properties_key (list);
@@ -277,7 +277,7 @@ notmuch_database_dump (notmuch_database_t *notmuch,
                       const char *query_str,
                       dump_format_t output_format,
                       dump_include_t include,
-                      notmuch_bool_t gzip_output)
+                      bool gzip_output)
 {
     gzFile output = NULL;
     const char *mode = gzip_output ? "w9" : "wT";
@@ -369,26 +369,26 @@ notmuch_dump_command (notmuch_config_t *config, int argc, char *argv[])
 
     notmuch_exit_if_unmatched_db_uuid (notmuch);
 
-    char *output_file_name = NULL;
+    const char *output_file_name = NULL;
     int opt_index;
 
     int output_format = DUMP_FORMAT_BATCH_TAG;
     int include = 0;
-    notmuch_bool_t gzip_output = 0;
+    bool gzip_output = 0;
 
     notmuch_opt_desc_t options[] = {
-       { NOTMUCH_OPT_KEYWORD, &output_format, "format", 'f',
+       { .opt_keyword = &output_format, .name = "format", .keywords =
          (notmuch_keyword_t []){ { "sup", DUMP_FORMAT_SUP },
                                  { "batch-tag", DUMP_FORMAT_BATCH_TAG },
                                  { 0, 0 } } },
-       { NOTMUCH_OPT_KEYWORD_FLAGS, &include, "include", 'I',
+       { .opt_flags = &include, .name = "include", .keywords =
          (notmuch_keyword_t []){ { "config", DUMP_INCLUDE_CONFIG },
                                  { "properties", DUMP_INCLUDE_PROPERTIES },
                                  { "tags", DUMP_INCLUDE_TAGS} } },
-       { 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 }
+       { .opt_string = &output_file_name, .name = "output" },
+       { .opt_bool = &gzip_output, .name = "gzip" },
+       { .opt_inherit = notmuch_shared_options },
+       { }
     };
 
     opt_index = parse_arguments (argc, argv, options, 1);
index bc96af0e881c4ec2b2dac8130bf9aea22588fb81..4d6b0a7fba2feb66d7d8ae3de5ca77cf5aa0bfce 100644 (file)
@@ -27,6 +27,7 @@
 #include <sys/types.h>
 #include <sys/stat.h>
 #include <fcntl.h>
+#include "string-util.h"
 
 static volatile sig_atomic_t interrupted;
 
@@ -64,7 +65,7 @@ safe_gethostname (char *hostname, size_t len)
 }
 
 /* Call fsync() on a directory path. */
-static notmuch_bool_t
+static bool
 sync_dir (const char *dir)
 {
     int fd, r;
@@ -72,7 +73,7 @@ sync_dir (const char *dir)
     fd = open (dir, O_RDONLY);
     if (fd == -1) {
        fprintf (stderr, "Error: open %s: %s\n", dir, strerror (errno));
-       return FALSE;
+       return false;
     }
 
     r = fsync (fd);
@@ -87,29 +88,29 @@ sync_dir (const char *dir)
 /*
  * Check the specified folder name does not contain a directory
  * component ".." to prevent writes outside of the Maildir
- * hierarchy. Return TRUE on valid folder name, FALSE otherwise.
+ * hierarchy. Return true on valid folder name, false otherwise.
  */
-static notmuch_bool_t
+static bool
 is_valid_folder_name (const char *folder)
 {
     const char *p = folder;
 
     for (;;) {
        if ((p[0] == '.') && (p[1] == '.') && (p[2] == '\0' || p[2] == '/'))
-           return FALSE;
+           return false;
        p = strchr (p, '/');
        if (!p)
-           return TRUE;
+           return true;
        p++;
     }
 }
 
 /*
  * Make the given directory and its parents as necessary, using the
- * given mode. Return TRUE on success, FALSE otherwise. Partial
+ * given mode. Return true on success, false otherwise. Partial
  * results are not cleaned up on errors.
  */
-static notmuch_bool_t
+static bool
 mkdir_recursive (const void *ctx, const char *path, int mode)
 {
     struct stat st;
@@ -122,13 +123,13 @@ mkdir_recursive (const void *ctx, const char *path, int mode)
         if (! S_ISDIR (st.st_mode)) {
            fprintf (stderr, "Error: '%s' is not a directory: %s\n",
                     path, strerror (EEXIST));
-           return FALSE;
+           return false;
        }
 
-       return TRUE;
+       return true;
     } else if (errno != ENOENT) {
        fprintf (stderr, "Error: stat '%s': %s\n", path, strerror (errno));
-       return FALSE;
+       return false;
     }
 
     /* mkdir parents, if any */
@@ -137,27 +138,27 @@ mkdir_recursive (const void *ctx, const char *path, int mode)
        parent = talloc_strndup (ctx, path, slash - path);
        if (! parent) {
            fprintf (stderr, "Error: %s\n", strerror (ENOMEM));
-           return FALSE;
+           return false;
        }
 
        if (! mkdir_recursive (ctx, parent, mode))
-           return FALSE;
+           return false;
     }
 
     if (mkdir (path, mode)) {
        fprintf (stderr, "Error: mkdir '%s': %s\n", path, strerror (errno));
-       return FALSE;
+       return false;
     }
 
-    return parent ? sync_dir (parent) : TRUE;
+    return parent ? sync_dir (parent) : true;
 }
 
 /*
  * Create the given maildir folder, i.e. maildir and its
- * subdirectories cur/new/tmp. Return TRUE on success, FALSE
+ * subdirectories cur/new/tmp. Return true on success, false
  * otherwise. Partial results are not cleaned up on errors.
  */
-static notmuch_bool_t
+static bool
 maildir_create_folder (const void *ctx, const char *maildir)
 {
     const char *subdirs[] = { "cur", "new", "tmp" };
@@ -169,14 +170,14 @@ maildir_create_folder (const void *ctx, const char *maildir)
        subdir = talloc_asprintf (ctx, "%s/%s", maildir, subdirs[i]);
        if (! subdir) {
            fprintf (stderr, "Error: %s\n", strerror (ENOMEM));
-           return FALSE;
+           return false;
        }
 
        if (! mkdir_recursive (ctx, subdir, mode))
-           return FALSE;
+           return false;
     }
 
-    return TRUE;
+    return true;
 }
 
 /*
@@ -240,13 +241,13 @@ maildir_mktemp (const void *ctx, const char *maildir, char **path_out)
 }
 
 /*
- * Copy fdin to fdout, return TRUE on success, and FALSE on errors and
+ * Copy fdin to fdout, return true on success, and false on errors and
  * empty input.
  */
-static notmuch_bool_t
+static bool
 copy_fd (int fdout, int fdin)
 {
-    notmuch_bool_t empty = TRUE;
+    bool empty = true;
 
     while (! interrupted) {
        ssize_t remain;
@@ -261,7 +262,7 @@ copy_fd (int fdout, int fdin)
                continue;
            fprintf (stderr, "Error: reading from standard input: %s\n",
                     strerror (errno));
-           return FALSE;
+           return false;
        }
 
        p = buf;
@@ -272,11 +273,11 @@ copy_fd (int fdout, int fdin)
            if (written <= 0) {
                fprintf (stderr, "Error: writing to temporary file: %s",
                         strerror (errno));
-               return FALSE;
+               return false;
            }
            p += written;
            remain -= written;
-           empty = FALSE;
+           empty = false;
        } while (remain > 0);
     }
 
@@ -366,24 +367,25 @@ FAIL:
 
 /*
  * Add the specified message file to the notmuch database, applying
- * tags in tag_ops. If synchronize_flags is TRUE, the tags are
+ * tags in tag_ops. If synchronize_flags is true, the tags are
  * synchronized to maildir flags (which may result in message file
  * rename).
  *
  * Return NOTMUCH_STATUS_SUCCESS on success, errors otherwise. If keep
- * is TRUE, errors in tag changes and flag syncing are ignored and
+ * is true, errors in tag changes and flag syncing are ignored and
  * success status is returned; otherwise such errors cause the message
  * to be removed from the database. Failure to add the message to the
  * database results in error status regardless of keep.
  */
 static notmuch_status_t
 add_file (notmuch_database_t *notmuch, const char *path, tag_op_list_t *tag_ops,
-         notmuch_bool_t synchronize_flags, notmuch_bool_t keep)
+         bool synchronize_flags, bool keep,
+         notmuch_indexopts_t *indexopts)
 {
     notmuch_message_t *message;
     notmuch_status_t status;
 
-    status = notmuch_database_add_message (notmuch, path, &message);
+    status = notmuch_database_index_file (notmuch, path, indexopts, &message);
     if (status == NOTMUCH_STATUS_SUCCESS) {
        status = tag_op_list_apply (message, tag_ops, 0);
        if (status) {
@@ -451,23 +453,24 @@ notmuch_insert_command (notmuch_config_t *config, int argc, char *argv[])
     size_t new_tags_length;
     tag_op_list_t *tag_ops;
     char *query_string = NULL;
-    const char *folder = NULL;
-    notmuch_bool_t create_folder = FALSE;
-    notmuch_bool_t keep = FALSE;
-    notmuch_bool_t no_hooks = FALSE;
-    notmuch_bool_t synchronize_flags;
-    const char *maildir;
+    const char *folder = "";
+    bool create_folder = false;
+    bool keep = false;
+    bool no_hooks = false;
+    bool synchronize_flags;
+    char *maildir;
     char *newpath;
     int opt_index;
     unsigned int i;
 
     notmuch_opt_desc_t options[] = {
-       { NOTMUCH_OPT_STRING, &folder, "folder", 0, 0 },
-       { 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 }
+       { .opt_string = &folder, .name = "folder" },
+       { .opt_bool = &create_folder, .name = "create-folder" },
+       { .opt_bool = &keep, .name = "keep" },
+       { .opt_bool =  &no_hooks, .name = "no-hooks" },
+       { .opt_inherit = notmuch_shared_indexing_options },
+       { .opt_inherit = notmuch_shared_options },
+       { }
     };
 
     opt_index = parse_arguments (argc, argv, options, 1);
@@ -488,14 +491,14 @@ notmuch_insert_command (notmuch_config_t *config, int argc, char *argv[])
     for (i = 0; i < new_tags_length; i++) {
        const char *error_msg;
 
-       error_msg = illegal_tag (new_tags[i], FALSE);
+       error_msg = illegal_tag (new_tags[i], false);
        if (error_msg) {
            fprintf (stderr, "Error: tag '%s' in new.tags: %s\n",
                     new_tags[i],  error_msg);
            return EXIT_FAILURE;
        }
 
-       if (tag_op_list_append (tag_ops, new_tags[i], FALSE))
+       if (tag_op_list_append (tag_ops, new_tags[i], false))
            return EXIT_FAILURE;
     }
 
@@ -508,22 +511,21 @@ notmuch_insert_command (notmuch_config_t *config, int argc, char *argv[])
        return EXIT_FAILURE;
     }
 
-    if (folder == NULL) {
-       maildir = db_path;
-    } else {
-       if (! is_valid_folder_name (folder)) {
-           fprintf (stderr, "Error: invalid folder name: '%s'\n", folder);
-           return EXIT_FAILURE;
-       }
-       maildir = talloc_asprintf (config, "%s/%s", db_path, folder);
-       if (! maildir) {
-           fprintf (stderr, "Out of memory\n");
-           return EXIT_FAILURE;
-       }
-       if (create_folder && ! maildir_create_folder (config, maildir))
-           return EXIT_FAILURE;
+    if (! is_valid_folder_name (folder)) {
+       fprintf (stderr, "Error: invalid folder name: '%s'\n", folder);
+       return EXIT_FAILURE;
     }
 
+    maildir = talloc_asprintf (config, "%s/%s", db_path, folder);
+    if (! maildir) {
+       fprintf (stderr, "Out of memory\n");
+       return EXIT_FAILURE;
+    }
+
+    strip_trailing (maildir, '/');
+    if (create_folder && ! maildir_create_folder (config, maildir))
+       return EXIT_FAILURE;
+
     /* 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));
@@ -545,9 +547,15 @@ notmuch_insert_command (notmuch_config_t *config, int argc, char *argv[])
 
     notmuch_exit_if_unmatched_db_uuid (notmuch);
 
+    status = notmuch_process_shared_indexing_options (notmuch, config);
+    if (status != NOTMUCH_STATUS_SUCCESS) {
+       fprintf (stderr, "Error: Failed to process index options. (%s)\n",
+                notmuch_status_to_string (status));
+       return EXIT_FAILURE;
+    }
 
     /* Index the message. */
-    status = add_file (notmuch, newpath, tag_ops, synchronize_flags, keep);
+    status = add_file (notmuch, newpath, tag_ops, synchronize_flags, keep, indexing_cli_choices.opts);
 
     /* Commit changes. */
     close_status = notmuch_database_destroy (notmuch);
index 3a60f7cae47fee568d8002f5db178b6ea7587d7a..fb021b18c4a29cea7a75e9256c5bda4999f80403 100644 (file)
@@ -44,7 +44,7 @@ enum verbosity {
 typedef struct {
     int output_is_a_tty;
     enum verbosity verbosity;
-    notmuch_bool_t debug;
+    bool debug;
     const char **new_tags;
     size_t new_tags_length;
     const char **new_ignore;
@@ -60,7 +60,7 @@ typedef struct {
     _filename_list_t *removed_directories;
     _filename_list_t *directory_mtimes;
 
-    notmuch_bool_t synchronize_flags;
+    bool synchronize_flags;
 } add_files_state_t;
 
 static volatile sig_atomic_t do_print_progress = 0;
@@ -234,18 +234,24 @@ _entries_resemble_maildir (const char *path, struct dirent **entries, int count)
     return 0;
 }
 
+static bool
+_special_directory (const char *entry)
+{
+    return strcmp (entry, ".") == 0 || strcmp (entry, "..") == 0;
+}
+
 /* Test if the file/directory is to be ignored.
  */
-static notmuch_bool_t
+static bool
 _entry_in_ignore_list (const char *entry, add_files_state_t *state)
 {
     size_t i;
 
     for (i = 0; i < state->new_ignore_length; i++)
        if (strcmp (entry, state->new_ignore[i]) == 0)
-           return TRUE;
+           return true;
 
-    return FALSE;
+    return false;
 }
 
 /* Add a single file to the database. */
@@ -261,16 +267,22 @@ add_file (notmuch_database_t *notmuch, const char *filename,
     if (status)
        goto DONE;
 
-    status = notmuch_database_add_message (notmuch, filename, &message);
+    status = notmuch_database_index_file (notmuch, filename, indexing_cli_choices.opts, &message);
     switch (status) {
     /* Success. */
     case NOTMUCH_STATUS_SUCCESS:
        state->added_messages++;
        notmuch_message_freeze (message);
-       for (tag = state->new_tags; *tag != NULL; tag++)
-           notmuch_message_add_tag (message, *tag);
        if (state->synchronize_flags)
            notmuch_message_maildir_flags_to_tags (message);
+
+       for (tag = state->new_tags; *tag != NULL; tag++) {
+           if (strcmp ("unread", *tag) !=0 ||
+               !notmuch_message_has_maildir_flag (message, 'S')) {
+               notmuch_message_add_tag (message, *tag);
+           }
+       }
+
        notmuch_message_thaw (message);
        break;
     /* Non-fatal issues (go on to next file). */
@@ -291,8 +303,7 @@ add_file (notmuch_database_t *notmuch, const char *filename,
     case NOTMUCH_STATUS_READ_ONLY_DATABASE:
     case NOTMUCH_STATUS_XAPIAN_EXCEPTION:
     case NOTMUCH_STATUS_OUT_OF_MEMORY:
-       fprintf (stderr, "Error: %s. Halting processing.\n",
-                notmuch_status_to_string (status));
+       (void) print_status_database("add_file", notmuch, status);
        goto DONE;
     default:
        INTERNAL_ERROR ("add_message returned unexpected value: %d", status);
@@ -365,7 +376,7 @@ add_files (notmuch_database_t *notmuch,
     notmuch_filenames_t *db_subdirs = NULL;
     time_t stat_time;
     struct stat st;
-    notmuch_bool_t is_maildir;
+    bool is_maildir;
 
     if (stat (path, &st)) {
        fprintf (stderr, "Error reading directory %s: %s\n",
@@ -438,12 +449,13 @@ add_files (notmuch_database_t *notmuch,
     /* Pass 1: Recurse into all sub-directories. */
     is_maildir = _entries_resemble_maildir (path, fs_entries, num_fs_entries);
 
-    for (i = 0; i < num_fs_entries; i++) {
-       if (interrupted)
-           break;
-
+    for (i = 0; i < num_fs_entries && ! interrupted; i++) {
        entry = fs_entries[i];
 
+       /* Ignore special directories to avoid infinite recursion. */
+       if (_special_directory (entry->d_name))
+           continue;
+
        /* Ignore any files/directories the user has configured to
         * ignore.  We do this before dirent_type both for performance
         * and because we don't care if dirent_type fails on entries
@@ -469,13 +481,10 @@ add_files (notmuch_database_t *notmuch,
            continue;
        }
 
-       /* Ignore special directories to avoid infinite recursion.
-        * Also ignore the .notmuch directory and any "tmp" directory
+       /* Ignore the .notmuch directory and any "tmp" directory
         * that appears within a maildir.
         */
-       if (strcmp (entry->d_name, ".") == 0 ||
-           strcmp (entry->d_name, "..") == 0 ||
-           (is_maildir && strcmp (entry->d_name, "tmp") == 0) ||
+       if ((is_maildir && strcmp (entry->d_name, "tmp") == 0) ||
            strcmp (entry->d_name, ".notmuch") == 0)
            continue;
 
@@ -509,13 +518,13 @@ add_files (notmuch_database_t *notmuch,
     }
 
     /* Pass 2: Scan for new files, removed files, and removed directories. */
-    for (i = 0; i < num_fs_entries; i++)
-    {
-       if (interrupted)
-           break;
-
+    for (i = 0; i < num_fs_entries && ! interrupted; i++) {
         entry = fs_entries[i];
 
+       /* Ignore special directories early. */
+       if (_special_directory (entry->d_name))
+           continue;
+
        /* Ignore files & directories user has configured to be ignored */
        if (_entry_in_ignore_list (entry->d_name, state)) {
            if (state->debug)
@@ -740,8 +749,7 @@ count_files (const char *path, int *count, add_files_state_t *state)
        /* Ignore special directories to avoid infinite recursion.
         * Also ignore the .notmuch directory.
         */
-       if (strcmp (entry->d_name, ".") == 0 ||
-           strcmp (entry->d_name, "..") == 0 ||
+       if (_special_directory (entry->d_name) ||
            strcmp (entry->d_name, ".notmuch") == 0)
            continue;
 
@@ -829,7 +837,7 @@ remove_filename (notmuch_database_t *notmuch,
     status = notmuch_database_remove_message (notmuch, path);
     if (status == NOTMUCH_STATUS_DUPLICATE_MESSAGE_ID) {
        add_files_state->renamed_messages++;
-       if (add_files_state->synchronize_flags == TRUE)
+       if (add_files_state->synchronize_flags == true)
            notmuch_message_maildir_flags_to_tags (message);
        status = NOTMUCH_STATUS_SUCCESS;
     } else if (status == NOTMUCH_STATUS_SUCCESS) {
@@ -933,7 +941,7 @@ notmuch_new_command (notmuch_config_t *config, int argc, char *argv[])
     notmuch_database_t *notmuch;
     add_files_state_t add_files_state = {
        .verbosity = VERBOSITY_NORMAL,
-       .debug = FALSE,
+       .debug = false,
        .output_is_a_tty = isatty (fileno (stdout)),
     };
     struct timeval tv_start;
@@ -945,18 +953,19 @@ notmuch_new_command (notmuch_config_t *config, int argc, char *argv[])
     _filename_node_t *f;
     int opt_index;
     unsigned int i;
-    notmuch_bool_t timer_is_active = FALSE;
-    notmuch_bool_t no_hooks = FALSE;
-    notmuch_bool_t quiet = FALSE, verbose = FALSE;
+    bool timer_is_active = false;
+    bool no_hooks = false;
+    bool quiet = false, verbose = false;
     notmuch_status_t status;
 
     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 }
+       { .opt_bool = &quiet, .name = "quiet" },
+       { .opt_bool = &verbose, .name = "verbose" },
+       { .opt_bool = &add_files_state.debug, .name = "debug" },
+       { .opt_bool = &no_hooks, .name = "no-hooks" },
+       { .opt_inherit = notmuch_shared_indexing_options },
+       { .opt_inherit = notmuch_shared_options },
+       { }
     };
 
     opt_index = parse_arguments (argc, argv, options, 1);
@@ -979,7 +988,7 @@ notmuch_new_command (notmuch_config_t *config, int argc, char *argv[])
     for (i = 0; i < add_files_state.new_tags_length; i++) {
        const char *error_msg;
 
-       error_msg = illegal_tag (add_files_state.new_tags[i], FALSE);
+       error_msg = illegal_tag (add_files_state.new_tags[i], false);
        if (error_msg) {
            fprintf (stderr, "Error: tag '%s' in new.tags: %s\n",
                     add_files_state.new_tags[i], error_msg);
@@ -1046,7 +1055,7 @@ notmuch_new_command (notmuch_config_t *config, int argc, char *argv[])
            }
 
            if (notmuch_database_dump (notmuch, backup_name, "",
-                                      DUMP_FORMAT_BATCH_TAG, DUMP_INCLUDE_DEFAULT, TRUE)) {
+                                      DUMP_FORMAT_BATCH_TAG, DUMP_INCLUDE_DEFAULT, true)) {
                fprintf (stderr, "Backup failed. Aborting upgrade.");
                return EXIT_FAILURE;
            }
@@ -1072,6 +1081,13 @@ notmuch_new_command (notmuch_config_t *config, int argc, char *argv[])
     if (notmuch == NULL)
        return EXIT_FAILURE;
 
+    status = notmuch_process_shared_indexing_options (notmuch, config);
+    if (status != NOTMUCH_STATUS_SUCCESS) {
+       fprintf (stderr, "Error: Failed to process index options. (%s)\n",
+                notmuch_status_to_string (status));
+       return EXIT_FAILURE;
+    }
+
     /* Set up our handler for SIGINT. We do this after having
      * potentially done a database upgrade we this interrupt handler
      * won't support. */
@@ -1093,7 +1109,7 @@ notmuch_new_command (notmuch_config_t *config, int argc, char *argv[])
     if (add_files_state.verbosity == VERBOSITY_NORMAL &&
        add_files_state.output_is_a_tty && ! debugger_is_active ()) {
        setup_progress_printing_timer ();
-       timer_is_active = TRUE;
+       timer_is_active = true;
     }
 
     ret = add_files (notmuch, db_path, &add_files_state);
diff --git a/notmuch-reindex.c b/notmuch-reindex.c
new file mode 100644 (file)
index 0000000..5d70251
--- /dev/null
@@ -0,0 +1,142 @@
+/* notmuch - Not much of an email program, (just index and search)
+ *
+ * Copyright © 2016 Daniel Kahn Gillmor
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see http://www.gnu.org/licenses/ .
+ *
+ * Author: Daniel Kahn Gillmor <dkg@fifthhorseman.net>
+ */
+
+#include "notmuch-client.h"
+#include "string-util.h"
+
+static volatile sig_atomic_t interrupted;
+
+static void
+handle_sigint (unused (int sig))
+{
+    static char msg[] = "Stopping...         \n";
+
+    /* This write is "opportunistic", so it's okay to ignore the
+     * result.  It is not required for correctness, and if it does
+     * fail or produce a short write, we want to get out of the signal
+     * handler as quickly as possible, not retry it. */
+    IGNORE_RESULT (write (2, msg, sizeof (msg) - 1));
+    interrupted = 1;
+}
+
+/* reindex all messages matching 'query_string' using the passed-in indexopts
+ */
+static int
+reindex_query (notmuch_database_t *notmuch, const char *query_string,
+              notmuch_indexopts_t *indexopts)
+{
+    notmuch_query_t *query;
+    notmuch_messages_t *messages;
+    notmuch_message_t *message;
+    notmuch_status_t status;
+
+    notmuch_status_t ret = NOTMUCH_STATUS_SUCCESS;
+
+    query = notmuch_query_create (notmuch, query_string);
+    if (query == NULL) {
+       fprintf (stderr, "Out of memory.\n");
+       return 1;
+    }
+
+    /* reindexing is not interested in any special sort order */
+    notmuch_query_set_sort (query, NOTMUCH_SORT_UNSORTED);
+
+    status = notmuch_query_search_messages (query, &messages);
+    if (print_status_query ("notmuch reindex", query, status))
+       return status;
+
+    ret = notmuch_database_begin_atomic (notmuch);
+    for (;
+        notmuch_messages_valid (messages) && ! interrupted;
+        notmuch_messages_move_to_next (messages)) {
+       message = notmuch_messages_get (messages);
+
+       ret = notmuch_message_reindex(message, indexopts);
+       if (ret != NOTMUCH_STATUS_SUCCESS)
+           break;
+    }
+
+    if (!ret)
+       ret = notmuch_database_end_atomic (notmuch);
+
+    notmuch_query_destroy (query);
+
+    return ret || interrupted;
+}
+
+int
+notmuch_reindex_command (notmuch_config_t *config, int argc, char *argv[])
+{
+    char *query_string = NULL;
+    notmuch_database_t *notmuch;
+    struct sigaction action;
+    int opt_index;
+    int ret;
+    notmuch_status_t status;
+
+    /* Set up our handler for SIGINT */
+    memset (&action, 0, sizeof (struct sigaction));
+    action.sa_handler = handle_sigint;
+    sigemptyset (&action.sa_mask);
+    action.sa_flags = SA_RESTART;
+    sigaction (SIGINT, &action, NULL);
+
+    notmuch_opt_desc_t options[] = {
+       { .opt_inherit = notmuch_shared_indexing_options },
+       { .opt_inherit = notmuch_shared_options },
+       { }
+    };
+
+    opt_index = parse_arguments (argc, argv, options, 1);
+    if (opt_index < 0)
+       return EXIT_FAILURE;
+
+    notmuch_process_shared_options (argv[0]);
+
+    if (notmuch_database_open (notmuch_config_get_database_path (config),
+                              NOTMUCH_DATABASE_MODE_READ_WRITE, &notmuch))
+       return EXIT_FAILURE;
+
+    notmuch_exit_if_unmatched_db_uuid (notmuch);
+
+    status = notmuch_process_shared_indexing_options (notmuch, config);
+    if (status != NOTMUCH_STATUS_SUCCESS) {
+       fprintf (stderr, "Error: Failed to process index options. (%s)\n",
+                notmuch_status_to_string (status));
+       return EXIT_FAILURE;
+    }
+
+    query_string = query_string_from_args (config, argc-opt_index, argv+opt_index);
+    if (query_string == NULL) {
+       fprintf (stderr, "Out of memory\n");
+       return EXIT_FAILURE;
+    }
+
+    if (*query_string == '\0') {
+       fprintf (stderr, "Error: notmuch reindex requires at least one search term.\n");
+       return EXIT_FAILURE;
+    }
+    
+    ret = reindex_query (notmuch, query_string, indexing_cli_choices.opts);
+
+    notmuch_database_destroy (notmuch);
+
+    return ret || interrupted ? EXIT_FAILURE : EXIT_SUCCESS;
+}
index 40201b805369307727507bb2a6a4b206af25d7a8..2c7cc4eba674c7cd761a5532ccb67dd55ad8a2b9 100644 (file)
@@ -93,7 +93,7 @@ typedef enum {
 } address_match_t;
 
 /* Match given string against given address according to mode. */
-static notmuch_bool_t
+static bool
 match_address (const char *str, const char *address, address_match_t mode)
 {
     switch (mode) {
@@ -105,7 +105,7 @@ match_address (const char *str, const char *address, address_match_t mode)
        return strcasecmp (address, str) == 0;
     }
 
-    return FALSE;
+    return false;
 }
 
 /* Match given string against user's configured "primary" and "other"
@@ -153,7 +153,7 @@ string_in_user_address (const char *str, notmuch_config_t *config)
 
 /* Is the given address configured as one of the user's "primary" or
  * "other" addresses. */
-static notmuch_bool_t
+static bool
 address_is_users (const char *address, notmuch_config_t *config)
 {
     return address_match (address, config, STRING_IS_USER_ADDRESS) != NULL;
@@ -221,7 +221,7 @@ scan_address_list (InternetAddressList *list,
 /* Does the address in the Reply-To header of 'message' already appear
  * in either the 'To' or 'Cc' header of the message?
  */
-static notmuch_bool_t
+static bool
 reply_to_header_is_redundant (GMimeMessage *message,
                              InternetAddressList *reply_to_list)
 {
@@ -229,7 +229,7 @@ reply_to_header_is_redundant (GMimeMessage *message,
     InternetAddress *address;
     InternetAddressMailbox *mailbox;
     InternetAddressList *recipients;
-    notmuch_bool_t ret = FALSE;
+    bool ret = false;
     int i;
 
     if (reply_to_list == NULL ||
@@ -253,7 +253,7 @@ reply_to_header_is_redundant (GMimeMessage *message,
        mailbox = INTERNET_ADDRESS_MAILBOX (address);
        addr = internet_address_mailbox_get_addr (mailbox);
        if (strcmp (addr, reply_to) == 0) {
-           ret = TRUE;
+           ret = true;
            break;
        }
     }
@@ -323,7 +323,7 @@ static const char *
 add_recipients_from_message (GMimeMessage *reply,
                             notmuch_config_t *config,
                             GMimeMessage *message,
-                            notmuch_bool_t reply_all)
+                            bool reply_all)
 {
 
     /* There is a memory leak here with gmime-2.6 because get_sender
@@ -522,8 +522,8 @@ create_reply_message(void *ctx,
                     notmuch_config_t *config,
                     notmuch_message_t *message,
                     GMimeMessage *mime_message,
-                    notmuch_bool_t reply_all,
-                    notmuch_bool_t limited)
+                    bool reply_all,
+                    bool limited)
 {
     const char *subject, *from_addr = NULL;
     const char *in_reply_to, *orig_references, *references;
@@ -612,7 +612,7 @@ static int do_reply(notmuch_config_t *config,
                    notmuch_query_t *query,
                    notmuch_show_params_t *params,
                    int format,
-                   notmuch_bool_t reply_all)
+                   bool reply_all)
 {
     GMimeMessage *reply;
     mime_node_t *node;
@@ -663,11 +663,11 @@ static int do_reply(notmuch_config_t *config,
 
            /* The headers of the reply message we've created */
            sp->map_key (sp, "reply-headers");
-           format_headers_sprinter (sp, reply, TRUE);
+           format_headers_sprinter (sp, reply, true);
 
            /* Start the original */
            sp->map_key (sp, "original");
-           format_part_sprinter (config, sp, node, TRUE, TRUE, FALSE);
+           format_part_sprinter (config, sp, node, true, false);
 
            /* End */
            sp->end (sp);
@@ -702,23 +702,23 @@ notmuch_reply_command (notmuch_config_t *config, int argc, char *argv[])
        .part = -1,
     };
     int format = FORMAT_DEFAULT;
-    int reply_all = TRUE;
+    int reply_all = true;
 
     notmuch_opt_desc_t options[] = {
-       { NOTMUCH_OPT_KEYWORD, &format, "format", 'f',
+       { .opt_keyword = &format, .name = "format", .keywords =
          (notmuch_keyword_t []){ { "default", FORMAT_DEFAULT },
                                  { "json", FORMAT_JSON },
                                  { "sexp", FORMAT_SEXP },
                                  { "headers-only", FORMAT_HEADERS_ONLY },
                                  { 0, 0 } } },
-       { NOTMUCH_OPT_INT, &notmuch_format_version, "format-version", 0, 0 },
-       { NOTMUCH_OPT_KEYWORD, &reply_all, "reply-to", 'r',
-         (notmuch_keyword_t []){ { "all", TRUE },
-                                 { "sender", FALSE },
+       { .opt_int = &notmuch_format_version, .name = "format-version" },
+       { .opt_keyword = &reply_all, .name = "reply-to", .keywords =
+         (notmuch_keyword_t []){ { "all", true },
+                                 { "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 }
+       { .opt_bool = &params.crypto.decrypt, .name = "decrypt" },
+       { .opt_inherit = notmuch_shared_options },
+       { }
     };
 
     opt_index = parse_arguments (argc, argv, options, 1);
@@ -759,7 +759,7 @@ notmuch_reply_command (notmuch_config_t *config, int argc, char *argv[])
     if (do_reply (config, query, &params, format, reply_all) != 0)
        return EXIT_FAILURE;
 
-    notmuch_crypto_cleanup (&params.crypto);
+    _notmuch_crypto_cleanup (&params.crypto);
     notmuch_query_destroy (query);
     notmuch_database_destroy (notmuch);
 
index d6429efb404519fd23dfaf6c1450c22b62782937..dee19c206d13b9ceb415a582e99bd747cd95d01b 100644 (file)
@@ -211,7 +211,7 @@ parse_sup_line (void *ctx, char *line,
            tok_len++;
        }
 
-       if (tag_op_list_append (tag_ops, tok, FALSE))
+       if (tag_op_list_append (tag_ops, tok, false))
            return -1;
     }
 
@@ -223,11 +223,11 @@ int
 notmuch_restore_command (notmuch_config_t *config, int argc, char *argv[])
 {
     notmuch_database_t *notmuch;
-    notmuch_bool_t accumulate = FALSE;
+    bool accumulate = false;
     tag_op_flag_t flags = 0;
     tag_op_list_t *tag_ops;
 
-    char *input_file_name = NULL;
+    const char *input_file_name = NULL;
     const char *name_for_error = NULL;
     gzFile input = NULL;
     char *line = NULL;
@@ -247,20 +247,20 @@ notmuch_restore_command (notmuch_config_t *config, int argc, char *argv[])
        flags |= TAG_FLAG_MAILDIR_SYNC;
 
     notmuch_opt_desc_t options[] = {
-       { NOTMUCH_OPT_KEYWORD, &input_format, "format", 'f',
+       { .opt_keyword = &input_format, .name = "format", .keywords =
          (notmuch_keyword_t []){ { "auto", DUMP_FORMAT_AUTO },
                                  { "batch-tag", DUMP_FORMAT_BATCH_TAG },
                                  { "sup", DUMP_FORMAT_SUP },
                                  { 0, 0 } } },
-       { NOTMUCH_OPT_KEYWORD_FLAGS, &include, "include", 'I',
+       { .opt_flags = &include, .name = "include", .keywords =
          (notmuch_keyword_t []){ { "config", DUMP_INCLUDE_CONFIG },
                                  { "properties", DUMP_INCLUDE_PROPERTIES },
                                  { "tags", DUMP_INCLUDE_TAGS} } },
 
-       { 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 }
+       { .opt_string = &input_file_name, .name = "input" },
+       { .opt_bool = &accumulate, .name = "accumulate" },
+       { .opt_inherit = notmuch_shared_options },
+       { }
     };
 
     opt_index = parse_arguments (argc, argv, options, 1);
index 019e14eea846d6a68fe36a0210318bcd35d84063..0abac08eb7ab3a2720fdeb7d3b67717dd833039f 100644 (file)
@@ -51,17 +51,17 @@ typedef enum {
 
 typedef struct {
     notmuch_database_t *notmuch;
-    format_sel_t format_sel;
+    int format_sel;
     sprinter_t *format;
-    notmuch_exclude_t exclude;
+    int exclude;
     notmuch_query_t *query;
-    notmuch_sort_t sort;
-    output_t output;
+    int sort;
+    int output;
     int offset;
     int limit;
     int dupe;
     GHashTable *addresses;
-    dedup_t dedup;
+    int dedup;
 } search_context_t;
 
 typedef struct {
@@ -160,9 +160,10 @@ do_search_threads (search_context_t *ctx)
            const char *subject = notmuch_thread_get_subject (thread);
            const char *thread_id = notmuch_thread_get_thread_id (thread);
            int matched = notmuch_thread_get_matched_messages (thread);
+           int files = notmuch_thread_get_total_files (thread);
            int total = notmuch_thread_get_total_messages (thread);
            const char *relative_date = NULL;
-           notmuch_bool_t first_tag = TRUE;
+           bool first_tag = true;
 
            format->begin_map (format);
 
@@ -175,13 +176,23 @@ do_search_threads (search_context_t *ctx)
 
            if (format->is_text_printer) {
                 /* Special case for the text formatter */
-               printf ("thread:%s %12s [%d/%d] %s; %s (",
+               printf ("thread:%s %12s ",
                        thread_id,
-                       relative_date,
+                       relative_date);
+               if (total == files)
+                   printf ("[%d/%d] %s; %s (",
                        matched,
                        total,
                        sanitize_string (ctx_quote, authors),
                        sanitize_string (ctx_quote, subject));
+               else
+                   printf ("[%d/%d(%d)] %s; %s (",
+                       matched,
+                       total,
+                       files,
+                       sanitize_string (ctx_quote, authors),
+                       sanitize_string (ctx_quote, subject));
+
            } else { /* Structured Output */
                format->map_key (format, "thread");
                format->string (format, thread_id);
@@ -232,7 +243,7 @@ do_search_threads (search_context_t *ctx)
                if (format->is_text_printer) {
                   /* Special case for the text formatter */
                    if (first_tag)
-                       first_tag = FALSE;
+                       first_tag = false;
                    else
                        fputc (' ', stdout);
                    fputs (tag, stdout);
@@ -284,9 +295,9 @@ static int mailbox_compare (const void *v1, const void *v2)
     return ret;
 }
 
-/* Returns TRUE iff name and addr is duplicate. If not, stores the
+/* 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
+static bool
 is_duplicate (const search_context_t *ctx, const char *name, const char *addr)
 {
     char *key;
@@ -304,12 +315,12 @@ is_duplicate (const search_context_t *ctx, const char *name, const char *addr)
        if (l) {
            mailbox = l->data;
            mailbox->count++;
-           return TRUE;
+           return true;
        }
 
        mailbox = new_mailbox (ctx->format, name, addr);
        if (! mailbox)
-           return FALSE;
+           return false;
 
        /*
         * XXX: It would be more efficient to prepend to the list, but
@@ -320,24 +331,24 @@ is_duplicate (const search_context_t *ctx, const char *name, const char *addr)
        if (list != g_list_append (list, mailbox))
            INTERNAL_ERROR ("appending to list changed list head\n");
 
-       return FALSE;
+       return false;
     }
 
     key = talloc_strdup (ctx->format, addr);
     if (! key)
-       return FALSE;
+       return false;
 
     mailbox = new_mailbox (ctx->format, name, addr);
     if (! mailbox)
-       return FALSE;
+       return false;
 
     list = g_list_append (NULL, mailbox);
     if (! list)
-       return FALSE;
+       return false;
 
     g_hash_table_insert (ctx->addresses, key, list);
 
-    return FALSE;
+    return false;
 }
 
 static void
@@ -352,7 +363,7 @@ print_mailbox (const search_context_t *ctx, const mailbox_t *mailbox)
 
     /* name_addr has the name part quoted if necessary. Compare
      * 'John Doe <john@doe.com>' vs. '"Doe, John" <john@doe.com>' */
-    name_addr = internet_address_to_string (ia, FALSE);
+    name_addr = internet_address_to_string (ia, false);
 
     if (format->is_text_printer) {
        if (ctx->output & OUTPUT_COUNT) {
@@ -775,18 +786,18 @@ static search_context_t search_context = {
 };
 
 static const notmuch_opt_desc_t common_options[] = {
-    { NOTMUCH_OPT_KEYWORD, &search_context.sort, "sort", 's',
+    { .opt_keyword = &search_context.sort, .name = "sort", .keywords =
       (notmuch_keyword_t []){ { "oldest-first", NOTMUCH_SORT_OLDEST_FIRST },
                              { "newest-first", NOTMUCH_SORT_NEWEST_FIRST },
                              { 0, 0 } } },
-    { NOTMUCH_OPT_KEYWORD, &search_context.format_sel, "format", 'f',
+    { .opt_keyword = &search_context.format_sel, .name = "format", .keywords =
       (notmuch_keyword_t []){ { "json", NOTMUCH_FORMAT_JSON },
                              { "sexp", NOTMUCH_FORMAT_SEXP },
                              { "text", NOTMUCH_FORMAT_TEXT },
                              { "text0", NOTMUCH_FORMAT_TEXT0 },
                              { 0, 0 } } },
-    { NOTMUCH_OPT_INT, &notmuch_format_version, "format-version", 0, 0 },
-    { 0, 0, 0, 0, 0 }
+    { .opt_int = &notmuch_format_version, .name = "format-version" },
+    { }
 };
 
 int
@@ -796,25 +807,25 @@ notmuch_search_command (notmuch_config_t *config, int argc, char *argv[])
     int opt_index, ret;
 
     notmuch_opt_desc_t options[] = {
-       { NOTMUCH_OPT_KEYWORD, &ctx->output, "output", 'o',
+       { .opt_keyword = &ctx->output, .name = "output", .keywords =
          (notmuch_keyword_t []){ { "summary", OUTPUT_SUMMARY },
                                  { "threads", OUTPUT_THREADS },
                                  { "messages", OUTPUT_MESSAGES },
                                  { "files", OUTPUT_FILES },
                                  { "tags", OUTPUT_TAGS },
                                  { 0, 0 } } },
-        { NOTMUCH_OPT_KEYWORD, &ctx->exclude, "exclude", 'x',
+        { .opt_keyword = &ctx->exclude, .name = "exclude", .keywords =
           (notmuch_keyword_t []){ { "true", NOTMUCH_EXCLUDE_TRUE },
                                   { "false", NOTMUCH_EXCLUDE_FALSE },
                                   { "flag", NOTMUCH_EXCLUDE_FLAG },
                                   { "all", NOTMUCH_EXCLUDE_ALL },
                                   { 0, 0 } } },
-       { NOTMUCH_OPT_INT, &ctx->offset, "offset", 'O', 0 },
-       { 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 }
+       { .opt_int = &ctx->offset, .name = "offset" },
+       { .opt_int = &ctx->limit, .name = "limit" },
+       { .opt_int = &ctx->dupe, .name = "duplicate" },
+       { .opt_inherit = common_options },
+       { .opt_inherit = notmuch_shared_options },
+       { }
     };
 
     ctx->output = OUTPUT_SUMMARY;
@@ -862,23 +873,23 @@ notmuch_address_command (notmuch_config_t *config, int argc, char *argv[])
     int opt_index, ret;
 
     notmuch_opt_desc_t options[] = {
-       { NOTMUCH_OPT_KEYWORD_FLAGS, &ctx->output, "output", 'o',
+       { .opt_flags = &ctx->output, .name = "output", .keywords =
          (notmuch_keyword_t []){ { "sender", OUTPUT_SENDER },
                                  { "recipients", OUTPUT_RECIPIENTS },
                                  { "count", OUTPUT_COUNT },
                                  { 0, 0 } } },
-       { NOTMUCH_OPT_KEYWORD, &ctx->exclude, "exclude", 'x',
+       { .opt_keyword = &ctx->exclude, .name = "exclude", .keywords =
          (notmuch_keyword_t []){ { "true", NOTMUCH_EXCLUDE_TRUE },
                                  { "false", NOTMUCH_EXCLUDE_FALSE },
                                  { 0, 0 } } },
-       { NOTMUCH_OPT_KEYWORD, &ctx->dedup, "deduplicate", 'D',
+       { .opt_keyword = &ctx->dedup, .name = "deduplicate", .keywords =
          (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 }
+       { .opt_inherit = common_options },
+       { .opt_inherit = notmuch_shared_options },
+       { }
     };
 
     opt_index = parse_arguments (argc, argv, options, 1);
index 9a66810db385cbbb9e64f93d7af4f1953546be44..5304800553cfbe3897e2c8774ba702a30b8b9488 100644 (file)
@@ -187,7 +187,7 @@ notmuch_setup_command (notmuch_config_t *config,
                                             (const char **)
                                             other_emails->pdata,
                                             other_emails->len);
-    g_ptr_array_free (other_emails, TRUE);
+    g_ptr_array_free (other_emails, true);
 
     prompt ("Top-level directory of your email archive [%s]: ",
            notmuch_config_get_database_path (config));
@@ -210,7 +210,7 @@ notmuch_setup_command (notmuch_config_t *config,
        notmuch_config_set_new_tags (config, (const char **) tags->pdata,
                                     tags->len);
 
-       g_ptr_array_free (tags, TRUE);
+       g_ptr_array_free (tags, true);
     }
 
 
@@ -227,7 +227,7 @@ notmuch_setup_command (notmuch_config_t *config,
                                                (const char **) tags->pdata,
                                                tags->len);
 
-       g_ptr_array_free (tags, TRUE);
+       g_ptr_array_free (tags, true);
     }
 
     if (notmuch_config_save (config))
index 74e77249d123816257388d3a993811f37fba682b..7afd39478b0e3aa5665d94b5e2acb6234c6793c0 100644 (file)
@@ -196,7 +196,7 @@ _is_from_line (const char *line)
 
 void
 format_headers_sprinter (sprinter_t *sp, GMimeMessage *message,
-                        notmuch_bool_t reply)
+                        bool reply)
 {
     /* Any changes to the JSON or S-Expression format should be
      * reflected in the file devel/schemata. */
@@ -283,7 +283,7 @@ show_text_part_content (GMimeObject *part, GMimeStream *stream_out,
        return;
 
     stream_filter = g_mime_stream_filter_new (stream_out);
-    crlf_filter = g_mime_filter_crlf_new (FALSE, FALSE);
+    crlf_filter = g_mime_filter_crlf_new (false, false);
     g_mime_stream_filter_add(GMIME_STREAM_FILTER (stream_filter),
                             crlf_filter);
     g_object_unref (crlf_filter);
@@ -305,7 +305,7 @@ show_text_part_content (GMimeObject *part, GMimeStream *stream_out,
 
     if (flags & NOTMUCH_SHOW_TEXT_PART_REPLY) {
        GMimeFilter *reply_filter;
-       reply_filter = g_mime_filter_reply_new (TRUE);
+       reply_filter = g_mime_filter_reply_new (true);
        if (reply_filter) {
            g_mime_stream_filter_add (GMIME_STREAM_FILTER (stream_filter),
                                      reply_filter);
@@ -350,7 +350,7 @@ do_format_signature_errors (sprinter_t *sp, struct key_map_struct *key_map,
     for (unsigned int i = 0; i < array_map_len; i++) {
        if (errors & key_map[i].bit) {
            sp->map_key (sp, key_map[i].string);
-           sp->boolean (sp, TRUE);
+           sp->boolean (sp, true);
        }
     }
 
@@ -490,7 +490,7 @@ format_part_text (const void *ctx, sprinter_t *sp, mime_node_t *node,
     GMimeObject *meta = node->envelope_part ?
        GMIME_OBJECT (node->envelope_part) : node->part;
     GMimeContentType *content_type = g_mime_object_get_content_type (meta);
-    const notmuch_bool_t leaf = GMIME_IS_PART (node->part);
+    const bool leaf = GMIME_IS_PART (node->part);
     GMimeStream *stream = params->out_stream;
     const char *part_type;
     int i;
@@ -603,8 +603,8 @@ format_omitted_part_meta_sprinter (sprinter_t *sp, GMimeObject *meta, GMimePart
 
 void
 format_part_sprinter (const void *ctx, sprinter_t *sp, mime_node_t *node,
-                     notmuch_bool_t first, notmuch_bool_t output_body,
-                     notmuch_bool_t include_html)
+                     bool output_body,
+                     bool include_html)
 {
     /* Any changes to the JSON or S-Expression format should be
      * reflected in the file devel/schemata. */
@@ -614,12 +614,12 @@ format_part_sprinter (const void *ctx, sprinter_t *sp, mime_node_t *node,
        format_message_sprinter (sp, node->envelope_file);
 
        sp->map_key (sp, "headers");
-       format_headers_sprinter (sp, GMIME_MESSAGE (node->part), FALSE);
+       format_headers_sprinter (sp, GMIME_MESSAGE (node->part), false);
 
        if (output_body) {
            sp->map_key (sp, "body");
            sp->begin_list (sp);
-           format_part_sprinter (ctx, sp, mime_node_child (node, 0), first, TRUE, include_html);
+           format_part_sprinter (ctx, sp, mime_node_child (node, 0), true, include_html);
            sp->end (sp);
        }
        sp->end (sp);
@@ -713,7 +713,7 @@ format_part_sprinter (const void *ctx, sprinter_t *sp, mime_node_t *node,
        sp->begin_map (sp);
 
        sp->map_key (sp, "headers");
-       format_headers_sprinter (sp, GMIME_MESSAGE (node->part), FALSE);
+       format_headers_sprinter (sp, GMIME_MESSAGE (node->part), false);
 
        sp->map_key (sp, "body");
        sp->begin_list (sp);
@@ -721,7 +721,7 @@ format_part_sprinter (const void *ctx, sprinter_t *sp, mime_node_t *node,
     }
 
     for (i = 0; i < node->nchildren; i++)
-       format_part_sprinter (ctx, sp, mime_node_child (node, i), i == 0, TRUE, include_html);
+       format_part_sprinter (ctx, sp, mime_node_child (node, i), true, include_html);
 
     /* Close content structures */
     for (i = 0; i < nclose; i++)
@@ -735,7 +735,7 @@ format_part_sprinter_entry (const void *ctx, sprinter_t *sp,
                            mime_node_t *node, unused (int indent),
                            const notmuch_show_params_t *params)
 {
-    format_part_sprinter (ctx, sp, node, TRUE, params->output_body, params->include_html);
+    format_part_sprinter (ctx, sp, node, params->output_body, params->include_html);
 
     return NOTMUCH_STATUS_SUCCESS;
 }
@@ -898,8 +898,8 @@ show_messages (void *ctx,
               notmuch_show_params_t *params)
 {
     notmuch_message_t *message;
-    notmuch_bool_t match;
-    notmuch_bool_t excluded;
+    bool match;
+    bool excluded;
     int next_indent;
     notmuch_status_t status, res = NOTMUCH_STATUS_SUCCESS;
 
@@ -1081,35 +1081,33 @@ notmuch_show_command (notmuch_config_t *config, int argc, char *argv[])
     sprinter_t *sprinter;
     notmuch_show_params_t params = {
        .part = -1,
-       .omit_excluded = TRUE,
-       .output_body = TRUE,
+       .omit_excluded = true,
+       .output_body = true,
     };
     int format = NOTMUCH_FORMAT_NOT_SPECIFIED;
-    int exclude = TRUE;
-
-    /* This value corresponds to neither true nor false being passed
-     * on the command line */
-    int entire_thread = -1;
-    notmuch_bool_t single_message;
+    bool exclude = true;
+    bool entire_thread_set = false;
+    bool single_message;
 
     notmuch_opt_desc_t options[] = {
-       { NOTMUCH_OPT_KEYWORD, &format, "format", 'f',
+       { .opt_keyword = &format, .name = "format", .keywords =
          (notmuch_keyword_t []){ { "json", NOTMUCH_FORMAT_JSON },
                                  { "text", NOTMUCH_FORMAT_TEXT },
                                  { "sexp", NOTMUCH_FORMAT_SEXP },
                                  { "mbox", NOTMUCH_FORMAT_MBOX },
                                  { "raw", NOTMUCH_FORMAT_RAW },
                                  { 0, 0 } } },
-       { NOTMUCH_OPT_INT, &notmuch_format_version, "format-version", 0, 0 },
-       { NOTMUCH_OPT_BOOLEAN, &exclude, "exclude", 'x', 0 },
-       { NOTMUCH_OPT_BOOLEAN, &entire_thread, "entire-thread", 't', 0 },
-       { NOTMUCH_OPT_INT, &params.part, "part", 'p', 0 },
-       { NOTMUCH_OPT_BOOLEAN, &params.crypto.decrypt, "decrypt", 'd', 0 },
-       { 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 }
+       { .opt_int = &notmuch_format_version, .name = "format-version" },
+       { .opt_bool = &exclude, .name = "exclude" },
+       { .opt_bool = &params.entire_thread, .name = "entire-thread",
+         .present = &entire_thread_set },
+       { .opt_int = &params.part, .name = "part" },
+       { .opt_bool = &params.crypto.decrypt, .name = "decrypt" },
+       { .opt_bool = &params.crypto.verify, .name = "verify" },
+       { .opt_bool = &params.output_body, .name = "body" },
+       { .opt_bool = &params.include_html, .name = "include-html" },
+       { .opt_inherit = notmuch_shared_options },
+       { }
     };
 
     opt_index = parse_arguments (argc, argv, options, 1);
@@ -1120,7 +1118,7 @@ notmuch_show_command (notmuch_config_t *config, int argc, char *argv[])
 
     /* decryption implies verification */
     if (params.crypto.decrypt)
-       params.crypto.verify = TRUE;
+       params.crypto.verify = true;
 
     /* specifying a part implies single message display */
     single_message = params.part >= 0;
@@ -1140,26 +1138,21 @@ notmuch_show_command (notmuch_config_t *config, int argc, char *argv[])
        }
     } else if (format == NOTMUCH_FORMAT_RAW) {
        /* raw format only supports single message display */
-       single_message = TRUE;
+       single_message = true;
     }
 
     notmuch_exit_if_unsupported_format ();
 
-    /* Default is entire-thread = FALSE except for format=json and
+    /* Default is entire-thread = false except for format=json and
      * format=sexp. */
-    if (entire_thread != FALSE && entire_thread != TRUE) {
-       if (format == NOTMUCH_FORMAT_JSON || format == NOTMUCH_FORMAT_SEXP)
-           params.entire_thread = TRUE;
-       else
-           params.entire_thread = FALSE;
-    } else {
-       params.entire_thread = entire_thread;
-    }
+    if (! entire_thread_set &&
+       (format == NOTMUCH_FORMAT_JSON || format == NOTMUCH_FORMAT_SEXP))
+       params.entire_thread = true;
 
     if (!params.output_body) {
        if (params.part > 0) {
            fprintf (stderr, "Warning: --body=false is incompatible with --part > 0. Disabling.\n");
-           params.output_body = TRUE;
+           params.output_body = true;
        } else {
            if (format != NOTMUCH_FORMAT_JSON && format != NOTMUCH_FORMAT_SEXP)
                fprintf (stderr,
@@ -1229,9 +1222,9 @@ notmuch_show_command (notmuch_config_t *config, int argc, char *argv[])
            }
        }
 
-       if (exclude == FALSE) {
-           notmuch_query_set_omit_excluded (query, FALSE);
-           params.omit_excluded = FALSE;
+       if (exclude == false) {
+           notmuch_query_set_omit_excluded (query, false);
+           params.omit_excluded = false;
        }
 
        ret = do_show (config, query, formatter, sprinter, &params);
@@ -1241,7 +1234,7 @@ notmuch_show_command (notmuch_config_t *config, int argc, char *argv[])
     g_mime_stream_flush (params.out_stream);
     g_object_unref (params.out_stream);
 
-    notmuch_crypto_cleanup (&params.crypto);
+    _notmuch_crypto_cleanup (&params.crypto);
     notmuch_query_destroy (query);
     notmuch_database_destroy (notmuch);
 
index 9c03754d58568a04f9a7971d2296ab014c19f054..05b1837d7c5038ee6cf9ad5f387ca8264bcab172 100644 (file)
@@ -194,10 +194,10 @@ notmuch_tag_command (notmuch_config_t *config, int argc, char *argv[])
     notmuch_database_t *notmuch;
     struct sigaction action;
     tag_op_flag_t tag_flags = TAG_FLAG_NONE;
-    notmuch_bool_t batch = FALSE;
-    notmuch_bool_t remove_all = FALSE;
+    bool batch = false;
+    bool remove_all = false;
     FILE *input = stdin;
-    char *input_file_name = NULL;
+    const char *input_file_name = NULL;
     int opt_index;
     int ret;
 
@@ -209,11 +209,11 @@ notmuch_tag_command (notmuch_config_t *config, int argc, char *argv[])
     sigaction (SIGINT, &action, NULL);
 
     notmuch_opt_desc_t options[] = {
-       { 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 }
+       { .opt_bool = &batch, .name = "batch" },
+       { .opt_string = &input_file_name, .name = "input" },
+       { .opt_bool = &remove_all, .name = "remove-all" },
+       { .opt_inherit = notmuch_shared_options },
+       { }
     };
 
     opt_index = parse_arguments (argc, argv, options, 1);
@@ -223,7 +223,7 @@ notmuch_tag_command (notmuch_config_t *config, int argc, char *argv[])
     notmuch_process_shared_options (argv[0]);
 
     if (input_file_name) {
-       batch = TRUE;
+       batch = true;
        input = fopen (input_file_name, "r");
        if (input == NULL) {
            fprintf (stderr, "Error opening %s for reading: %s\n",
@@ -235,6 +235,8 @@ notmuch_tag_command (notmuch_config_t *config, int argc, char *argv[])
     if (batch) {
        if (opt_index != argc) {
            fprintf (stderr, "Can't specify both cmdline and stdin!\n");
+           if (input)
+               fclose (input);
            return EXIT_FAILURE;
        }
     } else {
index 8e332ce644101addf99304952334b71838817f11..539ac58c445a6e4dd222e987f3e255dde5aa4519 100644 (file)
--- a/notmuch.c
+++ b/notmuch.c
@@ -46,14 +46,14 @@ 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;
+static bool print_version = false, print_help = false;
+const 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}
+    { .opt_bool = &print_version, .name = "version" },
+    { .opt_bool = &print_help, .name = "help" },
+    { .opt_string = &notmuch_requested_db_uuid, .name = "uuid" },
+    { }
 };
 
 /* any subcommand wanting to support these options should call
@@ -82,8 +82,8 @@ int notmuch_minimal_options (const char *subcommand_name,
     int opt_index;
 
     notmuch_opt_desc_t options[] = {
-       { NOTMUCH_OPT_INHERIT, (void *) &notmuch_shared_options, NULL, 0, 0 },
-       { 0, 0, 0, 0, 0 }
+       { .opt_inherit = notmuch_shared_options },
+       { }
     };
 
     opt_index = parse_arguments (argc, argv, options, 1);
@@ -96,6 +96,46 @@ int notmuch_minimal_options (const char *subcommand_name,
     return opt_index;
 }
 
+
+struct _notmuch_client_indexing_cli_choices indexing_cli_choices = { };
+const notmuch_opt_desc_t  notmuch_shared_indexing_options [] = {
+    { .opt_bool = &indexing_cli_choices.try_decrypt,
+      .present = &indexing_cli_choices.try_decrypt_set,
+      .name = "try-decrypt" },
+    { }
+};
+
+
+notmuch_status_t
+notmuch_process_shared_indexing_options (notmuch_database_t *notmuch, g_mime_3_unused(notmuch_config_t *config))
+{
+    if (indexing_cli_choices.opts == NULL)
+       indexing_cli_choices.opts = notmuch_database_get_default_indexopts (notmuch);
+    if (indexing_cli_choices.try_decrypt_set) {
+       notmuch_status_t status;
+       if (indexing_cli_choices.opts == NULL)
+           return NOTMUCH_STATUS_OUT_OF_MEMORY;
+       status = notmuch_indexopts_set_try_decrypt (indexing_cli_choices.opts, indexing_cli_choices.try_decrypt);
+       if (status != NOTMUCH_STATUS_SUCCESS) {
+           fprintf (stderr, "Error: Failed to set try_decrypt to %s. (%s)\n",
+                    indexing_cli_choices.try_decrypt ? "True" : "False", notmuch_status_to_string (status));
+           notmuch_indexopts_destroy (indexing_cli_choices.opts);
+           indexing_cli_choices.opts = NULL;
+           return status;
+       }
+    }
+#if (GMIME_MAJOR_VERSION < 3)
+    if (indexing_cli_choices.opts && notmuch_indexopts_get_try_decrypt (indexing_cli_choices.opts)) {
+       const char* gpg_path = notmuch_config_get_crypto_gpg_path (config);
+       if (gpg_path && strcmp(gpg_path, "gpg"))
+           fprintf (stderr, "Warning: deprecated crypto.gpg_path is set to '%s'\n"
+                    "\tbut ignoring (use $PATH instead)\n", gpg_path);
+    }
+#endif
+    return NOTMUCH_STATUS_SUCCESS;
+}
+
+
 static command_t commands[] = {
     { NULL, notmuch_command, NOTMUCH_CONFIG_OPEN | NOTMUCH_CONFIG_CREATE,
       "Notmuch main command." },
@@ -123,6 +163,8 @@ static command_t commands[] = {
       "Restore the tags from the given dump file (see 'dump')." },
     { "compact", notmuch_compact_command, NOTMUCH_CONFIG_OPEN,
       "Compact the notmuch database." },
+    { "reindex", notmuch_reindex_command, NOTMUCH_CONFIG_OPEN,
+      "Re-index all messages matching the search terms." },
     { "config", notmuch_config_command, NOTMUCH_CONFIG_OPEN,
       "Get or set settings in the notmuch configuration file." },
     { "help", notmuch_help_command, NOTMUCH_CONFIG_CREATE, /* create but don't save config */
@@ -369,13 +411,13 @@ notmuch_command (notmuch_config_t *config,
  * is).
  *
  * Does not return if the external command is found and
- * executed. Return TRUE if external command is not found. Return
- * FALSE on errors.
+ * executed. Return true if external command is not found. Return
+ * false on errors.
  */
-static notmuch_bool_t try_external_command(char *argv[])
+static bool try_external_command(char *argv[])
 {
     char *old_argv0 = argv[0];
-    notmuch_bool_t ret = TRUE;
+    bool ret = true;
 
     argv[0] = talloc_asprintf (NULL, "notmuch-%s", old_argv0);
 
@@ -387,7 +429,7 @@ static notmuch_bool_t try_external_command(char *argv[])
     if (errno != ENOENT) {
        fprintf (stderr, "Error: Running external command '%s' failed: %s\n",
                 argv[0], strerror(errno));
-       ret = FALSE;
+       ret = false;
     }
 
     talloc_free (argv[0]);
@@ -403,15 +445,15 @@ main (int argc, char *argv[])
     char *talloc_report;
     const char *command_name = NULL;
     command_t *command;
-    char *config_file_name = NULL;
+    const char *config_file_name = NULL;
     notmuch_config_t *config = NULL;
     int opt_index;
     int ret;
 
     notmuch_opt_desc_t options[] = {
-       { NOTMUCH_OPT_STRING, &config_file_name, "config", 'c', 0 },
-       { NOTMUCH_OPT_INHERIT, (void *) &notmuch_shared_options, NULL, 0, 0 },
-       { 0, 0, 0, 0, 0 }
+       { .opt_string = &config_file_name, .name = "config" },
+       { .opt_inherit = notmuch_shared_options },
+       { }
     };
 
     talloc_enable_null_tracking ();
index f3f9be41856b89863d20059ec3877110907b76d2..8a5dabf5a0346bffc7512eddd915581f4ed8c49d 100644 (file)
@@ -1,4 +1,4 @@
-tmp.*/
-log.*/
-corpus/
-notmuch.cache.*/
+/tmp.*/
+/log.*/
+/corpus/
+/notmuch.cache.*/
index a040a97e043a65e3691361a53556dba6f0f17da5..aab36e6953e2917b40a959a5bb4177d0c1cfd772 100755 (executable)
@@ -2,7 +2,7 @@
 
 test_description='notmuch new'
 
-. ./perf-test-lib.sh || exit 1
+. $(dirname "$0")/perf-test-lib.sh || exit 1
 
 # ensure initial 'notmuch new' is run by memory_start
 uncache_database
index 8fea98242d2cb91785ba08fdb5d717ce138532c2..32ab8dc9c0b99fac7e5375b17f8d184daea7187b 100755 (executable)
@@ -2,7 +2,7 @@
 
 test_description='dump and restore'
 
-. ./perf-test-lib.sh || exit 1
+. $(dirname "$0")/perf-test-lib.sh || exit 1
 
 memory_start
 
index d73035ea1197de8d0a9d6d587ad32afee90de4b2..2e218fd3c4b6fd0c5ea9163a82084de627752918 100755 (executable)
@@ -2,7 +2,7 @@
 
 test_description='show'
 
-. ./perf-test-lib.sh || exit 1
+. $(dirname "$0")/perf-test-lib.sh || exit 1
 
 memory_start
 
index 8d026eee366278048b95a7c4b4eb705fca56377b..343f5c7cfc24bba490ee67aa410be76937165763 100755 (executable)
@@ -2,7 +2,7 @@
 
 test_description='search'
 
-. ./perf-test-lib.sh || exit 1
+. $(dirname "$0")/perf-test-lib.sh || exit 1
 
 memory_start
 
index 0e1ce0876925dd18272e9517b42a7cffdf3e29bd..3c1205dbc41206f67c086de8640c3ef615d23f0f 100755 (executable)
@@ -2,7 +2,7 @@
 
 test_description='search'
 
-. ./perf-test-lib.sh || exit 1
+. $(dirname "$0")/perf-test-lib.sh || exit 1
 
 memory_start
 
diff --git a/performance-test/M05-reindex.sh b/performance-test/M05-reindex.sh
new file mode 100755 (executable)
index 0000000..17e2c82
--- /dev/null
@@ -0,0 +1,11 @@
+#!/bin/bash
+
+test_description='reindex'
+
+. $(dirname "$0")/perf-test-lib.sh || exit 1
+
+memory_start
+
+memory_run 'reindex *' "notmuch reindex '*'"
+
+memory_done
diff --git a/performance-test/M06-insert.sh b/performance-test/M06-insert.sh
new file mode 100755 (executable)
index 0000000..5ae0656
--- /dev/null
@@ -0,0 +1,16 @@
+#!/bin/bash
+
+test_description='search'
+
+. $(dirname "$0")/perf-test-lib.sh || exit 1
+
+memory_start
+
+mkdir -p "$MAIL_DIR"/{cur,new,tmp}
+
+for count in {1..20}; do
+    generate_message "[file]=\"insert-$count\"" "[dir]='tmp/'"
+    memory_run "insert $count" "notmuch insert < $gen_msg_filename"
+done
+
+memory_done
index b9f211581f5a20986605dbe0fb54c727e7ddd93d..687501294e2ce0751311703b7d283fc37db0b2e0 100755 (executable)
@@ -2,7 +2,7 @@
 
 test_description='notmuch new'
 
-. ./perf-test-lib.sh || exit 1
+. $(dirname "$0")/perf-test-lib.sh || exit 1
 
 uncache_database
 
index 9cfd5cd624accdb1e77b6c3a17bc2a79cebbfedc..12f12e660533ecd288cbdd61e6dbfddb34b6f916 100755 (executable)
@@ -2,7 +2,7 @@
 
 test_description='dump and restore'
 
-. ./perf-test-lib.sh || exit 1
+. $(dirname "$0")/perf-test-lib.sh || exit 1
 
 time_start
 
index dacb50b8bbf822eb808ae20202b1a0fb2615aa98..8c5dfd68031656503d2ba40b49271f9570b87eaa 100755 (executable)
@@ -2,7 +2,7 @@
 
 test_description='tagging'
 
-. ./perf-test-lib.sh || exit 1
+. $(dirname "$0")/perf-test-lib.sh || exit 1
 
 time_start
 
diff --git a/performance-test/T03-reindex.sh b/performance-test/T03-reindex.sh
new file mode 100755 (executable)
index 0000000..d6d5c3c
--- /dev/null
@@ -0,0 +1,13 @@
+#!/bin/bash
+
+test_description='tagging'
+
+. $(dirname "$0")/perf-test-lib.sh || exit 1
+
+time_start
+
+time_run 'reindex *' "notmuch reindex '*'"
+time_run 'reindex *' "notmuch reindex '*'"
+time_run 'reindex *' "notmuch reindex '*'"
+
+time_done
index 7b092346e4239ed9ced54115b05e67e35d60883f..5c3562045af230d93163323b39dbff144f4ed665 100644 (file)
@@ -1,2 +1,2 @@
-*.tar.gz
-*.tar.xz
+/*.tar.gz
+/*.tar.xz
index c89d5aab39a069e7ee28596b3f20ea0e7a8803ed..56538abddcdbc1d8950f62a6f2d73fd1aa7d7dda 100644 (file)
@@ -1,4 +1,4 @@
-. ./version.sh || exit 1
+. $(dirname "$0")/version.sh || exit 1
 
 corpus_size=large
 
@@ -25,12 +25,16 @@ do
                echo "error: unknown performance test option '$1'" >&2; exit 1 ;;
        esac
 done
-. ../test/test-lib-common.sh || exit 1
+
+# Ensure NOTMUCH_SRCDIR and NOTMUCH_BUILDDIR are set.
+. $(dirname "$0")/../test/export-dirs.sh || exit 1
+
+. "$NOTMUCH_SRCDIR/test/test-lib-common.sh" || exit 1
 
 set -e
 
-if ! test -x ../notmuch
-then
+# It appears that people try to run tests without building...
+if [[ ! -x "$NOTMUCH_BUILDDIR/notmuch" ]]; then
        echo >&2 'You do not seem to have built notmuch yet.'
        exit 1
 fi
index 0a077907cd83368c339c320968ef2b8a5e91f7d8..c6ec857720d9e6acd0c0c4b9410afaa391249606 100644 (file)
@@ -13,14 +13,14 @@ struct sprinter_json {
     /* A flag to signify that a separator should be inserted in the
      * output as soon as possible.
      */
-    notmuch_bool_t insert_separator;
+    bool insert_separator;
 };
 
 struct json_state {
     struct json_state *parent;
     /* True if nothing has been printed in this aggregate yet.
      * Suppresses the comma before a value. */
-    notmuch_bool_t first;
+    bool first;
     /* The character that closes the current aggregate. */
     char close;
 };
@@ -37,12 +37,12 @@ json_begin_value (struct sprinter *sp)
            fputc (',', spj->stream);
            if (spj->insert_separator) {
                fputc ('\n', spj->stream);
-               spj->insert_separator = FALSE;
+               spj->insert_separator = false;
            } else {
                fputc (' ', spj->stream);
            }
        } else {
-           spj->state->first = FALSE;
+           spj->state->first = false;
        }
     }
     return spj;
@@ -58,7 +58,7 @@ json_begin_aggregate (struct sprinter *sp, char open, char close)
 
     fputc (open, spj->stream);
     state->parent = spj->state;
-    state->first = TRUE;
+    state->first = true;
     state->close = close;
     spj->state = state;
 }
@@ -132,7 +132,7 @@ json_integer (struct sprinter *sp, int val)
 }
 
 static void
-json_boolean (struct sprinter *sp, notmuch_bool_t val)
+json_boolean (struct sprinter *sp, bool val)
 {
     struct sprinter_json *spj = json_begin_value (sp);
 
@@ -154,7 +154,7 @@ json_map_key (struct sprinter *sp, const char *key)
 
     json_string (sp, key);
     fputs (": ", spj->stream);
-    spj->state->first = TRUE;
+    spj->state->first = true;
 }
 
 static void
@@ -167,7 +167,7 @@ json_separator (struct sprinter *sp)
 {
     struct sprinter_json *spj = (struct sprinter_json *) sp;
 
-    spj->insert_separator = TRUE;
+    spj->insert_separator = true;
 }
 
 struct sprinter *
@@ -186,7 +186,7 @@ sprinter_json_create (const void *ctx, FILE *stream)
            .map_key = json_map_key,
            .separator = json_separator,
            .set_prefix = json_set_prefix,
-           .is_text_printer = FALSE,
+           .is_text_printer = false,
        }
     };
     struct sprinter_json *res;
index 08783e11d3bd37ddef3a02fdb606164008edfa89..6891ea4254f82e89b9758f0fb6921901f00377b3 100644 (file)
@@ -33,7 +33,7 @@ struct sprinter_sexp {
 
     /* A flag to signify that a separator should be inserted in the
      * output as soon as possible. */
-    notmuch_bool_t insert_separator;
+    bool insert_separator;
 };
 
 struct sexp_state {
@@ -41,7 +41,7 @@ struct sexp_state {
 
     /* True if nothing has been printed in this aggregate yet.
      * Suppresses the space before a value. */
-    notmuch_bool_t first;
+    bool first;
 };
 
 /* Helper function to set up the stream to print a value.  If this
@@ -55,12 +55,12 @@ sexp_begin_value (struct sprinter *sp)
        if (! sps->state->first) {
            if (sps->insert_separator) {
                fputc ('\n', sps->stream);
-               sps->insert_separator = FALSE;
+               sps->insert_separator = false;
            } else {
                fputc (' ', sps->stream);
            }
        } else {
-           sps->state->first = FALSE;
+           sps->state->first = false;
        }
     }
     return sps;
@@ -76,7 +76,7 @@ sexp_begin_aggregate (struct sprinter *sp)
 
     fputc ('(', sps->stream);
     state->parent = sps->state;
-    state->first = TRUE;
+    state->first = true;
     sps->state = state;
 }
 
@@ -169,7 +169,7 @@ sexp_integer (struct sprinter *sp, int val)
 }
 
 static void
-sexp_boolean (struct sprinter *sp, notmuch_bool_t val)
+sexp_boolean (struct sprinter *sp, bool val)
 {
     struct sprinter_sexp *sps = sexp_begin_value (sp);
 
@@ -202,7 +202,7 @@ sexp_separator (struct sprinter *sp)
 {
     struct sprinter_sexp *sps = (struct sprinter_sexp *) sp;
 
-    sps->insert_separator = TRUE;
+    sps->insert_separator = true;
 }
 
 struct sprinter *
@@ -221,7 +221,7 @@ sprinter_sexp_create (const void *ctx, FILE *stream)
            .map_key = sexp_map_key,
            .separator = sexp_separator,
            .set_prefix = sexp_set_prefix,
-           .is_text_printer = FALSE,
+           .is_text_printer = false,
        }
     };
     struct sprinter_sexp *res;
index 7779488f99a3db4c7919e93210e86acccf9c8949..648b54b1e886553cd839ca24f4f5f0a9378aa47b 100644 (file)
@@ -21,7 +21,7 @@ struct sprinter_text {
     /* A flag to indicate if this is the first tag. Used in list of tags
      * for summary.
      */
-    notmuch_bool_t first_tag;
+    bool first_tag;
 };
 
 static void
@@ -52,7 +52,7 @@ text_integer (struct sprinter *sp, int val)
 }
 
 static void
-text_boolean (struct sprinter *sp, notmuch_bool_t val)
+text_boolean (struct sprinter *sp, bool val)
 {
     struct sprinter_text *sptxt = (struct sprinter_text *) sp;
 
@@ -128,7 +128,7 @@ sprinter_text_create (const void *ctx, FILE *stream)
            .map_key = text_map_key,
            .separator = text_separator,
            .set_prefix = text_set_prefix,
-           .is_text_printer = TRUE,
+           .is_text_printer = true,
        },
     };
     struct sprinter_text *res;
index f859672f8b3f23342c25b5d50f6e64437c03dac7..9d2e9b6f7140ad4f2aa2eae6d9981cb858d56019 100644 (file)
@@ -1,7 +1,7 @@
 #ifndef NOTMUCH_SPRINTER_H
 #define NOTMUCH_SPRINTER_H
 
-/* Necessary for notmuch_bool_t */
+/* Necessary for bool */
 #include "notmuch-client.h"
 
 /* Structure printer interface. This is used to create output
@@ -34,7 +34,7 @@ typedef struct sprinter {
     void (*string) (struct sprinter *, const char *);
     void (*string_len) (struct sprinter *, const char *, size_t);
     void (*integer) (struct sprinter *, int);
-    void (*boolean) (struct sprinter *, notmuch_bool_t);
+    void (*boolean) (struct sprinter *, bool);
     void (*null) (struct sprinter *);
 
     /* Print the key of a map's key/value pair. The char * must be UTF-8
@@ -58,7 +58,7 @@ typedef struct sprinter {
 
     /* True if this is the special-cased plain text printer.
      */
-    notmuch_bool_t is_text_printer;
+    bool is_text_printer;
 } sprinter_t;
 
 
index d9fca7b832fd91ed4cc41469adf437cbd9fac595..1837b1aeafa347cfe6840ec70add6426d3a8f3f1 100644 (file)
@@ -7,7 +7,7 @@
 
 struct _tag_operation_t {
     const char *tag;
-    notmuch_bool_t remove;
+    bool remove;
 };
 
 struct _tag_op_list_t {
@@ -35,7 +35,7 @@ line_error (tag_parse_status_t status,
 }
 
 const char *
-illegal_tag (const char *tag, notmuch_bool_t remove)
+illegal_tag (const char *tag, bool remove)
 {
     if (*tag == '\0' && ! remove)
        return "empty tag forbidden";
@@ -84,7 +84,7 @@ parse_tag_line (void *ctx, char *line,
 
     /* Parse tags. */
     while ((tok = strtok_len (tok + tok_len, " ", &tok_len)) != NULL) {
-       notmuch_bool_t remove;
+       bool remove;
        char *tag;
 
        /* Optional explicit end of tags marker. */
@@ -168,7 +168,7 @@ parse_tag_command_line (void *ctx, int argc, char **argv,
        if (argv[i][0] != '+' && argv[i][0] != '-')
            break;
 
-       notmuch_bool_t is_remove = argv[i][0] == '-';
+       bool is_remove = argv[i][0] == '-';
        const char *msg;
 
        msg = illegal_tag (argv[i] + 1, is_remove);
@@ -215,7 +215,7 @@ makes_changes (notmuch_message_t *message,
     size_t i;
 
     notmuch_tags_t *tags;
-    notmuch_bool_t changes = FALSE;
+    bool changes = false;
 
     /* First, do we delete an existing tag? */
     for (tags = notmuch_message_get_tags (message);
@@ -239,11 +239,11 @@ makes_changes (notmuch_message_t *message,
     notmuch_tags_destroy (tags);
 
     if (changes)
-       return TRUE;
+       return true;
 
     /* Now check for adding new tags */
     for (i = 0; i < list->count; i++) {
-       notmuch_bool_t exists = FALSE;
+       bool exists = false;
 
        if (list->ops[i].remove)
            continue;
@@ -253,7 +253,7 @@ makes_changes (notmuch_message_t *message,
             notmuch_tags_move_to_next (tags)) {
            const char *cur_tag = notmuch_tags_get (tags);
            if (strcmp (cur_tag, list->ops[i].tag) == 0) {
-               exists = TRUE;
+               exists = true;
                break;
            }
        }
@@ -264,9 +264,9 @@ makes_changes (notmuch_message_t *message,
         * but this is OK from a correctness point of view
         */
        if (! exists)
-           return TRUE;
+           return true;
     }
-    return FALSE;
+    return false;
 
 }
 
@@ -359,7 +359,7 @@ tag_op_list_create (void *ctx)
 int
 tag_op_list_append (tag_op_list_t *list,
                    const char *tag,
-                   notmuch_bool_t remove)
+                   bool remove)
 {
     /* Make room if current array is full.  This should be a fairly
      * rare case, considering the initial array size.
@@ -387,7 +387,7 @@ tag_op_list_append (tag_op_list_t *list,
  *   Is the i'th tag operation a remove?
  */
 
-notmuch_bool_t
+bool
 tag_op_list_isremove (const tag_op_list_t *list, size_t i)
 {
     assert (i < list->count);
index 8a4074ce168feb22a5d0ae93d0849b6861e171a7..a2f0ddfaa280b5d3e743efba6976581ac793c1a9 100644 (file)
@@ -99,7 +99,7 @@ parse_tag_command_line (void *ctx, int argc, char **argv,
  *        explanatory message otherwise.
  */
 const char *
-illegal_tag (const char *tag, notmuch_bool_t remove);
+illegal_tag (const char *tag, bool remove);
 
 /*
  * Create an empty list of tag operations
@@ -111,14 +111,14 @@ tag_op_list_t *
 tag_op_list_create (void *ctx);
 
 /*
- * Add a tag operation (delete iff remove == TRUE) to a list.
+ * Add a tag operation (delete iff remove == true) to a list.
  * The list is expanded as necessary.
  */
 
 int
 tag_op_list_append (tag_op_list_t *list,
                    const char *tag,
-                   notmuch_bool_t remove);
+                   bool remove);
 
 /*
  * Apply a list of tag operations, in order, to a given message.
@@ -157,7 +157,7 @@ tag_op_list_tag (const tag_op_list_t *list, size_t i);
  *   Is the i'th tag operation a remove?
  */
 
-notmuch_bool_t
+bool
 tag_op_list_isremove (const tag_op_list_t *list, size_t i);
 
 #endif
index 0579feef25c659f6da7752b54e93bb672d95b21c..73fe7e24620f923826803dbaa0449e1350cd1716 100644 (file)
@@ -1,11 +1,11 @@
-arg-test
-corpus.mail
-hex-xcode
-parse-time
-random-corpus
-smtp-dummy
-symbol-test
-make-db-version
-test-results
-ghost-report
-tmp.*
+/arg-test
+/corpora.mail
+/hex-xcode
+/parse-time
+/random-corpus
+/smtp-dummy
+/symbol-test
+/make-db-version
+/test-results
+/ghost-report
+/tmp.*
index 0df72c9216541fce4a342358869d2d4f20b2e5f3..1a0ab813f996a620ef3c2d99b90fe6add01c4bde 100644 (file)
@@ -62,13 +62,13 @@ test-binaries: $(TEST_BINARIES)
 test:  all test-binaries
 ifeq ($V,)
        @echo 'Use "$(MAKE) V=1" to see the details for passing and known broken tests.'
-       @env NOTMUCH_TEST_QUIET=1 ${test_src_dir}/notmuch-test $(OPTIONS)
+       @env NOTMUCH_TEST_QUIET=1 $(NOTMUCH_SRCDIR)/$(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)
+       @env NOTMUCH_TEST_QUIET=1 $(NOTMUCH_SRCDIR)/$(test_src_dir)/notmuch-test $(OPTIONS)
 else
-       @${test_src_dir}/notmuch-test $(OPTIONS)
+       @$(NOTMUCH_SRCDIR)/$(test_src_dir)/notmuch-test $(OPTIONS)
 endif
 endif
 
index 8e06f44241a667609fdd5b47eda05b51e6fd3b47..b378c3ff3c5fa3a60b9758f52abd3c731d69ccfe 100644 (file)
@@ -80,15 +80,6 @@ The following command-line options are available when running tests:
        As the names depend on the tests' file names, it is safe to
        run the tests with this option in parallel.
 
---root=<dir>::
-       This runs the testsuites specified under a separate directory.
-       However, caution is advised, as not all tests are maintained
-       with this relocation in mind, so some tests may behave
-       differently.
-
-       Pointing this argument at a tmpfs filesystem can improve the
-       speed of the test suite for some users.
-
 Certain tests require precomputed databases to complete. You can fetch these
 databases with
 
index 36a7ca4c5bdfb78ed28430ab6c471123780b85f9..7fbdcfa3036cd860b13647a1678efde2063ce621 100755 (executable)
@@ -4,17 +4,7 @@
 #
 
 test_description='the test framework itself.'
-
-################################################################
-# It appears that people try to run tests without building...
-
-if ! test -x ../notmuch
-then
-       echo >&2 'You do not seem to have built notmuch yet.'
-       exit 1
-fi
-
-. ./test-lib.sh || exit 1
+. $(dirname "$0")/test-lib.sh || exit 1
 
 ################################################################
 # Test harness
@@ -23,8 +13,7 @@ test_expect_success ':'
 
 test_begin_subtest 'test runs if prerequisite is satisfied'
 test_set_prereq HAVEIT
-haveit=no
-test_expect_success 'test_have_prereq HAVEIT && haveit=yes'
+test_expect_success 'test_have_prereq HAVEIT'
 
 test_begin_subtest 'tests clean up after themselves'
 clean=no
@@ -43,19 +32,19 @@ fi
 test_begin_subtest 'failure to clean up causes the test to fail'
 test_expect_code 2 'test_when_finished "(exit 2)"'
 
-EXPECTED=$TEST_DIRECTORY/test.expected-output
+EXPECTED=$NOTMUCH_SRCDIR/test/test.expected-output
 suppress_diff_date() {
     sed -e 's/\(.*\-\-\- test-verbose\.4\.\expected\).*/\1/' \
        -e 's/\(.*\+\+\+ test-verbose\.4\.\output\).*/\1/'
 }
 
 test_begin_subtest "Ensure that test output is suppressed unless the test fails"
-output=$(cd $TEST_DIRECTORY; NOTMUCH_TEST_QUIET= ./test-verbose 2>&1 | suppress_diff_date)
+output=$(cd $TEST_DIRECTORY; NOTMUCH_TEST_QUIET= $NOTMUCH_SRCDIR/test/test-verbose 2>&1 | suppress_diff_date)
 expected=$(cat $EXPECTED/test-verbose-no | suppress_diff_date)
 test_expect_equal "$output" "$expected"
 
 test_begin_subtest "Ensure that -v does not suppress test output"
-output=$(cd $TEST_DIRECTORY; NOTMUCH_TEST_QUIET= ./test-verbose -v 2>&1 | suppress_diff_date)
+output=$(cd $TEST_DIRECTORY; NOTMUCH_TEST_QUIET= $NOTMUCH_SRCDIR/test/test-verbose -v 2>&1 | suppress_diff_date)
 expected=$(cat $EXPECTED/test-verbose-yes | suppress_diff_date)
 # Do not include the results of test-verbose in totals
 rm $TEST_DIRECTORY/test-results/test-verbose
index 0c833de262170f5aea7b778aa62bcef0f9ed4d63..da45d3aecf7ae7a0c6e86be06f6d21155c50129b 100755 (executable)
@@ -1,7 +1,7 @@
 #!/usr/bin/env bash
 
 test_description="online help"
-. ./test-lib.sh || exit 1
+. $(dirname "$0")/test-lib.sh || exit 1
 
 test_begin_subtest 'notmuch --help'
 test_expect_success 'notmuch --help'
index a3d7380e81c8c12f1a685ca1f4e61cac292ea63c..58cd2ba74cefd7f9a1b6fa6808be7fd77c424a1c 100755 (executable)
@@ -1,6 +1,6 @@
 #!/usr/bin/env bash
 test_description='"notmuch compact"'
-. ./test-lib.sh || exit 1
+. $(dirname "$0")/test-lib.sh || exit 1
 
 add_message '[subject]=One'
 add_message '[subject]=Two'
index 35d757f6b227fc96bab7b32e203d4c0aac925baf..e91c36597e3c1601fc4c9b2346adb587cc7fbea0 100755 (executable)
@@ -1,7 +1,7 @@
 #!/usr/bin/env bash
 
 test_description='"notmuch config"'
-. ./test-lib.sh || exit 1
+. $(dirname "$0")/test-lib.sh || exit 1
 
 test_begin_subtest "Get string value"
 test_expect_equal "$(notmuch config get user.name)" "Notmuch Test Suite"
index 998bd6e0798a26c0cc23cee47a8cdd8690d66f24..56efe1d57b496536166a8db1d90d3d2bc3b652a1 100755 (executable)
@@ -1,7 +1,7 @@
 #!/usr/bin/env bash
 
 test_description='"notmuch setup"'
-. ./test-lib.sh || exit 1
+. $(dirname "$0")/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)
index ffa303efaceca99cdc7c0d6cbf75a83f6184bb1d..2035d29f529aa8c93922d5f98827fc12c5fd6cec 100755 (executable)
@@ -1,6 +1,6 @@
 #!/usr/bin/env bash
 test_description='"notmuch new" in several variations'
-. ./test-lib.sh || exit 1
+. $(dirname "$0")/test-lib.sh || exit 1
 
 test_begin_subtest "No new messages"
 output=$(NOTMUCH_NEW --debug)
@@ -309,7 +309,7 @@ cat <<EOF > notmuch-new-vanish.gdb
 set breakpoint pending on
 set logging file notmuch-new-vanish-gdb.log
 set logging on
-break add_file
+break notmuch_database_index_file
 commands
 shell rm -f ${MAIL_DIR}/vanish
 continue
index 4751440e94a1acf7ef552fd0503be3d4264dbaf7..0c0bf47309e930477f0b69fafeb93130f747fd3c 100755 (executable)
@@ -1,6 +1,6 @@
 #!/usr/bin/env bash
 test_description='"notmuch count" for messages and threads'
-. ./test-lib.sh || exit 1
+. $(dirname "$0")/test-lib.sh || exit 1
 
 add_email_corpus
 
index 48f212ee09283f45468a405c55544223c31daf0f..f1650e623e351475d1a0d17b7075f8673e9b3d0b 100755 (executable)
@@ -1,6 +1,6 @@
 #!/usr/bin/env bash
 test_description='"notmuch insert"'
-. ./test-lib.sh || exit 1
+. $(dirname "$0")/test-lib.sh || exit 1
 
 test_require_external_prereq gdb
 
@@ -132,6 +132,13 @@ output=$(notmuch search --output=files path:Drafts/new)
 dirname=$(dirname "$output")
 test_expect_equal "$dirname" "$MAIL_DIR/Drafts/new"
 
+test_begin_subtest "Insert message into folder with trailing /"
+gen_insert_msg
+notmuch insert --folder=Drafts/ < "$gen_msg_filename"
+output=$(notmuch search --output=files id:${gen_msg_id})
+dirname=$(dirname "$output")
+test_expect_equal "$dirname" "$MAIL_DIR/Drafts/new"
+
 test_begin_subtest "Insert message into folder, add/remove tags"
 gen_insert_msg
 notmuch insert --folder=Drafts +draft -unread < "$gen_msg_filename"
@@ -193,7 +200,7 @@ cat <<EOF > index-file-$code.gdb
 set breakpoint pending on
 set logging file index-file-$code.log
 set logging on
-break notmuch_database_add_message
+break notmuch_database_index_file
 commands
 return NOTMUCH_STATUS_$code
 continue
@@ -205,13 +212,13 @@ done
 gen_insert_msg
 
 for code in  FILE_NOT_EMAIL READ_ONLY_DATABASE UPGRADE_REQUIRED PATH_ERROR; do
-    test_begin_subtest "EXIT_FAILURE when add_message returns $code"
+    test_begin_subtest "EXIT_FAILURE when index_file returns $code"
     test_expect_code 1 \
          "${TEST_GDB} --batch-silent --return-child-result \
             -ex 'set args insert < $gen_msg_filename' \
             -x index-file-$code.gdb notmuch"
 
-    test_begin_subtest "success exit with --keep when add_message returns $code"
+    test_begin_subtest "success exit with --keep when index_file returns $code"
     test_expect_code 0 \
          "${TEST_GDB} --batch-silent --return-child-result \
             -ex 'set args insert --keep < $gen_msg_filename' \
@@ -219,13 +226,13 @@ for code in  FILE_NOT_EMAIL READ_ONLY_DATABASE UPGRADE_REQUIRED PATH_ERROR; do
 done
 
 for code in OUT_OF_MEMORY XAPIAN_EXCEPTION ; do
-    test_begin_subtest "EX_TEMPFAIL when add_message returns $code"
+    test_begin_subtest "EX_TEMPFAIL when index_file returns $code"
     test_expect_code 75 \
          "${TEST_GDB} --batch-silent --return-child-result \
             -ex 'set args insert < $gen_msg_filename' \
             -x index-file-$code.gdb notmuch"
 
-    test_begin_subtest "success exit with --keep when add_message returns $code"
+    test_begin_subtest "success exit with --keep when index_file returns $code"
     test_expect_code 0 \
          "${TEST_GDB} --batch-silent --return-child-result \
             -ex 'set args insert --keep < $gen_msg_filename' \
index d2d71ca9e7c2aa154d591192a4ec008a1834aa08..70f2854973a9ca29e1cf0c9d483cf1b00564e7f4 100755 (executable)
@@ -1,6 +1,6 @@
 #!/usr/bin/env bash
 test_description='"notmuch search" in several variations'
-. ./test-lib.sh || exit 1
+. $(dirname "$0")/test-lib.sh || exit 1
 
 add_email_corpus
 
@@ -111,7 +111,7 @@ thread:XXX   2009-11-18 [3/3] Adrian Perez de Castro, Keith Packard, Carl Worth;
 thread:XXX   2009-11-18 [3/3] Israel Herraiz, Keith Packard, Carl Worth; [notmuch] New to the list (inbox unread)
 thread:XXX   2009-11-18 [3/3] Jan Janak, Carl Worth; [notmuch] What a great idea! (inbox unread)
 thread:XXX   2009-11-18 [2/2] Jan Janak, Carl Worth; [notmuch] [PATCH] Older versions of install do not support -C. (inbox unread)
-thread:XXX   2009-11-18 [3/3] Aron Griffis, Keith Packard, Carl Worth; [notmuch] archive (inbox unread)
+thread:XXX   2009-11-18 [3/3(4)] Aron Griffis, Keith Packard, Carl Worth; [notmuch] archive (inbox unread)
 thread:XXX   2009-11-18 [2/2] Keith Packard, Carl Worth; [notmuch] [PATCH] Make notmuch-show 'X' (and 'x') commands remove inbox (and unread) tags (inbox unread)
 thread:XXX   2009-11-18 [7/7] Lars Kellogg-Stedman, Mikhail Gusarov, Keith Packard, Carl Worth; [notmuch] Working with Maildir storage? (inbox signed unread)
 thread:XXX   2009-11-18 [5/5] Mikhail Gusarov, Carl Worth, Keith Packard; [notmuch] [PATCH 1/2] Close message file after parsing message headers (inbox unread)
index dccefcb7f0128b214c6d2d5e74afddacc394f3af..bf28d220a760515bfeeba06a7e2fa92d389ad42b 100755 (executable)
@@ -1,6 +1,6 @@
 #!/usr/bin/env bash
 test_description='various settings for "notmuch search --output="'
-. ./test-lib.sh || exit 1
+. $(dirname "$0")/test-lib.sh || exit 1
 
 add_email_corpus
 
index 5931b1475e0b3d7358d84c2692dee3b72f2af517..f0291d29ec43076a3fda9f41ffadb6a6aed2ef43 100755 (executable)
@@ -1,6 +1,6 @@
 #!/usr/bin/env bash
 test_description='"notmuch address" in several variants'
-. ./test-lib.sh || exit 1
+. $(dirname "$0")/test-lib.sh || exit 1
 
 add_email_corpus
 
index 2844ec61f47fc6f93cde459ac1e4e34c4b7af6f2..a090f3d247c418605ee8f80cb59e059386aec690 100755 (executable)
@@ -1,6 +1,6 @@
 #!/usr/bin/env bash
 test_description='"notmuch search" by folder: and path: (with variations)'
-. ./test-lib.sh || exit 1
+. $(dirname "$0")/test-lib.sh || exit 1
 
 add_message '[dir]=bad' '[subject]="To the bone"'
 add_message '[dir]=.' '[subject]="Top level"'
@@ -15,7 +15,7 @@ add_message '[dir]=things/bad' '[subject]="Bites, stings, sad feelings"'
 test_begin_subtest "Single-world folder: specification (multiple results)"
 output=$(notmuch search folder:bad folder:bad/news folder:things/bad | notmuch_search_sanitize)
 test_expect_equal "$output" "thread:XXX   2001-01-05 [1/1] Notmuch Test Suite; To the bone (inbox unread)
-thread:XXX   2001-01-05 [1/1] Notmuch Test Suite; Bears (inbox unread)
+thread:XXX   2001-01-05 [1/1(2)] Notmuch Test Suite; Bears (inbox unread)
 thread:XXX   2001-01-05 [1/1] Notmuch Test Suite; Bites, stings, sad feelings (inbox unread)"
 
 test_begin_subtest "Top level folder"
@@ -24,7 +24,7 @@ test_expect_equal "$output" "thread:XXX   2001-01-05 [1/1] Notmuch Test Suite; T
 
 test_begin_subtest "Two-word path to narrow results to one"
 output=$(notmuch search folder:bad/news | notmuch_search_sanitize)
-test_expect_equal "$output" "thread:XXX   2001-01-05 [1/1] Notmuch Test Suite; Bears (inbox unread)"
+test_expect_equal "$output" "thread:XXX   2001-01-05 [1/1(2)] Notmuch Test Suite; Bears (inbox unread)"
 
 test_begin_subtest "Folder search with --output=files"
 output=$(notmuch search --output=files folder:bad/news | notmuch_search_files_sanitize)
index 2a4238f95fc93ca48830b6d13f113bf6fe7b6268..f4d5ee1479bae98b8d52f29682aefa66cc50b271 100755 (executable)
@@ -18,7 +18,7 @@
 # id:3wd4o8wa7fx.fsf@testarossa.amd.com
 
 test_description='that notmuch does not overlap term positions'
-. ./test-lib.sh || exit 1
+. $(dirname "$0")/test-lib.sh || exit 1
 
 add_message '[to]="a@b.c, x@y.z"'
 
index 4862d82644df6979b2e84f98d8006fdadf18c957..509fec8b0f2a21e1b632c88f52dac4cffeb0c604 100755 (executable)
@@ -1,6 +1,6 @@
 #!/usr/bin/env bash
 test_description='messages with unquoted . in name'
-. ./test-lib.sh || exit 1
+. $(dirname "$0")/test-lib.sh || exit 1
 
 add_message \
   '[from]="Some.Name for Someone <bugs@quoting.com>"' \
index c8986f4e405795b58cda66553c80e5e9588bcf4a..8a30e7abf87fa84c5c5f31ea49c32ebaace23db9 100755 (executable)
@@ -1,6 +1,6 @@
 #!/usr/bin/env bash
 test_description='"notmuch search" --offset and --limit parameters'
-. ./test-lib.sh || exit 1
+. $(dirname "$0")/test-lib.sh || exit 1
 
 add_email_corpus
 
index f91d4d7f0cac11bd7282d9b7a8156c1ac6808294..0cf69975f44275c8f43f6939f363fd0111cc649f 100755 (executable)
@@ -1,6 +1,6 @@
 #!/usr/bin/env bash
 test_description='"notmuch search, count and show" with excludes in several variations'
-. ./test-lib.sh || exit 1
+. $(dirname "$0")/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"
index 0d0a3b874526dccd24166066be3767fbe29efea5..6140c67686305f8959bc7398ad819212cfb38ee4 100755 (executable)
@@ -1,6 +1,6 @@
 #!/usr/bin/env bash
 test_description='"notmuch tag"'
-. ./test-lib.sh || exit 1
+. $(dirname "$0")/test-lib.sh || exit 1
 
 add_message '[subject]=One'
 add_message '[subject]=Two'
index ac51895e826d260601260d6aae1a55bb90138fa9..91b98e5d6bdc5bdeccfee989424e1366c9fa5e40 100755 (executable)
@@ -1,6 +1,6 @@
 #!/usr/bin/env bash
 test_description="--format=json output"
-. ./test-lib.sh || exit 1
+. $(dirname "$0")/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\""
@@ -41,13 +41,13 @@ id="json-show-inline-attachment-filename@notmuchmail.org"
 emacs_fcc_message \
     "$subject" \
     'This is a test message with inline attachment with a filename' \
-    "(mml-attach-file \"$TEST_DIRECTORY/README\" nil nil \"inline\")
+    "(mml-attach-file \"$NOTMUCH_SRCDIR/test/README\" nil nil \"inline\")
      (message-goto-eoh)
      (insert \"Message-ID: <$id>\n\")"
 output=$(notmuch show --format=json "id:$id")
 filename=$(notmuch search --output=files "id:$id")
 # Get length of README after base64-encoding, minus additional newline.
-attachment_length=$(( $(base64 $TEST_DIRECTORY/README | wc -c) - 1 ))
+attachment_length=$(( $(base64 $NOTMUCH_SRCDIR/test/README | wc -c) - 1 ))
 test_expect_equal_json "$output" "[[[{\"id\": \"$id\", \"match\": true, \"excluded\": false, \"filename\": [\"$filename\"], \"timestamp\": 946728000, \"date_relative\": \"2000-01-01\", \"tags\": [\"inbox\"], \"headers\": {\"Subject\": \"$subject\", \"From\": \"Notmuch Test Suite <test_suite@notmuchmail.org>\", \"To\": \"test_suite@notmuchmail.org\", \"Date\": \"Sat, 01 Jan 2000 12:00:00 +0000\"}, \"body\": [{\"id\": 1, \"content-type\": \"multipart/mixed\", \"content\": [{\"id\": 2, \"content-type\": \"text/plain\", \"content\": \"This is a test message with inline attachment with a filename\"}, {\"id\": 3, \"content-type\": \"application/octet-stream\", \"content-length\": $attachment_length, \"content-transfer-encoding\": \"base64\", \"content-disposition\": \"inline\", \"filename\": \"README\"}]}]}, []]]]"
 
 test_begin_subtest "Search message: json, utf-8"
@@ -71,8 +71,8 @@ test_begin_subtest "Format version: too high"
 test_expect_code 21 "notmuch search --format-version=999 \\*"
 
 test_begin_subtest "Show message: multiple filenames"
-add_message "[id]=message-id@example.com [filename]=copy1"
-add_message "[id]=message-id@example.com [filename]=copy2"
+add_message '[id]=message-id@example.com [filename]=copy1 [date]="Fri, 05 Jan 2001 15:43:52 +0000"'
+add_message '[id]=message-id@example.com [filename]=copy2 [date]="Fri, 05 Jan 2001 15:43:52 +0000"'
 cat <<EOF > EXPECTED
 [
     [
index 40e5e21d62cc2c87b2b70e0a67933b932505f40c..c3dcf52a8294d542a80b4dfbdd922095091fd73f 100755 (executable)
@@ -1,6 +1,6 @@
 #!/usr/bin/env bash
 test_description="--format=sexp output"
-. ./test-lib.sh || exit 1
+. $(dirname "$0")/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\""
@@ -26,25 +26,24 @@ add_message "[subject]=\"sexp-show-utf8-body-sübjéct\"" "[date]=\"Sat, 01 Jan
 output=$(notmuch show --format=sexp "jsön-show-méssage")
 test_expect_equal "$output" "((((:id \"${gen_msg_id}\" :match t :excluded nil :filename (\"${gen_msg_filename}\") :timestamp 946728000 :date_relative \"2000-01-01\" :tags (\"inbox\" \"unread\") :headers (:Subject \"sexp-show-utf8-body-sübjéct\" :From \"Notmuch Test Suite <test_suite@notmuchmail.org>\" :To \"Notmuch Test Suite <test_suite@notmuchmail.org>\" :Date \"Sat, 01 Jan 2000 12:00:00 +0000\") :body ((:id 1 :content-type \"text/plain\" :content \"jsön-show-méssage\n\"))) ())))"
 
+test_begin_subtest "Search message: sexp, utf-8"
+add_message "[subject]=\"sexp-search-utf8-body-sübjéct\"" "[date]=\"Sat, 01 Jan 2000 12:00:00 -0000\"" "[body]=\"jsön-search-méssage\""
+output=$(notmuch search --format=sexp "jsön-search-méssage" | notmuch_search_sanitize)
+test_expect_equal "$output" "((:thread \"0000000000000004\" :timestamp 946728000 :date_relative \"2000-01-01\" :matched 1 :total 1 :authors \"Notmuch Test Suite\" :subject \"sexp-search-utf8-body-sübjéct\" :query (\"id:$gen_msg_id\" nil) :tags (\"inbox\" \"unread\")))"
+
 test_begin_subtest "Show message: sexp, inline attachment filename"
 subject='sexp-show-inline-attachment-filename'
 id="sexp-show-inline-attachment-filename@notmuchmail.org"
 emacs_fcc_message \
     "$subject" \
     'This is a test message with inline attachment with a filename' \
-    "(mml-attach-file \"$TEST_DIRECTORY/README\" nil nil \"inline\")
+    "(mml-attach-file \"$NOTMUCH_SRCDIR/test/README\" nil nil \"inline\")
      (message-goto-eoh)
      (insert \"Message-ID: <$id>\n\")"
 output=$(notmuch show --format=sexp "id:$id")
 filename=$(notmuch search --output=files "id:$id")
 # Get length of README after base64-encoding, minus additional newline.
-attachment_length=$(( $(base64 $TEST_DIRECTORY/README | wc -c) - 1 ))
+attachment_length=$(( $(base64 $NOTMUCH_SRCDIR/test/README | wc -c) - 1 ))
 test_expect_equal "$output" "((((:id \"$id\" :match t :excluded nil :filename (\"$filename\") :timestamp 946728000 :date_relative \"2000-01-01\" :tags (\"inbox\") :headers (:Subject \"sexp-show-inline-attachment-filename\" :From \"Notmuch Test Suite <test_suite@notmuchmail.org>\" :To \"test_suite@notmuchmail.org\" :Date \"Sat, 01 Jan 2000 12:00:00 +0000\") :body ((:id 1 :content-type \"multipart/mixed\" :content ((:id 2 :content-type \"text/plain\" :content \"This is a test message with inline attachment with a filename\") (:id 3 :content-type \"application/octet-stream\" :content-disposition \"inline\" :filename \"README\" :content-transfer-encoding \"base64\" :content-length $attachment_length))))) ())))"
 
-test_begin_subtest "Search message: sexp, utf-8"
-add_message "[subject]=\"sexp-search-utf8-body-sübjéct\"" "[date]=\"Sat, 01 Jan 2000 12:00:00 -0000\"" "[body]=\"jsön-search-méssage\""
-output=$(notmuch search --format=sexp "jsön-search-méssage" | notmuch_search_sanitize)
-test_expect_equal "$output" "((:thread \"0000000000000005\" :timestamp 946728000 :date_relative \"2000-01-01\" :matched 1 :total 1 :authors \"Notmuch Test Suite\" :subject \"sexp-search-utf8-body-sübjéct\" :query (\"id:$gen_msg_id\" nil) :tags (\"inbox\" \"unread\")))"
-
-
 test_done
index 3a265dbd5a73a24ef76354258ede1e689d8d8d26..ad2cb1f37c9b21d00d1c78e6179ecb56549c08e2 100755 (executable)
@@ -1,6 +1,6 @@
 #!/usr/bin/env bash
 test_description="--format=text output"
-. ./test-lib.sh || exit 1
+. $(dirname "$0")/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\""
index 94bb0570a3b2b2d5383de80c33a7972e213a013f..f73535b7bf7e7d45afa2ff28a193ac8363a8713d 100755 (executable)
@@ -1,6 +1,6 @@
 #!/usr/bin/env bash
 test_description="output of multipart message"
-. ./test-lib.sh || exit 1
+. $(dirname "$0")/test-lib.sh || exit 1
 
 cat <<EOF > embedded_message_body
 Content-Type: multipart/alternative; boundary="==-=-=="
index 2167ba8ef97333b90b89e9c765c27720461865bc..594d301f7c3a5407c3a6d5b6ab068a0bcda40115 100755 (executable)
@@ -1,6 +1,6 @@
 #!/usr/bin/env bash
 test_description="naming of threads with changing subject"
-. ./test-lib.sh || exit 1
+. $(dirname "$0")/test-lib.sh || exit 1
 
 test_begin_subtest "Initial thread name (oldest-first search)"
 add_message '[subject]="thread-naming: Initial thread subject"' \
index 69d8dc50ff8652f7f40a0d2a591f8f622e77a01d..68b85ced724eff3f57444ad043b01b7153b5f745 100755 (executable)
@@ -1,6 +1,6 @@
 #!/usr/bin/env bash
 test_description="naming of authors with unusual addresses"
-. ./test-lib.sh || exit 1
+. $(dirname "$0")/test-lib.sh || exit 1
 
 test_begin_subtest "Add author with empty quoted real name"
 add_message '[subject]="author-naming: Initial thread subject"' \
index 832a4ad311b708e5b8f86dd2df92d7958fd6c96b..99fdef72e634fad35e91801e2f292a749c615c76 100755 (executable)
@@ -1,7 +1,7 @@
 #!/usr/bin/env bash
 
 test_description='notmuch show --format=raw'
-. ./test-lib.sh || exit 1
+. $(dirname "$0")/test-lib.sh || exit 1
 
 add_message
 add_message
index 4fb67ffbfc0dab7a88075ceb78636ae2da34b7dd..ebe710f98c55a6d9130449be5c5dc47143bff51e 100755 (executable)
@@ -1,6 +1,6 @@
 #!/usr/bin/env bash
 test_description="\"notmuch reply\" in several variations"
-. ./test-lib.sh || exit 1
+. $(dirname "$0")/test-lib.sh || exit 1
 
 test_begin_subtest "Basic reply"
 add_message '[from]="Sender <sender@example.com>"' \
index 608334dc023ecd4bb4d82636bbd60dd8d596b80f..134a106365c4618064218c4f5619af5f5a8d54a3 100755 (executable)
@@ -1,6 +1,6 @@
 #!/usr/bin/env bash
 test_description="\"notmuch reply --reply-to=sender\" in several variations"
-. ./test-lib.sh || exit 1
+. $(dirname "$0")/test-lib.sh || exit 1
 
 test_begin_subtest "Basic reply-to-sender"
 add_message '[from]="Sender <sender@example.com>"' \
index 75fb0b404bbebbf134cc4fe7c040016817245910..0870ff921f1e1642359fe088fff7f0e9ef8a3e63 100755 (executable)
@@ -1,6 +1,6 @@
 #!/usr/bin/env bash
 test_description="\"notmuch dump\" and \"notmuch restore\""
-. ./test-lib.sh || exit 1
+. $(dirname "$0")/test-lib.sh || exit 1
 
 NOTMUCH_NEW > /dev/null
 test_begin_subtest "dump header"
index 6f45d3959bf2d6f020d9ab8731331deb106dd0b7..251c0b40f186c2802d47fbaff1b453ec7374ef0a 100755 (executable)
@@ -1,6 +1,6 @@
 #!/usr/bin/env bash
 test_description="handling of uuencoded data"
-. ./test-lib.sh || exit 1
+. $(dirname "$0")/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
index 89f4d1be4816a8926a0663eeea46db54750e77f3..fea612757c41c4675dd3a95deaea6da7e2ef16a0 100755 (executable)
@@ -1,10 +1,10 @@
 #!/usr/bin/env bash
 test_description="threading when messages received out of order"
-. ./test-lib.sh || exit 1
+. $(dirname "$0")/test-lib.sh || exit 1
 
 # Generate all single-root four message thread structures.  We'll use
 # this for multiple tests below.
-THREADS=$($NOTMUCH_PYTHON ${TEST_DIRECTORY}/gen-threads.py 4)
+THREADS=$($NOTMUCH_PYTHON ${NOTMUCH_SRCDIR}/test/gen-threads.py 4)
 nthreads=$(wc -l <<< "$THREADS")
 
 test_begin_subtest "Messages with one parent get linked in all delivery orders"
index 9124ece6f911a8f9862dfc1d008a45680fab90d0..c28ecb0285158feeef94dfac35f35744d475006e 100755 (executable)
@@ -1,6 +1,6 @@
 #!/usr/bin/env bash
 test_description="author reordering;"
-. ./test-lib.sh || exit 1
+. $(dirname "$0")/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"'
index 7c562fb9e3537c139f058bd2510b72b02ae988c1..b87182323b7209fcf108a3318a0dfd193db98ce4 100755 (executable)
@@ -1,6 +1,6 @@
 #!/usr/bin/env bash
 test_description="From line heuristics (with multiple configured addresses)"
-. ./test-lib.sh || exit 1
+. $(dirname "$0")/test-lib.sh || exit 1
 
 test_begin_subtest "Magic from guessing (nothing to go on)"
 add_message '[from]="Sender <sender@example.com>"' \
index 1fb7c037ee52833c23ea7205d15c4222e572d605..5e3879f5dabf1c52d4665c8d89b9bcf23dced9a0 100755 (executable)
@@ -1,6 +1,6 @@
 #!/usr/bin/env bash
 test_description="messages with ridiculously-long message IDs"
-. ./test-lib.sh || exit 1
+. $(dirname "$0")/test-lib.sh || exit 1
 
 test_begin_subtest "Referencing long ID before adding"
 generate_message '[subject]="Reference of ridiculously-long message ID"' \
index 8d201c7e8aa42d94c316c3e21a51658c58843de0..2c656a1e0950fdcfc72e8599a9b605b047dbe324 100755 (executable)
@@ -1,6 +1,6 @@
 #!/usr/bin/env bash
 test_description="encoding issues"
-. ./test-lib.sh || exit 1
+. $(dirname "$0")/test-lib.sh || exit 1
 
 test_begin_subtest "Message with text of unknown charset"
 add_message '[content-type]="text/plain; charset=unknown-8bit"' \
index fde11790a600ae3591803a3f820bcb42516e798f..ba08167f33eab4c5e475828235eaca7b73df323f 100755 (executable)
@@ -1,15 +1,15 @@
 #!/usr/bin/env bash
 
 test_description="emacs interface"
-. ./test-lib.sh || exit 1
+. $(dirname "$0")/test-lib.sh || exit 1
 
-EXPECTED=$TEST_DIRECTORY/emacs.expected-output
+EXPECTED=$NOTMUCH_SRCDIR/test/emacs.expected-output
 
 add_email_corpus
 
 # syntax errors in test-lib.el cause mysterious failures
 test_begin_subtest "Syntax of emacs test library"
-test_expect_success "${TEST_EMACS} -Q --batch --load $TEST_DIRECTORY/test-lib.el"
+test_expect_success "${TEST_EMACS} -Q --batch --load $NOTMUCH_SRCDIR/test/test-lib.el"
 
 test_begin_subtest "Basic notmuch-hello view in emacs"
 test_emacs '(notmuch-hello)
@@ -401,6 +401,28 @@ Notmuch Test Suite <test_suite@notmuchmail.org> writes:
 EOF
 test_expect_equal_file EXPECTED OUTPUT
 
+test_begin_subtest "Reply within emacs to a message with TAB in subject"
+test_emacs '(let ((message-hidden-headers ''()))
+           (notmuch-search "id:1258471718-6781-1-git-send-email-dottedmag@dottedmag.net")
+           (notmuch-test-wait)
+           (notmuch-search-show-thread)
+           (notmuch-test-wait)
+           (notmuch-show-reply-sender)
+           (test-output))'
+sed -i -e 's/^In-Reply-To: <.*>$/In-Reply-To: <XXX>/' OUTPUT
+sed -i -e 's/^References: <.*>$/References: <XXX>/' OUTPUT
+sed -i -e '/^--text follows this line--$/q' OUTPUT
+cat <<EOF >EXPECTED
+From: Notmuch Test Suite <test_suite@notmuchmail.org>
+To: Mikhail Gusarov <dottedmag@dottedmag.net>
+Subject: Re: [notmuch] [PATCH 1/2] Close message file after parsing message headers
+In-Reply-To: <XXX>
+Fcc: ${MAIL_DIR}/sent
+References: <XXX>
+--text follows this line--
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
 test_begin_subtest "Reply from alternate address within emacs"
 add_message '[from]="Sender <sender@example.com>"' \
             [to]=test_suite_other@notmuchmail.org
index e9d5e3584f87042472a205eefb00db8c115b7ced..f61e8a973fc58b1cd8a74a623aaa48dc786be06a 100755 (executable)
@@ -1,6 +1,6 @@
 #!/usr/bin/env bash
 test_description="Emacs with large search results buffer"
-. ./test-lib.sh || exit 1
+. $(dirname "$0")/test-lib.sh || exit 1
 
 x=xxxxxxxxxx # 10
 x=$x$x$x$x$x$x$x$x$x$x # 100
index 517fa8398261ad7c3aea91d641685c8bdba9baf8..eaf7c980903bed112fc2df81f7af8c7307f1eda1 100755 (executable)
@@ -1,7 +1,7 @@
 #!/usr/bin/env bash
 
 test_description="emacs: mail subject to filename"
-. ./test-lib.sh || exit 1
+. $(dirname "$0")/test-lib.sh || exit 1
 
 # emacs server can't be started in a child process with $(test_emacs ...)
 test_emacs '(ignore)' > /dev/null
index 6d9566354bb32b1b3705dcf6daeb993dc39fa29c..7fece5f2302c1d11f1cdeb861576440b12a1f618 100755 (executable)
@@ -2,7 +2,7 @@
 
 test_description="maildir synchronization"
 
-. ./test-lib.sh || exit 1
+. $(dirname "$0")/test-lib.sh || exit 1
 
 # Create the expected maildir structure
 mkdir $MAIL_DIR/cur
@@ -28,6 +28,14 @@ add_message [subject]='"Adding message with S"' [filename]='adding-with-s-flag:2
 output=$(notmuch search subject:"Adding message with S" | notmuch_search_sanitize)
 test_expect_equal "$output" "thread:XXX   2001-01-05 [1/1] Notmuch Test Suite; Adding message with S (inbox)"
 
+test_begin_subtest "Adding message with 'S' w/o 'unread' in new.tags prevents 'unread' tag"
+OLDCONFIG=$(notmuch config get new.tags)
+notmuch config set new.tags "inbox"
+add_message [subject]='"Adding message with S 2"' [filename]='adding-with-s-flag2:2,S' [dir]=cur
+notmuch config set new.tags $OLDCONFIG
+output=$(notmuch search subject:Adding-message-with-S-2 | notmuch_search_sanitize)
+test_expect_equal "$output" "thread:XXX   2001-01-05 [1/1] Notmuch Test Suite; Adding message with S 2 (inbox)"
+
 test_begin_subtest "Adding 'replied' tag adds 'R' flag to filename"
 add_message [subject]='"Adding replied tag"' [filename]='adding-replied-tag:2,S' [dir]=cur
 notmuch tag +replied subject:"Adding replied tag"
@@ -49,7 +57,7 @@ test_expect_equal_json "$output" '[[[{"id": "XXXXX",
 "Date": "GENERATED_DATE"},
 "body": [{"id": 1,
 "content-type": "text/plain",
-"content": "This is just a test message (#3)\n"}]},
+"content": "This is just a test message (#4)\n"}]},
 []]]]'
 
 test_begin_subtest "notmuch reply works with renamed file (without notmuch new)"
@@ -153,14 +161,14 @@ cp "$MAIL_DIR/cur/duplicated-message:2," "$MAIL_DIR/cur/duplicated-message-copy:
 NOTMUCH_NEW > output
 notmuch search subject:"Duplicated message" | notmuch_search_sanitize >> output
 test_expect_equal "$(< output)" "No new mail.
-thread:XXX   2001-01-05 [1/1] Notmuch Test Suite; Duplicated message (inbox replied)"
+thread:XXX   2001-01-05 [1/1(2)] Notmuch Test Suite; Duplicated message (inbox replied)"
 
 test_begin_subtest "Adding duplicate message without flags does not remove tags"
 cp "$MAIL_DIR/cur/duplicated-message-copy:2,RS" "$MAIL_DIR/cur/duplicated-message-another-copy:2,"
 NOTMUCH_NEW > output
 notmuch search subject:"Duplicated message" | notmuch_search_sanitize >> output
 test_expect_equal "$(< output)" "No new mail.
-thread:XXX   2001-01-05 [1/1] Notmuch Test Suite; Duplicated message (inbox replied)"
+thread:XXX   2001-01-05 [1/1(3)] Notmuch Test Suite; Duplicated message (inbox replied)"
 
 test_begin_subtest "Tag changes modify flags of multiple files"
 notmuch tag -replied subject:"Duplicated message"
@@ -181,11 +189,21 @@ test_expect_equal "$(cd $MAIL_DIR/cur/; ls non-compliant*)" "non-compliant-maild
 
 test_begin_subtest "Files in new/ get default synchronized tags"
 OLDCONFIG=$(notmuch config get new.tags)
-notmuch config set new.tags test
+notmuch config set new.tags "test;unread"
 add_message [subject]='"File in new/"' [dir]=new [filename]='file-in-new'
 notmuch config set new.tags $OLDCONFIG
 notmuch search 'subject:"File in new"' | notmuch_search_sanitize > output
 test_expect_equal "$(< output)" \
 "thread:XXX   2001-01-05 [1/1] Notmuch Test Suite; File in new/ (test unread)"
 
+for tag in draft flagged passed replied; do
+    test_begin_subtest "$tag is valid in new.tags"
+    OLDCONFIG=$(notmuch config get new.tags)
+    notmuch config set new.tags "$tag;unread"
+    add_message [subject]="\"$tag sync in new\"" [dir]=new
+    notmuch config set new.tags $OLDCONFIG
+    notmuch search "subject:\"$tag sync in new\"" | notmuch_search_sanitize > output
+    test_expect_equal "$(< output)" \
+                     "thread:XXX   2001-01-05 [1/1] Notmuch Test Suite; $tag sync in new ($tag unread)"
+done
 test_done
index 7dab39a2763160dd2dbbc946ce9094ba569b19ae..386156776ba4636171a047daad81835cbcc1b59a 100755 (executable)
@@ -5,24 +5,7 @@
 # - verification of signatures from expired/revoked keys
 
 test_description='PGP/MIME signature verification and decryption'
-. ./test-lib.sh || exit 1
-
-add_gnupg_home ()
-{
-    local output
-    [ -d ${GNUPGHOME} ] && return
-    _gnupg_exit () { gpgconf --kill all 2>/dev/null || true; }
-    at_exit_function _gnupg_exit
-    mkdir -m 0700 "$GNUPGHOME"
-    gpg --no-tty --import <$TEST_DIRECTORY/gnupg-secret-key.asc >"$GNUPGHOME"/import.log 2>&1
-    test_debug "cat $GNUPGHOME/import.log"
-    if (gpg --quick-random --version >/dev/null 2>&1) ; then
-       echo quick-random >> "$GNUPGHOME"/gpg.conf
-    elif (gpg --debug-quick-random --version >/dev/null 2>&1) ; then
-       echo debug-quick-random >> "$GNUPGHOME"/gpg.conf
-    fi
-    echo no-emit-version >> "$GNUPGHOME"/gpg.conf
-}
+. $(dirname "$0")/test-lib.sh || exit 1
 
 ##################################################
 
@@ -37,6 +20,10 @@ test_expect_success \
     "This is a test signed message." \
     "(mml-secure-message-sign)"'
 
+test_begin_subtest "signed part content-type indexing"
+output=$(notmuch search mimetype:multipart/signed and mimetype:application/pgp-signature | notmuch_search_sanitize)
+test_expect_equal "$output" "thread:XXX   2000-01-01 [1/1] Notmuch Test Suite; test signed message 001 (inbox signed)"
+
 test_begin_subtest "signature verification"
 output=$(notmuch show --format=json --verify subject:"test signed message 001" \
     | notmuch_json_show_sanitize \
@@ -231,6 +218,10 @@ test_expect_success \
     "This is a test encrypted message.\n" \
     "(mml-attach-file \"TESTATTACHMENT\") (mml-secure-message-encrypt)"'
 
+test_begin_subtest "encrypted part content-type indexing"
+output=$(notmuch search mimetype:multipart/encrypted and mimetype:application/pgp-encrypted and mimetype:application/octet-stream | notmuch_search_sanitize)
+test_expect_equal "$output" "thread:XXX   2000-01-01 [1/1] Notmuch Test Suite; test encrypted message 001 (encrypted inbox)"
+
 test_begin_subtest "decryption, --format=text"
 output=$(notmuch show --format=text --decrypt subject:"test encrypted message 001" \
     | notmuch_show_sanitize_all \
@@ -394,7 +385,7 @@ test_expect_equal_json \
 
 test_begin_subtest "reply to encrypted message"
 output=$(notmuch reply --decrypt subject:"test encrypted message 002" \
-    | grep -v -e '^In-Reply-To:' -e '^References:')
+    | notmuch_drop_mail_headers In-Reply-To References)
 expected='From: Notmuch Test Suite <test_suite@notmuchmail.org>
 Subject: Re: test encrypted message 002
 
index 03d24581de2b2ee81ce894a0e1002bf46069616f..1523f17b9f6f26f184deb01f98efc80a79a790c2 100755 (executable)
@@ -1,7 +1,7 @@
 #!/usr/bin/env bash
 
 test_description='S/MIME signature verification and decryption'
-. ./test-lib.sh || exit 1
+. $(dirname "$0")/test-lib.sh || exit 1
 
 add_gpgsm_home ()
 {
@@ -10,7 +10,7 @@ add_gpgsm_home ()
     _gnupg_exit () { gpgconf --kill all 2>/dev/null || true; }
     at_exit_function _gnupg_exit
     mkdir -m 0700 "$GNUPGHOME"
-    gpgsm --no-tty --no-common-certs-import --disable-dirmngr --import < $TEST_DIRECTORY/smime/test.crt >"$GNUPGHOME"/import.log 2>&1
+    gpgsm --no-tty --no-common-certs-import --disable-dirmngr --import < $NOTMUCH_SRCDIR/test/smime/test.crt >"$GNUPGHOME"/import.log 2>&1
     fpr=$(gpgsm  --list-key test_suite@notmuchmail.org | sed -n 's/.*fingerprint: //p')
     echo "$fpr S relax" >> $GNUPGHOME/trustlist.txt
     test_debug "cat $GNUPGHOME/import.log"
@@ -19,7 +19,7 @@ add_gpgsm_home ()
 test_require_external_prereq openssl
 test_require_external_prereq gpgsm
 
-cp $TEST_DIRECTORY/smime/key+cert.pem test_suite.pem
+cp $NOTMUCH_SRCDIR/test/smime/key+cert.pem test_suite.pem
 
 FINGERPRINT=$(openssl x509 -fingerprint -in test_suite.pem -noout | sed -e 's/^.*=//' -e s/://g)
 
@@ -41,7 +41,7 @@ test_expect_success \
 
 test_begin_subtest "Signature verification (openssl)"
 notmuch show --format=raw subject:"test signed message 001" |\
-    openssl smime -verify -CAfile $TEST_DIRECTORY/smime/test.crt 2>OUTPUT
+    openssl smime -verify -CAfile $NOTMUCH_SRCDIR/test/smime/test.crt 2>OUTPUT
 cat <<EOF > EXPECTED
 Verification successful
 EOF
@@ -86,7 +86,7 @@ test_expect_equal_json \
 test_begin_subtest "Decryption and signature verification (openssl)"
 notmuch show --format=raw subject:"test encrypted message 001" |\
     openssl smime -decrypt -recip test_suite.pem |\
-    openssl smime -verify -CAfile $TEST_DIRECTORY/smime/test.crt 2>OUTPUT
+    openssl smime -verify -CAfile $NOTMUCH_SRCDIR/test/smime/test.crt 2>OUTPUT
 cat <<EOF > EXPECTED
 Verification successful
 EOF
diff --git a/test/T357-index-decryption.sh b/test/T357-index-decryption.sh
new file mode 100755 (executable)
index 0000000..22e716c
--- /dev/null
@@ -0,0 +1,170 @@
+#!/usr/bin/env bash
+
+# TODO: test index.decryption=failed
+
+test_description='indexing decrypted mail'
+. $(dirname "$0")/test-lib.sh || exit 1
+
+##################################################
+
+add_gnupg_home
+# get key fingerprint
+FINGERPRINT=$(gpg --no-tty --list-secret-keys --with-colons --fingerprint | grep '^fpr:' | cut -d: -f10)
+
+# create a test encrypted message
+test_begin_subtest 'emacs delivery of encrypted message'
+test_expect_success \
+'emacs_fcc_message \
+    "test encrypted message for cleartext index 001" \
+    "This is a test encrypted message with a wumpus.\n" \
+    "(mml-secure-message-encrypt)"'
+
+test_begin_subtest "search for unindexed cleartext"
+output=$(notmuch search wumpus)
+expected=''
+test_expect_equal \
+    "$output" \
+    "$expected"
+
+# create a test encrypted message that is indexed in the clear
+test_begin_subtest 'emacs delivery of encrypted message'
+test_expect_success \
+'emacs_fcc_message --try-decrypt=true \
+    "test encrypted message for cleartext index 002" \
+    "This is a test encrypted message with a wumpus.\n" \
+    "(mml-secure-message-encrypt)"'
+
+test_begin_subtest "emacs delivery of encrypted message, indexed cleartext"
+output=$(notmuch search wumpus)
+expected='thread:0000000000000002   2000-01-01 [1/1] Notmuch Test Suite; test encrypted message for cleartext index 002 (encrypted inbox)'
+test_expect_equal \
+    "$output" \
+    "$expected"
+
+# and the same search, but by property ($expected is untouched):
+test_begin_subtest "emacs search by property for one message"
+output=$(notmuch search property:index.decryption=success)
+test_expect_equal \
+    "$output" \
+    "$expected"
+
+
+test_begin_subtest "message should go away after deletion"
+# cache the message in an env var and remove it:
+fname=$(notmuch search --output=files wumpus)
+contents="$(notmuch show --format=raw wumpus)"
+rm -f "$fname"
+notmuch new
+output=$(notmuch search wumpus)
+expected=''
+test_expect_equal \
+    "$output" \
+    "$expected"
+
+# try reinserting it without decryption, should stay the same:
+test_begin_subtest "message cleartext not present after insert"
+notmuch insert --folder=sent <<<"$contents"
+output=$(notmuch search wumpus)
+test_expect_equal \
+    "$output" \
+    "$expected"
+
+# try reinserting it with decryption, should appear again, but now we
+# have two copies of the message:
+test_begin_subtest "message cleartext is present after reinserting with --try-decrypt"
+notmuch insert --folder=sent --try-decrypt <<<"$contents"
+output=$(notmuch search wumpus)
+expected='thread:0000000000000003   2000-01-01 [1/1(2)] Notmuch Test Suite; test encrypted message for cleartext index 002 (encrypted inbox unread)'
+test_expect_equal \
+    "$output" \
+    "$expected"
+
+# remove all copies
+test_begin_subtest "delete all copies of the message"
+mid="$(notmuch search --output=messages wumpus)"
+rm -f $(notmuch search --output=files wumpus)
+notmuch new
+output=$(notmuch search "id:$mid")
+expected=''
+test_expect_equal \
+    "$output" \
+    "$expected"
+
+# try inserting it with decryption, should appear as a single copy
+# (note: i think thread id skips 4 because of duplicate message-id
+# insertion, above)
+test_begin_subtest "message cleartext is present with insert --try-decrypt"
+notmuch insert --folder=sent --try-decrypt <<<"$contents"
+output=$(notmuch search wumpus)
+expected='thread:0000000000000005   2000-01-01 [1/1] Notmuch Test Suite; test encrypted message for cleartext index 002 (encrypted inbox unread)'
+test_expect_equal \
+    "$output" \
+    "$expected"
+
+
+# add a tag to all messages to ensure that it stays after reindexing
+test_begin_subtest 'tagging all messages'
+test_expect_success 'notmuch tag +blarney "encrypted message"'
+test_begin_subtest "verify that tags have not changed"
+output=$(notmuch search tag:blarney)
+expected='thread:0000000000000001   2000-01-01 [1/1] Notmuch Test Suite; test encrypted message for cleartext index 001 (blarney encrypted inbox)
+thread:0000000000000005   2000-01-01 [1/1] Notmuch Test Suite; test encrypted message for cleartext index 002 (blarney encrypted inbox unread)'
+test_expect_equal \
+    "$output" \
+    "$expected"
+
+# see if first message shows up after reindexing with --try-decrypt=true (same $expected, untouched):
+test_begin_subtest 'reindex old messages'
+test_expect_success 'notmuch reindex --try-decrypt=true tag:encrypted and not property:index.decryption=success'
+test_begin_subtest "reindexed encrypted message, including cleartext"
+output=$(notmuch search wumpus)
+test_expect_equal \
+    "$output" \
+    "$expected"
+
+# and the same search, but by property ($expected is untouched):
+test_begin_subtest "emacs search by property for both messages"
+output=$(notmuch search property:index.decryption=success)
+test_expect_equal \
+    "$output" \
+    "$expected"
+
+
+# try to remove cleartext indexing
+test_begin_subtest 'reindex without cleartext'
+test_expect_success 'notmuch reindex tag:encrypted and property:index.decryption=success'
+test_begin_subtest "reindexed encrypted messages, without cleartext"
+output=$(notmuch search wumpus)
+expected=''
+test_expect_equal \
+    "$output" \
+    "$expected"
+
+# and the same search, but by property ($expected is untouched):
+test_begin_subtest "emacs search by property with both messages unindexed"
+output=$(notmuch search property:index.decryption=success)
+test_expect_equal \
+    "$output" \
+    "$expected"
+
+# ensure that the tags remain even when we are dropping the cleartext.
+test_begin_subtest "verify that tags remain without cleartext"
+output=$(notmuch search tag:blarney)
+expected='thread:0000000000000001   2000-01-01 [1/1] Notmuch Test Suite; test encrypted message for cleartext index 001 (blarney encrypted inbox)
+thread:0000000000000005   2000-01-01 [1/1] Notmuch Test Suite; test encrypted message for cleartext index 002 (blarney encrypted inbox unread)'
+test_expect_equal \
+    "$output" \
+    "$expected"
+
+
+# TODO: test removal of a message from the message store between
+# indexing and reindexing.
+
+# TODO: insert the same message into the message store twice, index,
+# remove one of them from the message store, and then reindex.
+# reindexing should return a failure but the message should still be
+# present? -- or what should the semantics be if you ask to reindex a
+# message whose underlying files have been renamed or moved or
+# removed?
+
+test_done
index 9c6d4e647db5dc0e124e470e8ed707121406881c..43921cb4a275eecd00d6ebbc7e614be28c6c25f1 100755 (executable)
@@ -9,7 +9,7 @@
 
 test_description='exception symbol hiding'
 
-. ./test-lib.sh || exit 1
+. $(dirname "$0")/test-lib.sh || exit 1
 
 test_begin_subtest 'running test' run_test
 mkdir -p ${PWD}/fakedb/.notmuch
@@ -26,8 +26,8 @@ test_begin_subtest 'checking output'
 test_expect_equal "$result" "$output"
 
 test_begin_subtest 'comparing existing to exported symbols'
-nm -P $TEST_DIRECTORY/../lib/libnotmuch.so | awk '$2 == "T" && $1 ~ "^notmuch" {print $1}' | sort | uniq > ACTUAL
-sed -n 's/^\(notmuch_[a-zA-Z0-9_]*\)[[:blank:]]*(.*/\1/p' $TEST_DIRECTORY/../lib/notmuch.h | sort | uniq > EXPORTED
+nm -P $NOTMUCH_BUILDDIR/lib/libnotmuch.so | awk '$2 == "T" && $1 ~ "^notmuch" {print $1}' | sort | uniq > ACTUAL
+sed -n 's/^\(notmuch_[a-zA-Z0-9_]*\)[[:blank:]]*(.*/\1/p' $NOTMUCH_SRCDIR/lib/notmuch.h | sort | uniq > EXPORTED
 test_expect_equal_file EXPORTED ACTUAL
 
 test_done
index d1cb45eccc143ed27273f9ef83ccc214306dba6a..0a2727e74fb4573a36294ac3f68260b9f26f3e2e 100755 (executable)
@@ -1,6 +1,6 @@
 #!/usr/bin/env bash
 test_description='folder tags removed and added through file renames remain consistent'
-. ./test-lib.sh || exit 1
+. $(dirname "$0")/test-lib.sh || exit 1
 
 test_begin_subtest "No new messages"
 output=$(NOTMUCH_NEW)
@@ -32,7 +32,7 @@ test_expect_equal_file EXPECTED OUTPUT
 
 test_begin_subtest "Test matches folder:spam"
 output=$(notmuch search folder:spam)
-test_expect_equal "$output" "thread:0000000000000001   2001-01-05 [1/1] Notmuch Test Suite; Single new message (inbox unread)"
+test_expect_equal "$output" "thread:0000000000000001   2001-01-05 [1/1(2)] Notmuch Test Suite; Single new message (inbox unread)"
 
 test_begin_subtest "Remove folder:spam copy of email"
 rm $dir/spam/$(basename $file_x)
index a46a2df2a1febb740a0e5bd180da97224e0677ba..45de22284cb83af03294390bb4d2c52aab20810f 100755 (executable)
@@ -1,6 +1,6 @@
 #!/usr/bin/env bash
 test_description='atomicity'
-. ./test-lib.sh || exit 1
+. $(dirname "$0")/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
@@ -64,7 +64,7 @@ if test_require_external_prereq gdb; then
     # -tty /dev/null works around a conflict between the 'timeout' wrapper
     # and gdb's attempt to control the TTY.
     export MAIL_DIR
-    ${TEST_GDB} -tty /dev/null -batch -x $TEST_DIRECTORY/atomicity.py notmuch 1>gdb.out 2>&1
+    ${TEST_GDB} -tty /dev/null -batch -x $NOTMUCH_SRCDIR/test/atomicity.py notmuch 1>gdb.out 2>&1
 
     # Get the final, golden output
     notmuch search '*' > expected
index a9a61145699b19ca5e61129f91255c7b154e6f72..a93a7f34bef7c1f64042e7cd7c8627c64a7ec185 100755 (executable)
@@ -1,6 +1,6 @@
 #!/usr/bin/env bash
 test_description="python bindings"
-. ./test-lib.sh || exit 1
+. $(dirname "$0")/test-lib.sh || exit 1
 
 test_require_external_prereq ${NOTMUCH_PYTHON}
 
@@ -56,5 +56,22 @@ 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
+add_message '[content-type]="text/plain; charset=iso-8859-2"' \
+            '[content-transfer-encoding]=8bit' \
+            '[subject]="ISO-8859-2 encoded message"' \
+            "[body]=$'Czech word tu\350\362\341\350\350\355 means pinguin\'s.'" # ISO-8859-2 characters are generated by shell's escape sequences
+test_begin_subtest "Add ISO-8859-2 encoded message, call get_message_parts"
+test_python <<EOF
+import notmuch
+db = notmuch.Database(mode=notmuch.Database.MODE.READ_ONLY)
+q_new = notmuch.Query(db, 'ISO-8859-2 encoded message')
+for m in q_new.search_messages():
+    for mp in m.get_message_parts():
+      continue
+    print(m.get_message_id())
+EOF
+
+notmuch search --sort=oldest-first --output=messages "tučňáččí" | sed s/^id:// > EXPECTED
+test_expect_equal_file EXPECTED OUTPUT
 
 test_done
index 52f9fe0f8bfd7e8ee224f50b4b3785c6888a1c69..a0b76eb89ca1ba1da02c861a39f1e280105dbaf6 100755 (executable)
@@ -1,6 +1,6 @@
 #!/usr/bin/env bash
 test_description="ruby bindings"
-. ./test-lib.sh || exit 1
+. $(dirname "$0")/test-lib.sh || exit 1
 
 if [ "${NOTMUCH_HAVE_RUBY_DEV}" = "0" ]; then
     test_subtest_missing_external_prereq_["ruby development files"]=t
index 7917a82f0154ef1d8355e62d7ae30ebb796e3fa8..49c690eb0360ab8c1b6e7f00059ea1c0da79b273 100755 (executable)
@@ -1,6 +1,6 @@
 #!/usr/bin/env bash
 test_description='hooks'
-. ./test-lib.sh || exit 1
+. $(dirname "$0")/test-lib.sh || exit 1
 
 HOOK_DIR=${MAIL_DIR}/.notmuch/hooks
 
index fad134e305c59592b2d6d12dfbb7afa12c9f7266..71ed7e38553bcd9e1794992ecfae56e42a2ea4ec 100755 (executable)
@@ -1,10 +1,11 @@
 #!/usr/bin/env bash
 test_description="argument parsing"
-. ./test-lib.sh || exit 1
+. $(dirname "$0")/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
+$TEST_DIRECTORY/arg-test  pos1  --keyword=one --boolean --string=foo pos2 --int=7 --flag=one --flag=three > OUTPUT
 cat <<EOF > EXPECTED
+boolean 1
 keyword 1
 flags 5
 int 7
@@ -14,4 +15,26 @@ positional arg 2 pos2
 EOF
 test_expect_equal_file EXPECTED OUTPUT
 
+test_begin_subtest "sanity check zero values"
+$TEST_DIRECTORY/arg-test --keyword=zero --boolean=false --int=0 > OUTPUT
+cat <<EOF > EXPECTED
+boolean 0
+keyword 0
+int 0
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "space instead of = between parameter name and value"
+# Note: spaces aren't allowed for booleans. false turns into a positional arg!
+$TEST_DIRECTORY/arg-test --keyword one --boolean false --string foo --int 7 --flag one --flag three > OUTPUT
+cat <<EOF > EXPECTED
+boolean 1
+keyword 1
+flags 5
+int 7
+string foo
+positional arg 1 false
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
 test_done
index 955c5f7ff295beacdf3dc52b61e5a2ef6b2561c0..bfc10be3b7b5101ee213a620a0628962336f9e09 100755 (executable)
@@ -1,7 +1,7 @@
 #!/usr/bin/env bash
 
 test_description="emacs test function sanity"
-. ./test-lib.sh || exit 1
+. $(dirname "$0")/test-lib.sh || exit 1
 
 test_begin_subtest "emacs test function sanity"
 test_emacs_expect_t 't'
index 664b79d293fb0ead36326ab4c8a7e04af1da0c7f..02d3b4117b9407f8fed83330681e45ddfa6d9e13 100755 (executable)
@@ -1,7 +1,7 @@
 #!/usr/bin/env bash
 
 test_description="emacs address cleaning"
-. ./test-lib.sh || exit 1
+. $(dirname "$0")/test-lib.sh || exit 1
 
 test_begin_subtest "notmuch-test-address-clean part 1"
 test_emacs_expect_t '(notmuch-test-address-cleaning-1)'
index ac214a5b2b451c05d0b6d2baa9b733362e8fbf7d..d23c1fca9876bb656065fb7cd9ac77221ee071ca 100755 (executable)
@@ -1,9 +1,9 @@
 #!/usr/bin/env bash
 
 test_description="emacs notmuch-hello view"
-. ./test-lib.sh || exit 1
+. $(dirname "$0")/test-lib.sh || exit 1
 
-EXPECTED=$TEST_DIRECTORY/emacs.expected-output
+EXPECTED=$NOTMUCH_SRCDIR/test/emacs.expected-output
 
 add_email_corpus
 
index db48c7d5b4c0508714f72235c3600719b887bf00..d6aa5b4158b85a10bf67f857fbcfb5c56a428ef4 100755 (executable)
@@ -1,9 +1,9 @@
 #!/usr/bin/env bash
 
 test_description="emacs notmuch-show view"
-. ./test-lib.sh || exit 1
+. $(dirname "$0")/test-lib.sh || exit 1
 
-EXPECTED=$TEST_DIRECTORY/emacs-show.expected-output
+EXPECTED=$NOTMUCH_SRCDIR/test/emacs-show.expected-output
 
 add_email_corpus
 
index 5d6d53a82326c7f58260f83e0798722b5768393e..cb1297caa5dc3f1421c57de1bbeddea41dd3e194 100755 (executable)
@@ -1,7 +1,7 @@
 #!/usr/bin/env bash
 
 test_description="emacs notmuch-show charset handling"
-. ./test-lib.sh || exit 1
+. $(dirname "$0")/test-lib.sh || exit 1
 
 
 UTF8_YEN=$'\xef\xbf\xa5'
index 958ff888a8652127a855ffe2509af61e247cd11a..0f4e4503fd6e472eff47565fe7a0a7252d2c9d27 100755 (executable)
@@ -1,9 +1,9 @@
 #!/usr/bin/env bash
 
 test_description="emacs tree view interface"
-. ./test-lib.sh || exit 1
+. $(dirname "$0")/test-lib.sh || exit 1
 
-EXPECTED=$TEST_DIRECTORY/tree.expected-output
+EXPECTED=$NOTMUCH_SRCDIR/test/tree.expected-output
 
 add_email_corpus
 
index 32031e3174d3005413ac0d652b0654f30cfed21f..4bf5d285c49b43c567f94fb85c556b5d34317efc 100755 (executable)
@@ -1,6 +1,6 @@
 #!/usr/bin/env bash
 test_description='messages with missing headers'
-. ./test-lib.sh || exit 1
+. $(dirname "$0")/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
index 86cf37292d8561b2725fe2594b0240e1c78d511f..2c5bbb6313050f7590a244a14c7d5787a02c5bc5 100755 (executable)
@@ -1,9 +1,9 @@
 #!/usr/bin/env bash
 test_description="hex encoding and decoding"
-. ./test-lib.sh || exit 1
+. $(dirname "$0")/test-lib.sh || exit 1
 
 test_begin_subtest "round trip"
-find $TEST_DIRECTORY/corpus -type f -print | sort | xargs cat > EXPECTED
+find $NOTMUCH_SRCDIR/test/corpora/default -type f -print | sort | xargs cat > EXPECTED
 $TEST_DIRECTORY/hex-xcode --direction=encode < EXPECTED | $TEST_DIRECTORY/hex-xcode --direction=decode > OUTPUT
 test_expect_equal_file EXPECTED OUTPUT
 
@@ -25,7 +25,7 @@ $TEST_DIRECTORY/hex-xcode --direction=decode  < EXPECTED.$test_count |\
 test_expect_equal_file EXPECTED.$test_count OUTPUT.$test_count
 
 test_begin_subtest "round trip (in-place)"
-find $TEST_DIRECTORY/corpus -type f -print | sort | xargs cat > EXPECTED
+find $NOTMUCH_SRCDIR/test/corpora/default -type f -print | sort | xargs cat > EXPECTED
 $TEST_DIRECTORY/hex-xcode --in-place --direction=encode < EXPECTED |\
      $TEST_DIRECTORY/hex-xcode --in-place --direction=decode > OUTPUT
 test_expect_equal_file EXPECTED OUTPUT
index ab90fcc5a76d444c831843d681c2499afc936c2e..d1c70cfaf5d5fca98e66b9e1c6a2709f55fb7731 100755 (executable)
@@ -1,6 +1,6 @@
 #!/usr/bin/env bash
 test_description="date/time parser module"
-. ./test-lib.sh || exit 1
+. $(dirname "$0")/test-lib.sh || exit 1
 
 # Sanity/smoke tests for the date/time parser independent of notmuch
 
index f10207f8cf8e1840ac40628b23089ef657b4e51d..5c5b99a0e2b0545318764f33b1cbdb329d6db974 100755 (executable)
@@ -1,6 +1,6 @@
 #!/usr/bin/env bash
 test_description="date:since..until queries"
-. ./test-lib.sh || exit 1
+. $(dirname "$0")/test-lib.sh || exit 1
 
 add_email_corpus
 
@@ -23,7 +23,7 @@ notmuch search date:18-Nov-2009_02:19:26-0800..2009-11-18_04:49:52-06:00 | notmu
 cat <<EOF >EXPECTED
 thread:XXX   2009-11-18 [1/3] Carl Worth| Jan Janak; [notmuch] What a great idea! (inbox unread)
 thread:XXX   2009-11-18 [1/2] Carl Worth| Jan Janak; [notmuch] [PATCH] Older versions of install do not support -C. (inbox unread)
-thread:XXX   2009-11-18 [1/3] Carl Worth| Aron Griffis, Keith Packard; [notmuch] archive (inbox unread)
+thread:XXX   2009-11-18 [1/3(4)] Carl Worth| Aron Griffis, Keith Packard; [notmuch] archive (inbox unread)
 thread:XXX   2009-11-18 [1/2] Carl Worth| Keith Packard; [notmuch] [PATCH] Make notmuch-show 'X' (and 'x') commands remove inbox (and unread) tags (inbox unread)
 EOF
 test_expect_equal_file EXPECTED OUTPUT
index fa288bb199038ba445206905713b7b32d647ca60..6837ff17663002cfb8c4f9f627f2715ed6d2d154 100755 (executable)
@@ -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 || exit 1
+. $(dirname "$0")/test-lib.sh || exit 1
 
 test_begin_subtest "Use References when In-Reply-To is broken"
 add_message '[id]="foo@one.com"' \
index fb232a32510f77eced30311d02da2fc4a757d73e..16222650eafc68b1d1f816a8cdda96bc3b0ce23a 100755 (executable)
@@ -1,7 +1,7 @@
 #!/usr/bin/env bash
 test_description='"notmuch show"'
 
-. ./test-lib.sh || exit 1
+. $(dirname "$0")/test-lib.sh || exit 1
 
 add_email_corpus
 
index f0fd1511d0567f99fd0ab027a8e627441855431e..69ebec68846482a1a701c72e788eee7bdd9135c6 100755 (executable)
@@ -1,7 +1,7 @@
 #!/usr/bin/env bash
 test_description="database upgrade"
 
-. ./test-lib.sh || exit 1
+. $(dirname "$0")/test-lib.sh || exit 1
 
 dbtarball=database-v1.tar.xz
 
index f94a660d750838b63d6ab263190bbdfef126831d..9d5a9e70a598089793d5d06b136dcbd0ae428ced 100755 (executable)
@@ -1,7 +1,7 @@
 #!/usr/bin/env bash
 test_description="database version and feature compatibility"
 
-. ./test-lib.sh || exit 1
+. $(dirname "$0")/test-lib.sh || exit 1
 
 test_begin_subtest "future database versions abort open"
 ${TEST_DIRECTORY}/make-db-version ${MAIL_DIR} 9999 ""
index 34d15698d9b69c80139c1bc54c3eef95b54d3c73..06a6b860ae8a09f5362a18a3a36acf4a734c1ed4 100755 (executable)
@@ -1,7 +1,7 @@
 #!/usr/bin/env bash
 test_description="error reporting for library"
 
-. ./test-lib.sh || exit 1
+. $(dirname "$0")/test-lib.sh || exit 1
 
 add_email_corpus
 
@@ -127,7 +127,7 @@ int main (int argc, char** argv)
    if (stat != NOTMUCH_STATUS_SUCCESS) {
      fprintf (stderr, "error opening database: %d\n", stat);
    }
-   stat = notmuch_database_add_message (db, "/dev/null", NULL);
+   stat = notmuch_database_index_file (db, "/dev/null", NULL, NULL);
    if (stat)
        fputs (notmuch_database_status_string (db), stderr);
 
@@ -152,7 +152,7 @@ int main (int argc, char** argv)
    if (stat != NOTMUCH_STATUS_SUCCESS) {
      fprintf (stderr, "error opening database: %d\n", stat);
    }
-   stat = notmuch_database_add_message (db, "./nonexistent", NULL);
+   stat = notmuch_database_index_file (db, "./nonexistent", NULL, NULL);
    if (stat) {
        char *status_string = notmuch_database_status_string (db);
        if (status_string) fputs (status_string, stderr);
index 76ad227944a0636b53168c8c0afd401dc26ef767..a59e7c980fc97c7c0fb4658cfe616da63d5a9e15 100755 (executable)
@@ -1,7 +1,7 @@
 #!/usr/bin/env bash
 test_description="database revision tracking"
 
-. ./test-lib.sh || exit 1
+. $(dirname "$0")/test-lib.sh || exit 1
 
 add_email_corpus
 
index 512559a3aff90086809247e2cb3e8ef0a6451e32..01aa3efd2e7949bb170ca817af1d42fa39683ff9 100755 (executable)
@@ -5,7 +5,7 @@
 
 test_description='test of searching by thread-id'
 
-. ./test-lib.sh || exit 1
+. $(dirname "$0")/test-lib.sh || exit 1
 
 add_email_corpus
 
index 1b308693c527cf026f1fa5005e93f20f54588813..46f3a76d574fbd8c485179ac6e241a5cc93ed07a 100755 (executable)
@@ -1,7 +1,7 @@
 #!/usr/bin/env bash
 test_description="library config API"
 
-. ./test-lib.sh || exit 1
+. $(dirname "$0")/test-lib.sh || exit 1
 
 add_email_corpus
 
index 38abc2113c269cc328417312629789054b2d863d..aeb82cf4e3f300b10dfc80b9333fc18f433ce6ac 100755 (executable)
@@ -19,7 +19,7 @@ test_description='thread breakage during reindexing'
 # works properly and attempted fixes to threading issues do not break
 # the expected contents of the index.
 
-. ./test-lib.sh || exit 1
+. $(dirname "$0")/test-lib.sh || exit 1
 
 message_a() {
     mkdir -p ${MAIL_DIR}/cur
@@ -66,7 +66,7 @@ test_thread_count() {
 
 test_ghost_count() {
     test_begin_subtest "${2:-Expecting $1 ghosts(s)}"
-    ghosts=$(../ghost-report ${MAIL_DIR}/.notmuch/xapian)
+    ghosts=$($NOTMUCH_BUILDDIR/test/ghost-report ${MAIL_DIR}/.notmuch/xapian)
     test_expect_equal "$ghosts" "$1"
 }
 
@@ -111,7 +111,7 @@ test_content_count banana 0
 test_begin_subtest 'No ghosts should remain after deletion of second message'
 # this is known to fail; we are leaking ghost messages deliberately
 test_subtest_known_broken
-ghosts=$(../ghost-report ${MAIL_DIR}/.notmuch/xapian)
+ghosts=$($NOTMUCH_BUILDDIR/test/ghost-report ${MAIL_DIR}/.notmuch/xapian)
 test_expect_equal "$ghosts" "0"
 
 rm -f ${MAIL_DIR}/cur/a
index 495b7699a0a781beee246fb6070bdb67e3ae584d..59496c3e94a15d490c1f7f598fbfe8506dd99e5c 100755 (executable)
@@ -1,6 +1,6 @@
 #!/usr/bin/env bash
 test_description='named queries'
-. ./test-lib.sh || exit 1
+. $(dirname "$0")/test-lib.sh || exit 1
 
 QUERYSTR="date:2009-11-18..2009-11-18 and tag:unread"
 
index ba5f55daf9b189176a2955be325d98847094728f..74b3f5a1811f5fa6664f157d28480d6214b25cf6 100755 (executable)
@@ -1,7 +1,7 @@
 #!/usr/bin/env bash
 test_description="message property API"
 
-. ./test-lib.sh || exit 1
+. $(dirname "$0")/test-lib.sh || exit 1
 
 add_email_corpus
 
index f46475e8ddccf6dc1926b7cf198da3d088592194..085ffe4377c2c3a034bd991dc1450723b6004549 100755 (executable)
@@ -1,6 +1,6 @@
 #!/usr/bin/env bash
 test_description="locking"
-. ./test-lib.sh || exit 1
+. $(dirname "$0")/test-lib.sh || exit 1
 
 if [ "${NOTMUCH_HAVE_XAPIAN_DB_RETRY_LOCK}" = "0" ]; then
     test_subtest_missing_external_prereq_["lock retry support"]=t
index cd9e33a7c4057505986338b8716fd231b30e8643..d7903ce799da7ac846fce8c50f0c521f70650adc 100755 (executable)
@@ -1,6 +1,6 @@
 #!/usr/bin/env bash
 test_description="Emacs Draft Handling"
-. ./test-lib.sh || exit 1
+. $(dirname "$0")/test-lib.sh || exit 1
 
 add_email_corpus
 
index e35e35f64289d5b84c11b5c0d244e7d3f6e09728..274105c708fa2d4c73429520e3811fcdce6debff 100755 (executable)
@@ -1,6 +1,6 @@
 #!/usr/bin/env bash
 test_description="DatabaseModifiedError handling"
-. ./test-lib.sh || exit 1
+. $(dirname "$0")/test-lib.sh || exit 1
 
 # add enough messages to trigger the exception
 add_email_corpus
index b7bdda118e97850f4c8c25bb9607c313fd4b1123..4085340f66d42b270c13ced3c5eb845fa2cc48c6 100755 (executable)
@@ -1,6 +1,6 @@
 #!/usr/bin/env bash
 test_description='regular expression searches'
-. ./test-lib.sh || exit 1
+. $(dirname "$0")/test-lib.sh || exit 1
 
 if [ $NOTMUCH_HAVE_XAPIAN_FIELD_PROCESSOR -eq 0 ]; then
     test_done
@@ -29,7 +29,7 @@ test_expect_equal_file EXPECTED OUTPUT
 test_begin_subtest "unanchored folder:// specification"
 output=$(notmuch search folder:/bad/ | notmuch_search_sanitize)
 test_expect_equal "$output" "thread:XXX   2001-01-05 [1/1] Notmuch Test Suite; To the bone (inbox unread)
-thread:XXX   2001-01-05 [1/1] Notmuch Test Suite; Bears (inbox unread)
+thread:XXX   2001-01-05 [1/1(2)] Notmuch Test Suite; Bears (inbox unread)
 thread:XXX   2001-01-05 [1/1] Notmuch Test Suite; Bites, stings, sad feelings (inbox unread)"
 
 test_begin_subtest "anchored folder:// search"
@@ -39,7 +39,7 @@ test_expect_equal "$output" "thread:XXX   2001-01-05 [1/1] Notmuch Test Suite; T
 test_begin_subtest "unanchored path:// specification"
 output=$(notmuch search path:/bad/ | notmuch_search_sanitize)
 test_expect_equal "$output" "thread:XXX   2001-01-05 [1/1] Notmuch Test Suite; To the bone (inbox unread)
-thread:XXX   2001-01-05 [1/1] Notmuch Test Suite; Bears (inbox unread)
+thread:XXX   2001-01-05 [1/1(2)] Notmuch Test Suite; Bears (inbox unread)
 thread:XXX   2001-01-05 [1/1] Notmuch Test Suite; Bites, stings, sad feelings (inbox unread)"
 
 test_begin_subtest "anchored path:// search"
index a98e11c88714aace6ec417d50de92fce2da916cf..f65544b9ffdf70d07f1f94a4c9b3448dcc84aa5a 100755 (executable)
@@ -1,6 +1,6 @@
 #!/usr/bin/env bash
 test_description="parsing of bad dates"
-. ./test-lib.sh || exit 1
+. $(dirname "$0")/test-lib.sh || exit 1
 
 add_message [date]='"()"'
 
diff --git a/test/T670-duplicate-mid.sh b/test/T670-duplicate-mid.sh
new file mode 100755 (executable)
index 0000000..c198c50
--- /dev/null
@@ -0,0 +1,102 @@
+#!/usr/bin/env bash
+test_description="duplicate message ids"
+. $(dirname "$0")/test-lib.sh || exit 1
+
+add_message '[id]="duplicate"' '[subject]="message 1" [filename]=copy1'
+add_message '[id]="duplicate"' '[subject]="message 2" [filename]=copy2'
+
+add_message '[id]="duplicate"' '[subject]="message 0" [filename]=copy0'
+test_begin_subtest 'search: first indexed subject preserved'
+cat <<EOF > EXPECTED
+thread:XXX   2001-01-05 [1/1(3)] Notmuch Test Suite; message 1 (inbox unread)
+EOF
+notmuch search id:duplicate | notmuch_search_sanitize > OUTPUT
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest 'First subject preserved in notmuch-show (json)'
+test_subtest_known_broken
+output=$(notmuch show --body=false --format=json id:duplicate | notmuch_json_show_sanitize)
+expected='[[[{
+    "id": "XXXXX",
+    "match": true,
+    "excluded": false,
+    "filename": [
+        "'"${MAIL_DIR}"/copy0'",
+        "'"${MAIL_DIR}"/copy1'",
+        "'"${MAIL_DIR}"/copy2'"
+    ],
+    "timestamp": 42,
+    "date_relative": "2001-01-05",
+    "tags": ["inbox","unread"],
+    "headers": {
+        "Subject": "message 1",
+        "From": "Notmuch Test Suite <test_suite@notmuchmail.org>",
+        "To": "Notmuch Test Suite <test_suite@notmuchmail.org>",
+        "Date": "GENERATED_DATE"
+    }
+ },
+[]]]]'
+test_expect_equal_json "$output" "$expected"
+
+test_begin_subtest 'Search for second subject'
+cat <<EOF >EXPECTED
+MAIL_DIR/copy0
+MAIL_DIR/copy1
+MAIL_DIR/copy2
+EOF
+notmuch search --output=files subject:'"message 2"' | notmuch_dir_sanitize > OUTPUT
+test_expect_equal_file EXPECTED OUTPUT
+
+add_message '[id]="duplicate"' '[body]="sekrit" [filename]=copy3'
+test_begin_subtest 'search for body in duplicate file'
+cat <<EOF >EXPECTED
+MAIL_DIR/copy0
+MAIL_DIR/copy1
+MAIL_DIR/copy2
+MAIL_DIR/copy3
+EOF
+notmuch search --output=files "sekrit" | notmuch_dir_sanitize > OUTPUT
+test_expect_equal_file EXPECTED OUTPUT
+
+rm ${MAIL_DIR}/copy3
+test_begin_subtest 'reindex drops terms in duplicate file'
+cp /dev/null EXPECTED
+notmuch reindex '*'
+notmuch search --output=files "sekrit" | notmuch_dir_sanitize > OUTPUT
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest 'reindex choses subject from first filename'
+cat <<EOF > EXPECTED
+thread:XXX   2001-01-05 [1/1(3)] Notmuch Test Suite; message 0 (inbox unread)
+EOF
+notmuch search id:duplicate | notmuch_search_sanitize > OUTPUT
+test_expect_equal_file EXPECTED OUTPUT
+
+rm ${MAIL_DIR}/copy0
+test_begin_subtest 'Deleted first duplicate file does not stop notmuch show from working'
+output=$(notmuch show --body=false --format=json id:duplicate |
+            notmuch_json_show_sanitize | sed 's/message [0-9]/A_SUBJECT/')
+expected='[[[{
+    "id": "XXXXX",
+    "match": true,
+    "excluded": false,
+    "filename": [
+        "'"${MAIL_DIR}"/copy0'",
+        "'"${MAIL_DIR}"/copy1'",
+        "'"${MAIL_DIR}"/copy2'"
+    ],
+    "timestamp": 42,
+    "date_relative": "2001-01-05",
+    "tags": ["inbox","unread"],
+    "headers": {
+        "Subject": "A_SUBJECT",
+        "From": "Notmuch Test Suite <test_suite@notmuchmail.org>",
+        "To": "Notmuch Test Suite <test_suite@notmuchmail.org>",
+        "Date": "GENERATED_DATE"
+    }
+ },
+[]]]]'
+
+test_expect_equal_json "$output" "$expected"
+
+test_done
index 74f33708be93e70bd59b6dd921a08df1fa8ea7fa..62ba849852310beaf815a5c30712991edefd0720 100755 (executable)
@@ -1,6 +1,6 @@
 #!/usr/bin/env bash
 test_description="indexing of html parts"
-. ./test-lib.sh || exit 1
+. $(dirname "$0")/test-lib.sh || exit 1
 
 add_email_corpus html
 
index a4f4b5f50f8da02e58761abf1457e34ee0722a12..9aa47611a93545b12d43e9046a995e1d4a702098 100755 (executable)
@@ -1,7 +1,7 @@
 #!/usr/bin/env bash
 
 test_description="command line arguments"
-. ./test-lib.sh || exit 1
+. $(dirname "$0")/test-lib.sh || exit 1
 
 add_message
 
diff --git a/test/T700-reindex.sh b/test/T700-reindex.sh
new file mode 100755 (executable)
index 0000000..2b7bc65
--- /dev/null
@@ -0,0 +1,79 @@
+#!/usr/bin/env bash
+test_description='reindexing messages'
+. $(dirname "$0")/test-lib.sh || exit 1
+
+add_email_corpus
+
+notmuch tag +usertag1 '*'
+
+notmuch search '*' | notmuch_search_sanitize > initial-threads
+notmuch search --output=messages '*' > initial-message-ids
+notmuch dump > initial-dump
+
+test_begin_subtest 'reindex preserves threads'
+notmuch reindex '*'
+notmuch search '*' | notmuch_search_sanitize > OUTPUT
+test_expect_equal_file initial-threads OUTPUT
+
+test_begin_subtest 'reindex after removing duplicate file preserves threads'
+# remove one copy
+sed 's,3/3(4),3/3,' < initial-threads > EXPECTED
+mv $MAIL_DIR/bar/18:2, duplicate-msg-1.eml
+notmuch reindex '*'
+notmuch search '*' | notmuch_search_sanitize > OUTPUT
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest 'reindex preserves message-ids'
+notmuch reindex '*'
+notmuch search --output=messages '*' > OUTPUT
+test_expect_equal_file initial-message-ids OUTPUT
+
+test_begin_subtest 'reindex preserves tags'
+notmuch reindex '*'
+notmuch dump > OUTPUT
+test_expect_equal_file initial-dump OUTPUT
+
+test_begin_subtest 'reindex moves a message between threads'
+notmuch search --output=threads id:87iqd9rn3l.fsf@vertex.dottedmag > EXPECTED
+# re-parent
+sed -i 's/1258471718-6781-1-git-send-email-dottedmag@dottedmag.net/87iqd9rn3l.fsf@vertex.dottedmag/' $MAIL_DIR/02:2,*
+notmuch reindex id:1258471718-6781-2-git-send-email-dottedmag@dottedmag.net
+notmuch search --output=threads id:1258471718-6781-2-git-send-email-dottedmag@dottedmag.net > OUTPUT
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest 'reindex detects removal of all files'
+notmuch search --output=messages not id:20091117232137.GA7669@griffis1.net> EXPECTED
+# remove both copies
+mv $MAIL_DIR/cur/51:2,* duplicate-message-2.eml
+notmuch reindex id:20091117232137.GA7669@griffis1.net
+notmuch search --output=messages '*' > OUTPUT
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "reindex preserves properties"
+cat <<EOF > prop-dump
+#= 1258471718-6781-1-git-send-email-dottedmag@dottedmag.net userprop=userval
+#= 1258471718-6781-2-git-send-email-dottedmag@dottedmag.net userprop=userval
+#= 1258491078-29658-1-git-send-email-dottedmag@dottedmag.net userprop=userval1
+#= 20091117190054.GU3165@dottiness.seas.harvard.edu userprop=userval
+#= 20091117203301.GV3165@dottiness.seas.harvard.edu userprop=userval3
+#= 87fx8can9z.fsf@vertex.dottedmag userprop=userval2
+#= 87iqd9rn3l.fsf@vertex.dottedmag userprop=userval
+#= 87lji4lx9v.fsf@yoom.home.cworth.org userprop=userval3
+#= 87lji5cbwo.fsf@yoom.home.cworth.org userprop=userval
+#= cf0c4d610911171136h1713aa59w9cf9aa31f052ad0a@mail.gmail.com userprop=userval
+EOF
+notmuch restore < prop-dump
+notmuch reindex '*'
+notmuch dump | grep '^#=' | sort > OUTPUT
+test_expect_equal_file prop-dump OUTPUT
+test_done
+
+add_email_corpus lkml
+
+test_begin_subtest "reindex of lkml corpus preserves threads"
+notmuch search '*' | notmuch_search_sanitize > EXPECTED
+notmuch reindex '*'
+notmuch search '*' | notmuch_search_sanitize > OUTPUT
+test_expect_equal_file EXPECTED OUTPUT
+
+test_done
index 736686ded2c07287f1daf237a5b6c22f71e5bb82..7aff825507a53dca4f39fe4ffe99df344133fb88 100644 (file)
@@ -9,47 +9,61 @@ int main(int argc, char **argv){
     int kw_val=0;
     int fl_val=0;
     int int_val=0;
-    char *pos_arg1=NULL;
-    char *pos_arg2=NULL;
-    char *string_val=NULL;
-
-    notmuch_opt_desc_t options[] = {
-       { NOTMUCH_OPT_KEYWORD, &kw_val, "keyword", 'k',
-         (notmuch_keyword_t []){ { "one", 1 },
-                                 { "two", 2 },
-                                 { 0, 0 } } },
-       { NOTMUCH_OPT_KEYWORD_FLAGS, &fl_val, "flag", 'f',
+    const char *pos_arg1=NULL;
+    const char *pos_arg2=NULL;
+    const char *string_val=NULL;
+    bool bool_val = false;
+    bool fl_set = false, int_set = false, bool_set = false,
+       kw_set = false, string_set = false, pos1_set = false, pos2_set = false;
+
+    notmuch_opt_desc_t parent_options[] = {
+       { .opt_flags = &fl_val, .name = "flag", .present = &fl_set, .keywords =
          (notmuch_keyword_t []){ { "one",   1 << 0},
                                  { "two",   1 << 1 },
                                  { "three", 1 << 2 },
                                  { 0, 0 } } },
-       { NOTMUCH_OPT_INT, &int_val, "int", 'i', 0},
-       { NOTMUCH_OPT_STRING, &string_val, "string", 's', 0},
-       { NOTMUCH_OPT_POSITION, &pos_arg1, 0,0, 0},
-       { NOTMUCH_OPT_POSITION, &pos_arg2, 0,0, 0},
-       { 0, 0, 0, 0, 0 } };
+       { .opt_int = &int_val, .name = "int", .present = &int_set },
+       { }
+    };
+
+    notmuch_opt_desc_t options[] = {
+       { .opt_bool = &bool_val, .name = "boolean", .present = &bool_set },
+       { .opt_keyword = &kw_val, .name = "keyword", .present = &kw_set, .keywords =
+         (notmuch_keyword_t []){ { "zero", 0 },
+                                 { "one", 1 },
+                                 { "two", 2 },
+                                 { 0, 0 } } },
+       { .opt_inherit = parent_options },
+       { .opt_string = &string_val, .name = "string", .present = &string_set },
+       { .opt_position = &pos_arg1, .present = &pos1_set },
+       { .opt_position = &pos_arg2, .present = &pos2_set },
+       { }
+    };
 
     opt_index = parse_arguments(argc, argv, options, 1);
 
     if (opt_index < 0)
        return 1;
 
-    if (kw_val)
+    if (bool_set)
+       printf("boolean %d\n", bool_val);
+
+    if (kw_set)
        printf("keyword %d\n", kw_val);
 
-    if (fl_val)
+    if (fl_set)
        printf("flags %d\n", fl_val);
 
-    if (int_val)
+    if (int_set)
        printf("int %d\n", int_val);
 
-    if (string_val)
+    if (string_set)
        printf("string %s\n", string_val);
 
-    if (pos_arg1)
+    if (pos1_set)
        printf("positional arg 1 %s\n", pos_arg1);
 
-    if (pos_arg2)
+    if (pos2_set)
        printf("positional arg 2 %s\n", pos_arg2);
 
 
diff --git a/test/export-dirs.sh b/test/export-dirs.sh
new file mode 100644 (file)
index 0000000..0578b1e
--- /dev/null
@@ -0,0 +1,32 @@
+# Source this script to set and export NOTMUCH_SRCDIR and
+# NOTMUCH_BUILDDIR.
+#
+# For this to work, always have current directory somewhere within the
+# build directory hierarchy, and run the script sourcing this script
+# using a path (relative or absolute) to the source directory.
+
+if [[ -z "${NOTMUCH_SRCDIR}" ]]; then
+       export NOTMUCH_SRCDIR="$(cd "$(dirname "$0")"/.. && pwd)"
+fi
+
+find_builddir()
+{
+       local dir="$1"
+
+       while [[ -n "$dir" ]] && [[ "$dir" != "/" ]]; do
+               if [[ -x "$dir/notmuch" ]] && [[ ! -d "$dir/notmuch" ]]; then
+                       echo "$dir"
+                       break
+               fi
+               dir="$(dirname "$dir")"
+       done
+}
+
+if [[ -z "${NOTMUCH_BUILDDIR}" ]]; then
+       export NOTMUCH_BUILDDIR="$(find_builddir "$(pwd)")"
+
+       if [[ -z "${NOTMUCH_BUILDDIR}" ]]; then
+               echo "Run tests in a subdir of built notmuch tree." >&2
+               exit 1
+       fi
+fi
index 65d49564a3e1369f546d8b55102c5affbdcb9c29..33046e9a51785378d5f5003469941f1f428a2755 100644 (file)
@@ -16,7 +16,7 @@ enum direction {
     DECODE
 };
 
-static int inplace = FALSE;
+static bool inplace = false;
 
 static int
 xcode (void *ctx, enum direction dir, char *in, char **buf_p, size_t *size_p)
@@ -44,17 +44,17 @@ int
 main (int argc, char **argv)
 {
 
-    enum direction dir = DECODE;
-    int omit_newline = FALSE;
+    int dir = DECODE;
+    bool omit_newline = false;
 
     notmuch_opt_desc_t options[] = {
-       { NOTMUCH_OPT_KEYWORD, &dir, "direction", 'd',
+       { .opt_keyword = &dir, .name = "direction", .keywords =
          (notmuch_keyword_t []){ { "encode", ENCODE },
                                  { "decode", DECODE },
                                  { 0, 0 } } },
-       { NOTMUCH_OPT_BOOLEAN, &omit_newline, "omit-newline", 'n', 0 },
-       { NOTMUCH_OPT_BOOLEAN, &inplace, "in-place", 'i', 0 },
-       { 0, 0, 0, 0, 0 }
+       { .opt_bool = &omit_newline, .name = "omit-newline" },
+       { .opt_bool = &inplace, .name = "in-place" },
+       { }
     };
 
     int opt_index = parse_arguments (argc, argv, options, 1);
@@ -71,7 +71,7 @@ main (int argc, char **argv)
     char *buffer = NULL;
     size_t buf_size = 0;
 
-    notmuch_bool_t read_stdin = TRUE;
+    bool read_stdin = true;
 
     for (; opt_index < argc; opt_index++) {
 
@@ -82,7 +82,7 @@ main (int argc, char **argv)
        if (! omit_newline)
            putchar ('\n');
 
-       read_stdin = FALSE;
+       read_stdin = false;
     }
 
     if (! read_stdin)
index e7d3151ce8cbb528c68d6526ba4c39a035d5a1eb..ca68dd416cfc17ab5132538b8fe36db37a6cbc5a 100755 (executable)
@@ -15,12 +15,20 @@ if [ ${BASH_VERSINFO[0]} -lt 4 ]; then
     exit 1
 fi
 
-cd "$(dirname "$0")"
+# Ensure NOTMUCH_SRCDIR and NOTMUCH_BUILDDIR are set.
+. $(dirname "$0")/export-dirs.sh || exit 1
 
-TESTS=${NOTMUCH_TESTS:-T[0-9][0-9][0-9]-*.sh}
+TESTS=
+for test in $NOTMUCH_TESTS; do
+    TESTS="$TESTS $NOTMUCH_SRCDIR/test/$test"
+done
+
+if [[ -z "$TESTS" ]]; then
+    TESTS="$NOTMUCH_SRCDIR/test/T[0-9][0-9][0-9]-*.sh"
+fi
 
 # Clean up any results from a previous run
-rm -rf test-results
+rm -rf $NOTMUCH_BUILDDIR/test/test-results
 
 # Test for timeout utility
 if command -v timeout >/dev/null; then
@@ -33,12 +41,13 @@ fi
 trap 'e=$?; kill $!; exit $e' HUP INT TERM
 # Run the tests
 for test in $TESTS; do
-    $TEST_TIMEOUT_CMD ./$test "$@" &
+    $TEST_TIMEOUT_CMD $test "$@" &
     wait $!
     # If the test failed without producing results, then it aborted,
     # so we should abort, too.
     RES=$?
-    if [[ $RES != 0 && ! -e "test-results/${test%.sh}" ]]; then
+    testname=$(basename $test .sh)
+    if [[ $RES != 0 && ! -e "$NOTMUCH_BUILDDIR/test/test-results/$testname" ]]; then
         exit $RES
     fi
 done
@@ -46,10 +55,10 @@ trap - HUP INT TERM
 
 # Report results
 echo
-./aggregate-results.sh test-results/*
+$NOTMUCH_SRCDIR/test/aggregate-results.sh $NOTMUCH_BUILDDIR/test/test-results/*
 ev=$?
 
 # Clean up
-rm -rf test-results corpus.mail
+rm -rf $NOTMUCH_BUILDDIR/test/test-results $NOTMUCH_BUILDDIR/test/corpora.mail
 
 exit $ev
index aca694a3c1d364c12d3a0ca2c05984ddda261433..9272afda8bedab235293a870ed9de794943e6dc0 100644 (file)
@@ -116,10 +116,10 @@ random_utf8_string (void *ctx, size_t char_count)
 
 /* 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;
+const char *notmuch_requested_db_uuid = NULL;
 
 void
 notmuch_process_shared_options (unused (const char *dummy))
@@ -140,7 +140,7 @@ main (int argc, char **argv)
 
     void *ctx = talloc_new (NULL);
 
-    char *config_path  = NULL;
+    const char *config_path = NULL;
     notmuch_config_t *config;
     notmuch_database_t *notmuch;
 
@@ -155,13 +155,13 @@ main (int argc, char **argv)
     int seed = 734569;
 
     notmuch_opt_desc_t options[] = {
-       { NOTMUCH_OPT_STRING, &config_path, "config-path", 'c', 0 },
-       { NOTMUCH_OPT_INT, &num_messages, "num-messages", 'n', 0 },
-       { NOTMUCH_OPT_INT, &max_tags, "max-tags", 'm', 0 },
-       { NOTMUCH_OPT_INT, &message_id_len, "message-id-len", 'M', 0 },
-       { NOTMUCH_OPT_INT, &tag_len, "tag-len", 't', 0 },
-       { NOTMUCH_OPT_INT, &seed, "seed", 's', 0 },
-       { 0, 0, 0, 0, 0 }
+       { .opt_string = &config_path, .name = "config-path" },
+       { .opt_int = &num_messages, .name = "num-messages" },
+       { .opt_int = &max_tags, .name = "max-tags" },
+       { .opt_int = &message_id_len, .name = "message-id-len" },
+       { .opt_int = &tag_len, .name = "tag-len" },
+       { .opt_int = &seed, .name = "seed" },
+       { }
     };
 
     int opt_index = parse_arguments (argc, argv, options, 1);
@@ -179,7 +179,7 @@ main (int argc, char **argv)
        exit (1);
     }
 
-    config = notmuch_config_open (ctx, config_path, FALSE);
+    config = notmuch_config_open (ctx, config_path, false);
     if (config == NULL)
        return 1;
 
index a629927c263c461582a872a961ae59f6329f3e29..71992edd1d968d5d0d14907ef505f0a0427f3051 100644 (file)
 static void
 receive_data_to_file (FILE *peer, FILE *output)
 {
-       char *line = NULL;
-       size_t line_size;
-       ssize_t line_len;
+    char *line = NULL;
+    size_t line_size;
+    ssize_t line_len;
 
-       while ((line_len = getline (&line, &line_size, peer)) != -1) {
-               if (STRNCMP_LITERAL (line, ".\r\n") == 0)
-                       break;
-               if (line_len < 2)
-                       continue;
-               if (line[line_len-1] == '\n' && line[line_len-2] == '\r') {
-                       line[line_len-2] = '\n';
-                       line[line_len-1] = '\0';
-               }
-               fprintf (output, "%s",
-                        line[0] == '.' ? line + 1 : line);
+    while ((line_len = getline (&line, &line_size, peer)) != -1) {
+       if (STRNCMP_LITERAL (line, ".\r\n") == 0)
+           break;
+       if (line_len < 2)
+           continue;
+       if (line[line_len - 1] == '\n' && line[line_len - 2] == '\r') {
+           line[line_len - 2] = '\n';
+           line[line_len - 1] = '\0';
        }
+       fprintf (output, "%s",
+                line[0] == '.' ? line + 1 : line);
+    }
 
-       free (line);
+    free (line);
 }
 
 static int
 process_command (FILE *peer, FILE *output, const char *command)
 {
-       if (STRNCMP_LITERAL (command, "EHLO ") == 0) {
-               fprintf (peer, "502 not implemented\r\n");
-               fflush (peer);
-       } else if (STRNCMP_LITERAL (command, "HELO ") == 0) {
-               fprintf (peer, "250 localhost\r\n");
-               fflush (peer);
-       } else if (STRNCMP_LITERAL (command, "MAIL FROM:") == 0 ||
-                  STRNCMP_LITERAL (command, "RCPT TO:") == 0) {
-               fprintf (peer, "250 OK\r\n");
-               fflush (peer);
-       } else if (STRNCMP_LITERAL (command, "DATA") == 0) {
-               fprintf (peer, "354 End data with <CR><LF>.<CR><LF>\r\n");
-               fflush (peer);
-               receive_data_to_file (peer, output);
-               fprintf (peer, "250 OK\r\n");
-               fflush (peer);
-       } else if (STRNCMP_LITERAL (command, "QUIT") == 0) {
-               fprintf (peer, "221 BYE\r\n");
-               fflush (peer);
-               return 1;
-       } else {
-               fprintf (stderr, "Unknown command: %s\n", command);
-       }
-       return 0;
+    if (STRNCMP_LITERAL (command, "EHLO ") == 0) {
+       fprintf (peer, "502 not implemented\r\n");
+       fflush (peer);
+    } else if (STRNCMP_LITERAL (command, "HELO ") == 0) {
+       fprintf (peer, "250 localhost\r\n");
+       fflush (peer);
+    } else if (STRNCMP_LITERAL (command, "MAIL FROM:") == 0 ||
+              STRNCMP_LITERAL (command, "RCPT TO:") == 0) {
+       fprintf (peer, "250 OK\r\n");
+       fflush (peer);
+    } else if (STRNCMP_LITERAL (command, "DATA") == 0) {
+       fprintf (peer, "354 End data with <CR><LF>.<CR><LF>\r\n");
+       fflush (peer);
+       receive_data_to_file (peer, output);
+       fprintf (peer, "250 OK\r\n");
+       fflush (peer);
+    } else if (STRNCMP_LITERAL (command, "QUIT") == 0) {
+       fprintf (peer, "221 BYE\r\n");
+       fflush (peer);
+       return 1;
+    } else {
+       fprintf (stderr, "Unknown command: %s\n", command);
+    }
+    return 0;
 }
 
 static void
 do_smtp_to_file (FILE *peer, FILE *output)
 {
-       char *line = NULL;
-       size_t line_size;
-       ssize_t line_len;
+    char *line = NULL;
+    size_t line_size;
+    ssize_t line_len;
 
-       fprintf (peer, "220 localhost smtp-dummy\r\n");
-       fflush (peer);
+    fprintf (peer, "220 localhost smtp-dummy\r\n");
+    fflush (peer);
 
-       while ((line_len = getline (&line, &line_size, peer)) != -1) {
-               if (process_command (peer, output, line))
-                       break;
-       }
+    while ((line_len = getline (&line, &line_size, peer)) != -1) {
+       if (process_command (peer, output, line))
+           break;
+    }
 
-       free (line);
+    free (line);
 }
 
 int
 main (int argc, char *argv[])
 {
-       const char * progname;
-       char *output_filename;
-       FILE *peer_file, *output;
-       int sock, peer, err;
-       struct sockaddr_in addr, peer_addr;
-       struct hostent *hostinfo;
-       socklen_t peer_addr_len;
-       int reuse;
-       int background;
+    const char *progname;
+    char *output_filename;
+    FILE *peer_file = NULL, *output = NULL;
+    int sock = -1, peer, err;
+    struct sockaddr_in addr, peer_addr;
+    struct hostent *hostinfo;
+    socklen_t peer_addr_len;
+    int reuse;
+    int background;
+    int ret = 0;
 
-       progname = argv[0];
+    progname = argv[0];
 
-       background = 0;
-       for (; argc >= 2; argc--, argv++) {
-               if (argv[1][0] != '-')
-                       break;
-               if (strcmp (argv[1], "--") == 0) {
-                       argc--;
-                       argv++;
-                       break;
-               }
-               if (strcmp (argv[1], "--background") == 0) {
-                       background = 1;
-                       continue;
-               }
-               fprintf(stderr, "%s: unregognized option '%s'\n",
-                       progname, argv[1]);
-               return 1;
+    background = 0;
+    for (; argc >= 2; argc--, argv++) {
+       if (argv[1][0] != '-')
+           break;
+       if (strcmp (argv[1], "--") == 0) {
+           argc--;
+           argv++;
+           break;
        }
-
-       if (argc != 2) {
-               fprintf (stderr,
-                        "Usage: %s [--background] <output-file>\n", progname);
-               return 1;
+       if (strcmp (argv[1], "--background") == 0) {
+           background = 1;
+           continue;
        }
+       fprintf (stderr, "%s: unregognized option '%s'\n",
+                progname, argv[1]);
+       return 1;
+    }
 
-       output_filename = argv[1];
-       output = fopen (output_filename, "w");
-       if (output == NULL) {
-               fprintf (stderr, "Failed to open %s for writing: %s\n",
-                        output_filename, strerror (errno));
-               return 1;
-       }
+    if (argc != 2) {
+       fprintf (stderr,
+                "Usage: %s [--background] <output-file>\n", progname);
+       return 1;
+    }
 
-       sock = socket (AF_INET, SOCK_STREAM, 0);
-       if (sock == -1) {
-               fprintf (stderr, "Error: socket() failed: %s\n",
-                        strerror (errno));
-               return 1;
-       }
+    output_filename = argv[1];
+    output = fopen (output_filename, "w");
+    if (output == NULL) {
+       fprintf (stderr, "Failed to open %s for writing: %s\n",
+                output_filename, strerror (errno));
+       ret = 1;
+       goto DONE;
+    }
 
-       reuse = 1;
-       err = setsockopt (sock, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof (reuse));
-       if (err) {
-               fprintf (stderr, "Error: setsockopt() failed: %s\n",
-                        strerror (errno));
-               return 1;
-       }
+    sock = socket (AF_INET, SOCK_STREAM, 0);
+    if (sock == -1) {
+       fprintf (stderr, "Error: socket() failed: %s\n",
+                strerror (errno));
+       ret = 1;
+       goto DONE;
+    }
 
-       hostinfo = gethostbyname ("localhost");
-       if (hostinfo == NULL) {
-               fprintf (stderr, "Unknown host: localhost\n");
-               return 1;
-       }
+    reuse = 1;
+    err = setsockopt (sock, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof (reuse));
+    if (err) {
+       fprintf (stderr, "Error: setsockopt() failed: %s\n",
+                strerror (errno));
+       ret = 1;
+       goto DONE;
+    }
 
-       memset (&addr, 0, sizeof (addr));
-       addr.sin_family = AF_INET;
-       addr.sin_port = htons (25025);
-       addr.sin_addr = *(struct in_addr *) hostinfo->h_addr;
-       err = bind (sock, (struct sockaddr *) &addr, sizeof(addr));
-       if (err) {
-               fprintf (stderr, "Error: bind() failed: %s\n",
-                        strerror (errno));
-               close (sock);
-               return 1;
-       }
+    hostinfo = gethostbyname ("localhost");
+    if (hostinfo == NULL) {
+       fprintf (stderr, "Unknown host: localhost\n");
+       ret = 1;
+       goto DONE;
+    }
 
-       err = listen (sock, 1);
-       if (err) {
-               fprintf (stderr, "Error: listen() failed: %s\n",
-                        strerror (errno));
-               close (sock);
-               return 1;
-       }
+    memset (&addr, 0, sizeof (addr));
+    addr.sin_family = AF_INET;
+    addr.sin_port = htons (25025);
+    addr.sin_addr = *(struct in_addr *) hostinfo->h_addr;
+    err = bind (sock, (struct sockaddr *) &addr, sizeof (addr));
+    if (err) {
+       fprintf (stderr, "Error: bind() failed: %s\n",
+                strerror (errno));
+       close (sock);
+       ret = 1;
+       goto DONE;
+    }
 
-       if (background) {
-               int pid = fork ();
-               if (pid > 0) {
-                       printf ("smtp_dummy_pid='%d'\n", pid);
-                       fflush (stdout);
-                       close (sock);
-                       return 0;
-               }
-               if (pid < 0) {
-                       fprintf (stderr, "Error: fork() failed: %s\n",
-                                strerror (errno));
-                       close (sock);
-                       return 1;
-               }
-               /* Reached if pid == 0 (the child process). */
-               /* Close stdout so that the one interested in pid value will
-                  also get EOF. */
-               close (STDOUT_FILENO);
-               /* dup2() will re-reserve fd of stdout (1) (opportunistically),
-                  in case fd of stderr (2) is open. If that was not open we
-                  don't care fd of stdout (1) either. */
-               dup2 (STDERR_FILENO, STDOUT_FILENO);
+    err = listen (sock, 1);
+    if (err) {
+       fprintf (stderr, "Error: listen() failed: %s\n",
+                strerror (errno));
+       close (sock);
+       ret = 1;
+       goto DONE;
+    }
 
-               /* This process is now out of reach of shell's job control.
-                  To resolve the rare but possible condition where this
-                  "daemon" is started but never connected this process will
-                  (only) have 30 seconds to exist. */
-               alarm (30);
+    if (background) {
+       int pid = fork ();
+       if (pid > 0) {
+           printf ("smtp_dummy_pid='%d'\n", pid);
+           fflush (stdout);
+           close (sock);
+           ret = 0;
+           goto DONE;
        }
-
-       peer_addr_len = sizeof (peer_addr);
-       peer = accept (sock, (struct sockaddr *) &peer_addr, &peer_addr_len);
-       if (peer == -1) {
-               fprintf (stderr, "Error: accept() failed: %s\n",
-                        strerror (errno));
-               return 1;
+       if (pid < 0) {
+           fprintf (stderr, "Error: fork() failed: %s\n",
+                    strerror (errno));
+           close (sock);
+           ret = 1;
+           goto DONE;
        }
+       /* Reached if pid == 0 (the child process). */
+       /* Close stdout so that the one interested in pid value will
+        * also get EOF. */
+       close (STDOUT_FILENO);
+       /* dup2() will re-reserve fd of stdout (1) (opportunistically),
+        * in case fd of stderr (2) is open. If that was not open we
+        * don't care fd of stdout (1) either. */
+       dup2 (STDERR_FILENO, STDOUT_FILENO);
 
-       peer_file = fdopen (peer, "w+");
-       if (peer_file == NULL) {
-               fprintf (stderr, "Error: fdopen() failed: %s\n",
-                        strerror (errno));
-               return 1;
-       }
+       /* This process is now out of reach of shell's job control.
+        * To resolve the rare but possible condition where this
+        * "daemon" is started but never connected this process will
+        * (only) have 30 seconds to exist. */
+       alarm (30);
+    }
+
+    peer_addr_len = sizeof (peer_addr);
+    peer = accept (sock, (struct sockaddr *) &peer_addr, &peer_addr_len);
+    if (peer == -1) {
+       fprintf (stderr, "Error: accept() failed: %s\n",
+                strerror (errno));
+       ret = 1;
+       goto DONE;
+    }
+
+    peer_file = fdopen (peer, "w+");
+    if (peer_file == NULL) {
+       fprintf (stderr, "Error: fdopen() failed: %s\n",
+                strerror (errno));
+       ret = 1;
+       goto DONE;
+    }
 
-       do_smtp_to_file (peer_file, output);
+    do_smtp_to_file (peer_file, output);
 
+ DONE:
+    if (output)
        fclose (output);
+    if (peer_file)
        fclose (peer_file);
+    if (sock >= 0)
        close (sock);
 
-       return 0;
+    return ret;
 }
index b5624b74f80412bfa5317d790615a2605fca7095..9452199f13a84781558a555477c4e6b62d8148dd 100644 (file)
@@ -1 +1 @@
-*.tar.xz
+/*.tar.xz
index ef409171fa86d547f43d5b940c7afd6e98d5b08d..6c3571d4c56001b65402ac4658662684b8613839 100644 (file)
 #
 type die >/dev/null 2>&1 || die () { echo "$@" >&2; exit 1; }
 
-find_notmuch_path ()
-{
-    dir="$1"
-
-    while [ -n "$dir" ]; do
-       bin="$dir/notmuch"
-       if [ -x "$bin" ]; then
-           echo "$dir"
-           return
-       fi
-       dir="$(dirname "$dir")"
-       if [ "$dir" = "/" ]; then
-           break
-       fi
-    done
-}
+if [[ -z "$NOTMUCH_SRCDIR" ]] || [[ -z "$NOTMUCH_BUILDDIR" ]]; then
+       echo "internal: srcdir or builddir not set" >&2
+       exit 1
+fi
 
 backup_database () {
     test_name=$(basename $0 .sh)
-    rm -rf notmuch-dir-backup."$test_name"
-    cp -pR ${MAIL_DIR}/.notmuch notmuch-dir-backup."${test_name}"
+    rm -rf $TMP_DIRECTORY/notmuch-dir-backup."$test_name"
+    cp -pR ${MAIL_DIR}/.notmuch $TMP_DIRECTORY/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
+    cp -pR $TMP_DIRECTORY/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 -P)
-notmuch_path=`find_notmuch_path "$TEST_DIRECTORY"`
+TEST_DIRECTORY=$NOTMUCH_BUILDDIR/test
 
 # Prepend $TEST_DIRECTORY/../lib to LD_LIBRARY_PATH, to make tests work
 # on systems where ../notmuch depends on LD_LIBRARY_PATH.
@@ -64,13 +51,190 @@ LD_LIBRARY_PATH=${TEST_DIRECTORY%/*}/lib${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}
 export LD_LIBRARY_PATH
 
 # configure output
-. $notmuch_path/sh.config || exit 1
+. "$NOTMUCH_BUILDDIR/sh.config" || exit 1
 
 # load OS specifics
-if [ -e ./test-lib-$PLATFORM.sh ]; then
-       . ./test-lib-$PLATFORM.sh || exit 1
+if [[ -e "$NOTMUCH_SRCDIR/test/test-lib-$PLATFORM.sh" ]]; then
+    . "$NOTMUCH_SRCDIR/test/test-lib-$PLATFORM.sh" || exit 1
 fi
 
+# Generate a new message in the mail directory, with a unique message
+# ID and subject. The message is not added to the index.
+#
+# After this function returns, the filename of the generated message
+# is available as $gen_msg_filename and the message ID is available as
+# $gen_msg_id .
+#
+# This function supports named parameters with the bash syntax for
+# assigning a value to an associative array ([name]=value). The
+# supported parameters are:
+#
+#  [dir]=directory/of/choice
+#
+#      Generate the message in directory 'directory/of/choice' within
+#      the mail store. The directory will be created if necessary.
+#
+#  [filename]=name
+#
+#      Store the message in file 'name'. The default is to store it
+#      in 'msg-<count>', where <count> is three-digit number of the
+#      message.
+#
+#  [body]=text
+#
+#      Text to use as the body of the email message
+#
+#  '[from]="Some User <user@example.com>"'
+#  '[to]="Some User <user@example.com>"'
+#  '[subject]="Subject of email message"'
+#  '[date]="RFC 822 Date"'
+#
+#      Values for email headers. If not provided, default values will
+#      be generated instead.
+#
+#  '[cc]="Some User <user@example.com>"'
+#  [reply-to]=some-address
+#  [in-reply-to]=<message-id>
+#  [references]=<message-id>
+#  [content-type]=content-type-specification
+#  '[header]=full header line, including keyword'
+#
+#      Additional values for email headers. If these are not provided
+#      then the relevant headers will simply not appear in the
+#      message.
+#
+#  '[id]=message-id'
+#
+#      Controls the message-id of the created message.
+gen_msg_cnt=0
+gen_msg_filename=""
+gen_msg_id=""
+generate_message ()
+{
+    # This is our (bash-specific) magic for doing named parameters
+    local -A template="($@)"
+    local additional_headers
+
+    gen_msg_cnt=$((gen_msg_cnt + 1))
+    if [ -z "${template[filename]}" ]; then
+       gen_msg_name="msg-$(printf "%03d" $gen_msg_cnt)"
+    else
+       gen_msg_name=${template[filename]}
+    fi
+
+    if [ -z "${template[id]}" ]; then
+       gen_msg_id="${gen_msg_name%:2,*}@notmuch-test-suite"
+    else
+       gen_msg_id="${template[id]}"
+    fi
+
+    if [ -z "${template[dir]}" ]; then
+       gen_msg_filename="${MAIL_DIR}/$gen_msg_name"
+    else
+       gen_msg_filename="${MAIL_DIR}/${template[dir]}/$gen_msg_name"
+       mkdir -p "$(dirname "$gen_msg_filename")"
+    fi
+
+    if [ -z "${template[body]}" ]; then
+       template[body]="This is just a test message (#${gen_msg_cnt})"
+    fi
+
+    if [ -z "${template[from]}" ]; then
+       template[from]="Notmuch Test Suite <test_suite@notmuchmail.org>"
+    fi
+
+    if [ -z "${template[to]}" ]; then
+       template[to]="Notmuch Test Suite <test_suite@notmuchmail.org>"
+    fi
+
+    if [ -z "${template[subject]}" ]; then
+       if [ -n "$test_subtest_name" ]; then
+           template[subject]="$test_subtest_name"
+       else
+           template[subject]="Test message #${gen_msg_cnt}"
+       fi
+    elif [ "${template[subject]}" = "@FORCE_EMPTY" ]; then
+       template[subject]=""
+    fi
+
+    if [ -z "${template[date]}" ]; then
+       # we use decreasing timestamps here for historical reasons;
+       # the existing test suite when we converted to unique timestamps just
+       # happened to have signicantly fewer failures with that choice.
+       local date_secs=$((978709437 - gen_msg_cnt))
+       # printf %(..)T is bash 4.2+ feature. use perl fallback if needed...
+       TZ=UTC printf -v template[date] "%(%a, %d %b %Y %T %z)T" $date_secs 2>/dev/null ||
+           template[date]=`perl -le 'use POSIX "strftime";
+                               @time = gmtime '"$date_secs"';
+                               print strftime "%a, %d %b %Y %T +0000", @time'`
+    fi
+
+    additional_headers=""
+    if [ ! -z "${template[header]}" ]; then
+       additional_headers="${template[header]}
+${additional_headers}"
+    fi
+
+    if [ ! -z "${template[reply-to]}" ]; then
+       additional_headers="Reply-To: ${template[reply-to]}
+${additional_headers}"
+    fi
+
+    if [ ! -z "${template[in-reply-to]}" ]; then
+       additional_headers="In-Reply-To: ${template[in-reply-to]}
+${additional_headers}"
+    fi
+
+    if [ ! -z "${template[cc]}" ]; then
+       additional_headers="Cc: ${template[cc]}
+${additional_headers}"
+    fi
+
+    if [ ! -z "${template[bcc]}" ]; then
+       additional_headers="Bcc: ${template[bcc]}
+${additional_headers}"
+    fi
+
+    if [ ! -z "${template[references]}" ]; then
+       additional_headers="References: ${template[references]}
+${additional_headers}"
+    fi
+
+    if [ ! -z "${template[content-type]}" ]; then
+       additional_headers="Content-Type: ${template[content-type]}
+${additional_headers}"
+    fi
+
+    if [ ! -z "${template[content-transfer-encoding]}" ]; then
+       additional_headers="Content-Transfer-Encoding: ${template[content-transfer-encoding]}
+${additional_headers}"
+    fi
+
+    # Note that in the way we're setting it above and using it below,
+    # `additional_headers' will also serve as the header / body separator
+    # (empty line in between).
+
+    cat <<EOF >"$gen_msg_filename"
+From: ${template[from]}
+To: ${template[to]}
+Message-Id: <${gen_msg_id}>
+Subject: ${template[subject]}
+Date: ${template[date]}
+${additional_headers}
+${template[body]}
+EOF
+}
+
+# Generate a new message and add it to the database.
+#
+# All of the arguments and return values supported by generate_message
+# are also supported here, so see that function for details.
+add_message ()
+{
+    generate_message "$@" &&
+    notmuch new > /dev/null
+}
+
 if test -n "$valgrind"
 then
        make_symlink () {
@@ -131,25 +295,21 @@ then
        PATH=$GIT_VALGRIND/bin:$PATH
        GIT_EXEC_PATH=$GIT_VALGRIND/bin
        export GIT_VALGRIND
-       test -n "$notmuch_path" && MANPATH="$notmuch_path/doc/_build/man"
+       test -n "$NOTMUCH_BUILDDIR" && MANPATH="$NOTMUCH_BUILDDIR/doc/_build/man"
 else # normal case
-       if test -n "$notmuch_path"
+       if test -n "$NOTMUCH_BUILDDIR"
                then
-                       PATH="$notmuch_path:$PATH"
-                       MANPATH="$notmuch_path/doc/_build/man"
+                       PATH="$NOTMUCH_BUILDDIR:$PATH"
+                       MANPATH="$NOTMUCH_BUILDDIR/doc/_build/man"
                fi
 fi
 export PATH MANPATH
 
 # Test repository
 test="tmp.$(basename "$0" .sh)"
-test -n "$root" && test="$root/$test"
-case "$test" in
-/*) TMP_DIRECTORY="$test" ;;
- *) TMP_DIRECTORY="$TEST_DIRECTORY/$test" ;;
-esac
+TMP_DIRECTORY="$TEST_DIRECTORY/$test"
 test ! -z "$debug" || remove_tmp=$TMP_DIRECTORY
-rm -fr "$test" || {
+rm -rf "$TMP_DIRECTORY" || {
        GIT_EXIT_OK=t
        echo >&6 "FATAL: Cannot prepare test area"
        exit 1
@@ -165,7 +325,6 @@ mkdir -p "${HOME}"
 MAIL_DIR="${TMP_DIRECTORY}/mail"
 export NOTMUCH_CONFIG="${TMP_DIRECTORY}/notmuch-config"
 
-mkdir -p "${test}"
 mkdir -p "${MAIL_DIR}"
 
 cat <<EOF >"${NOTMUCH_CONFIG}"
index 7ae96c6999aaed84007b99e4e2a6c8a6beb754da..42a45f158105f8c4885ff4ebed5512297d7e118a 100644 (file)
@@ -26,6 +26,15 @@ fi
 # Make sure echo builtin does not expand backslash-escape sequences by default.
 shopt -u xpg_echo
 
+# Ensure NOTMUCH_SRCDIR and NOTMUCH_BUILDDIR are set.
+. $(dirname "$0")/export-dirs.sh || exit 1
+
+# It appears that people try to run tests without building...
+if [[ ! -x "$NOTMUCH_BUILDDIR/notmuch" ]]; then
+       echo >&2 'You do not seem to have built notmuch yet.'
+       exit 1
+fi
+
 this_test=${0##*/}
 this_test=${this_test%.sh}
 this_test_bare=${this_test#T[0-9][0-9][0-9]-}
@@ -93,6 +102,23 @@ unset GREP_OPTIONS
 # For emacsclient
 unset ALTERNATE_EDITOR
 
+add_gnupg_home ()
+{
+    local output
+    [ -d ${GNUPGHOME} ] && return
+    _gnupg_exit () { gpgconf --kill all 2>/dev/null || true; }
+    at_exit_function _gnupg_exit
+    mkdir -m 0700 "$GNUPGHOME"
+    gpg --no-tty --import <$NOTMUCH_SRCDIR/test/gnupg-secret-key.asc >"$GNUPGHOME"/import.log 2>&1
+    test_debug "cat $GNUPGHOME/import.log"
+    if (gpg --quick-random --version >/dev/null 2>&1) ; then
+       echo quick-random >> "$GNUPGHOME"/gpg.conf
+    elif (gpg --debug-quick-random --version >/dev/null 2>&1) ; then
+       echo debug-quick-random >> "$GNUPGHOME"/gpg.conf
+    fi
+    echo no-emit-version >> "$GNUPGHOME"/gpg.conf
+}
+
 # Each test should start with something like this, after copyright notices:
 #
 # test_description='Description of this test...
@@ -134,9 +160,6 @@ do
                valgrind=t; verbose=t; shift ;;
        --tee)
                shift ;; # was handled already
-       --root=*)
-               root=$(expr "z$1" : 'z[^=]*=\(.*\)')
-               shift ;;
        *)
                echo "error: unknown test option '$1'" >&2; exit 1 ;;
        esac
@@ -276,183 +299,6 @@ export GNUPGHOME="${TEST_TMPDIR}/gnupg"
 trap 'trap_exit' EXIT
 trap 'trap_signal' HUP INT TERM
 
-# Generate a new message in the mail directory, with a unique message
-# ID and subject. The message is not added to the index.
-#
-# After this function returns, the filename of the generated message
-# is available as $gen_msg_filename and the message ID is available as
-# $gen_msg_id .
-#
-# This function supports named parameters with the bash syntax for
-# assigning a value to an associative array ([name]=value). The
-# supported parameters are:
-#
-#  [dir]=directory/of/choice
-#
-#      Generate the message in directory 'directory/of/choice' within
-#      the mail store. The directory will be created if necessary.
-#
-#  [filename]=name
-#
-#      Store the message in file 'name'. The default is to store it
-#      in 'msg-<count>', where <count> is three-digit number of the
-#      message.
-#
-#  [body]=text
-#
-#      Text to use as the body of the email message
-#
-#  '[from]="Some User <user@example.com>"'
-#  '[to]="Some User <user@example.com>"'
-#  '[subject]="Subject of email message"'
-#  '[date]="RFC 822 Date"'
-#
-#      Values for email headers. If not provided, default values will
-#      be generated instead.
-#
-#  '[cc]="Some User <user@example.com>"'
-#  [reply-to]=some-address
-#  [in-reply-to]=<message-id>
-#  [references]=<message-id>
-#  [content-type]=content-type-specification
-#  '[header]=full header line, including keyword'
-#
-#      Additional values for email headers. If these are not provided
-#      then the relevant headers will simply not appear in the
-#      message.
-#
-#  '[id]=message-id'
-#
-#      Controls the message-id of the created message.
-gen_msg_cnt=0
-gen_msg_filename=""
-gen_msg_id=""
-generate_message ()
-{
-    # This is our (bash-specific) magic for doing named parameters
-    local -A template="($@)"
-    local additional_headers
-
-    gen_msg_cnt=$((gen_msg_cnt + 1))
-    if [ -z "${template[filename]}" ]; then
-       gen_msg_name="msg-$(printf "%03d" $gen_msg_cnt)"
-    else
-       gen_msg_name=${template[filename]}
-    fi
-
-    if [ -z "${template[id]}" ]; then
-       gen_msg_id="${gen_msg_name%:2,*}@notmuch-test-suite"
-    else
-       gen_msg_id="${template[id]}"
-    fi
-
-    if [ -z "${template[dir]}" ]; then
-       gen_msg_filename="${MAIL_DIR}/$gen_msg_name"
-    else
-       gen_msg_filename="${MAIL_DIR}/${template[dir]}/$gen_msg_name"
-       mkdir -p "$(dirname "$gen_msg_filename")"
-    fi
-
-    if [ -z "${template[body]}" ]; then
-       template[body]="This is just a test message (#${gen_msg_cnt})"
-    fi
-
-    if [ -z "${template[from]}" ]; then
-       template[from]="Notmuch Test Suite <test_suite@notmuchmail.org>"
-    fi
-
-    if [ -z "${template[to]}" ]; then
-       template[to]="Notmuch Test Suite <test_suite@notmuchmail.org>"
-    fi
-
-    if [ -z "${template[subject]}" ]; then
-       if [ -n "$test_subtest_name" ]; then
-           template[subject]="$test_subtest_name"
-       else
-           template[subject]="Test message #${gen_msg_cnt}"
-       fi
-    elif [ "${template[subject]}" = "@FORCE_EMPTY" ]; then
-       template[subject]=""
-    fi
-
-    if [ -z "${template[date]}" ]; then
-       # we use decreasing timestamps here for historical reasons;
-       # the existing test suite when we converted to unique timestamps just
-       # happened to have signicantly fewer failures with that choice.
-       local date_secs=$((978709437 - gen_msg_cnt))
-       # printf %(..)T is bash 4.2+ feature. use perl fallback if needed...
-       TZ=UTC printf -v template[date] "%(%a, %d %b %Y %T %z)T" $date_secs 2>/dev/null ||
-           template[date]=`perl -le 'use POSIX "strftime";
-                               @time = gmtime '"$date_secs"';
-                               print strftime "%a, %d %b %Y %T +0000", @time'`
-    fi
-
-    additional_headers=""
-    if [ ! -z "${template[header]}" ]; then
-       additional_headers="${template[header]}
-${additional_headers}"
-    fi
-
-    if [ ! -z "${template[reply-to]}" ]; then
-       additional_headers="Reply-To: ${template[reply-to]}
-${additional_headers}"
-    fi
-
-    if [ ! -z "${template[in-reply-to]}" ]; then
-       additional_headers="In-Reply-To: ${template[in-reply-to]}
-${additional_headers}"
-    fi
-
-    if [ ! -z "${template[cc]}" ]; then
-       additional_headers="Cc: ${template[cc]}
-${additional_headers}"
-    fi
-
-    if [ ! -z "${template[bcc]}" ]; then
-       additional_headers="Bcc: ${template[bcc]}
-${additional_headers}"
-    fi
-
-    if [ ! -z "${template[references]}" ]; then
-       additional_headers="References: ${template[references]}
-${additional_headers}"
-    fi
-
-    if [ ! -z "${template[content-type]}" ]; then
-       additional_headers="Content-Type: ${template[content-type]}
-${additional_headers}"
-    fi
-
-    if [ ! -z "${template[content-transfer-encoding]}" ]; then
-       additional_headers="Content-Transfer-Encoding: ${template[content-transfer-encoding]}
-${additional_headers}"
-    fi
-
-    # Note that in the way we're setting it above and using it below,
-    # `additional_headers' will also serve as the header / body separator
-    # (empty line in between).
-
-    cat <<EOF >"$gen_msg_filename"
-From: ${template[from]}
-To: ${template[to]}
-Message-Id: <${gen_msg_id}>
-Subject: ${template[subject]}
-Date: ${template[date]}
-${additional_headers}
-${template[body]}
-EOF
-}
-
-# Generate a new message and add it to the database.
-#
-# All of the arguments and return values supported by generate_message
-# are also supported here, so see that function for details.
-add_message ()
-{
-    generate_message "$@" &&
-    notmuch new > /dev/null
-}
-
 # Deliver a message with emacs and add it to the database
 #
 # Uses emacs to generate and deliver a message to the mail store.
@@ -500,8 +346,17 @@ emacs_deliver_message ()
 # Accepts arbitrary extra emacs/elisp functions to modify the message
 # before sending, which is useful to doing things like attaching files
 # to the message and encrypting/signing.
+#
+# If any GNU-style long-arguments (like --quiet or --try-decrypt=true) are
+# at the head of the argument list, they are sent directly to "notmuch
+# new" after message delivery
 emacs_fcc_message ()
 {
+    local nmn_args=''
+    while [[ "$1" =~ ^-- ]]; do
+        nmn_args="$nmn_args $1"
+        shift
+    done
     local subject="$1"
     local body="$2"
     shift 2
@@ -520,7 +375,7 @@ emacs_fcc_message ()
           (insert \"${body}\")
           $@
           (notmuch-mua-send-and-exit))" || return 1
-    notmuch new >/dev/null
+    notmuch new $nmn_args >/dev/null
 }
 
 # Add an existing, fixed corpus of email to the database.
@@ -539,7 +394,7 @@ add_email_corpus ()
     if [ -d $TEST_DIRECTORY/corpora.mail/$corpus ]; then
        cp -a $TEST_DIRECTORY/corpora.mail/$corpus ${MAIL_DIR}
     else
-       cp -a $TEST_DIRECTORY/corpora/$corpus ${MAIL_DIR}
+       cp -a $NOTMUCH_SRCDIR/test/corpora/$corpus ${MAIL_DIR}
        notmuch new >/dev/null || die "'notmuch new' failed while adding email corpus"
        mkdir -p $TEST_DIRECTORY/corpora.mail
        cp -a ${MAIL_DIR} $TEST_DIRECTORY/corpora.mail/$corpus
@@ -627,9 +482,10 @@ 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 $NOTMUCH_PYTHON -mjson.tool \
+    local script='import json, sys; json.dump(json.load(sys.stdin), sys.stdout, sort_keys=True, indent=4)'
+    output=$(echo "$1" | PYTHONIOENCODING=utf-8 $NOTMUCH_PYTHON -c "$script" \
         || echo "$1")
-    expected=$(echo "$2" | PYTHONIOENCODING=utf-8 $NOTMUCH_PYTHON -mjson.tool \
+    expected=$(echo "$2" | PYTHONIOENCODING=utf-8 $NOTMUCH_PYTHON -c "$script" \
         || echo "$2")
     shift 2
     test_expect_equal "$output" "$expected" "$@"
@@ -683,6 +539,16 @@ NOTMUCH_DUMP_TAGS ()
     notmuch dump --include=tags "${@}" | sed '/^#/d' | sort
 }
 
+notmuch_drop_mail_headers ()
+{
+    $NOTMUCH_PYTHON -c '
+import email, sys
+msg = email.message_from_file(sys.stdin)
+for hdr in sys.argv[1:]: del msg[hdr]
+print(msg.as_string(False))
+' "$@"
+}
+
 notmuch_search_sanitize ()
 {
     perl -pe 's/("?thread"?: ?)("?)................("?)/\1\2XXX\3/'
@@ -1077,8 +943,8 @@ export NOTMUCH_CONFIG=$NOTMUCH_CONFIG
 # --load               Force loading of notmuch.el and test-lib.el
 
 exec ${TEST_EMACS} --quick \
-       --directory "$TEST_DIRECTORY/../emacs" --load notmuch.el \
-       --directory "$TEST_DIRECTORY" --load test-lib.el \
+       --directory "$NOTMUCH_SRCDIR/emacs" --load notmuch.el \
+       --directory "$NOTMUCH_SRCDIR/test" --load test-lib.el \
        "\$@"
 EOF
        chmod a+x "$TMP_DIRECTORY/run_emacs"
@@ -1093,8 +959,8 @@ test_emacs () {
        test -z "$missing_dependencies" || return
 
        if [ -z "$EMACS_SERVER" ]; then
-               emacs_tests="${this_test_bare}.el"
-               if [ -f "$TEST_DIRECTORY/$emacs_tests" ]; then
+               emacs_tests="$NOTMUCH_SRCDIR/test/${this_test_bare}.el"
+               if [ -f "$emacs_tests" ]; then
                        load_emacs_tests="--eval '(load \"$emacs_tests\")'"
                else
                        load_emacs_tests=
@@ -1137,14 +1003,14 @@ test_python() {
 }
 
 test_ruby() {
-    MAIL_DIR=$MAIL_DIR ruby -I $TEST_DIRECTORY/../bindings/ruby> OUTPUT
+    MAIL_DIR=$MAIL_DIR ruby -I $NOTMUCH_SRCDIR/bindings/ruby> OUTPUT
 }
 
 test_C () {
     exec_file="test${test_count}"
     test_file="${exec_file}.c"
     cat > ${test_file}
-    ${TEST_CC} ${TEST_CFLAGS} -I${TEST_DIRECTORY} -I${NOTMUCH_SRCDIR}/lib -o ${exec_file} ${test_file} -L${TEST_DIRECTORY}/../lib/ -lnotmuch -ltalloc
+    ${TEST_CC} ${TEST_CFLAGS} -I${NOTMUCH_SRCDIR}/test -I${NOTMUCH_SRCDIR}/lib -o ${exec_file} ${test_file} -L${NOTMUCH_BUILDDIR}/lib/ -lnotmuch -ltalloc
     echo "== stdout ==" > OUTPUT.stdout
     echo "== stderr ==" > OUTPUT.stderr
     ./${exec_file} "$@" 1>>OUTPUT.stdout 2>>OUTPUT.stderr
@@ -1200,7 +1066,7 @@ test_init_ () {
 }
 
 
-. ./test-lib-common.sh || exit 1
+. "$NOTMUCH_SRCDIR/test/test-lib-common.sh" || exit 1
 
 if [ "${NOTMUCH_GMIME_MAJOR}" = 3 ]; then
     test_subtest_broken_gmime_3 () {
@@ -1223,7 +1089,7 @@ 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 set up test environment"
+cd -P "$TMP_DIRECTORY" || error "Cannot set up test environment"
 
 if test "$verbose" = "t"
 then
index 158be28a365eaef601efea2209ab07bcafce8622..8af6d9a97600b0ad867a42839841908a8c6c82e4 100755 (executable)
@@ -2,7 +2,7 @@
 
 test_description='the verbosity options of the test framework itself.'
 
-. ./test-lib.sh || exit 1
+. $(dirname "$0")/test-lib.sh || exit 1
 
 test_begin_subtest 'print something in test_expect_success and pass'
 test_expect_success '
index 3027880b45beee8ef24594bcef0410d44ce9d81b..ba03230e0741b70e0223245b8963db4dec56edd7 100644 (file)
@@ -5,7 +5,7 @@ extra_cflags += -I$(srcdir)/$(dir)
 
 libnotmuch_util_c_srcs := $(dir)/xutil.c $(dir)/error_util.c $(dir)/hex-escape.c \
                  $(dir)/string-util.c $(dir)/talloc-extra.c $(dir)/zlib-extra.c \
-               $(dir)/util.c $(dir)/gmime-extra.c
+               $(dir)/util.c $(dir)/gmime-extra.c $(dir)/crypto.c
 
 libnotmuch_util_modules := $(libnotmuch_util_c_srcs:.c=.o)
 
diff --git a/util/crypto.c b/util/crypto.c
new file mode 100644 (file)
index 0000000..5c84282
--- /dev/null
@@ -0,0 +1,140 @@
+/* notmuch - Not much of an email program, (just index and search)
+ *
+ * Copyright © 2012 Jameson Rollins
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see https://www.gnu.org/licenses/ .
+ *
+ * Authors: Jameson Rollins <jrollins@finestructure.net>
+ */
+
+#include "crypto.h"
+#include <strings.h>
+#define unused(x) x __attribute__ ((unused))
+
+#define ARRAY_SIZE(arr) (sizeof (arr) / sizeof (arr[0]))
+
+#if (GMIME_MAJOR_VERSION < 3)
+/* Create or pass on a GPG context (GMime 2.6) */
+static notmuch_status_t
+get_gpg_context (_notmuch_crypto_t *crypto, GMimeCryptoContext **ctx)
+{
+    if (ctx == NULL || crypto == NULL)
+       return NOTMUCH_STATUS_NULL_POINTER;
+
+    if (crypto->gpgctx) {
+       *ctx = crypto->gpgctx;
+       return NOTMUCH_STATUS_SUCCESS;
+    }
+
+    /* TODO: GMimePasswordRequestFunc */
+    crypto->gpgctx = g_mime_gpg_context_new (NULL, crypto->gpgpath ? crypto->gpgpath : "gpg");
+    if (! crypto->gpgctx) {
+       return NOTMUCH_STATUS_FAILED_CRYPTO_CONTEXT_CREATION;
+    }
+
+    g_mime_gpg_context_set_use_agent ((GMimeGpgContext *) crypto->gpgctx, true);
+    g_mime_gpg_context_set_always_trust ((GMimeGpgContext *) crypto->gpgctx, false);
+
+    *ctx = crypto->gpgctx;
+    return NOTMUCH_STATUS_SUCCESS;
+}
+
+/* Create or pass on a PKCS7 context (GMime 2.6) */
+static notmuch_status_t
+get_pkcs7_context (_notmuch_crypto_t *crypto, GMimeCryptoContext **ctx)
+{
+    if (ctx == NULL || crypto == NULL)
+       return NOTMUCH_STATUS_NULL_POINTER;
+
+    if (crypto->pkcs7ctx) {
+       *ctx = crypto->pkcs7ctx;
+       return NOTMUCH_STATUS_SUCCESS;
+    }
+
+    /* TODO: GMimePasswordRequestFunc */
+    crypto->pkcs7ctx = g_mime_pkcs7_context_new (NULL);
+    if (! crypto->pkcs7ctx) {
+       return NOTMUCH_STATUS_FAILED_CRYPTO_CONTEXT_CREATION;
+    }
+
+    g_mime_pkcs7_context_set_always_trust ((GMimePkcs7Context *) crypto->pkcs7ctx,
+                                          false);
+
+    *ctx = crypto->pkcs7ctx;
+    return NOTMUCH_STATUS_SUCCESS;
+}
+static const struct {
+    const char *protocol;
+    notmuch_status_t (*get_context) (_notmuch_crypto_t *crypto, GMimeCryptoContext **ctx);
+} protocols[] = {
+    {
+       .protocol = "application/pgp-signature",
+       .get_context = get_gpg_context,
+    },
+    {
+       .protocol = "application/pgp-encrypted",
+       .get_context = get_gpg_context,
+    },
+    {
+       .protocol = "application/pkcs7-signature",
+       .get_context = get_pkcs7_context,
+    },
+    {
+       .protocol = "application/x-pkcs7-signature",
+       .get_context = get_pkcs7_context,
+    },
+};
+
+/* for the specified protocol return the context pointer (initializing
+ * if needed) */
+notmuch_status_t
+_notmuch_crypto_get_gmime_ctx_for_protocol (_notmuch_crypto_t *crypto,
+                                           const char *protocol,
+                                           GMimeCryptoContext **ctx)
+{
+    if (! protocol)
+       return NOTMUCH_STATUS_MALFORMED_CRYPTO_PROTOCOL;
+
+    /* As per RFC 1847 section 2.1: "the [protocol] value token is
+     * comprised of the type and sub-type tokens of the Content-Type".
+     * As per RFC 1521 section 2: "Content-Type values, subtypes, and
+     * parameter names as defined in this document are
+     * case-insensitive."  Thus, we use strcasecmp for the protocol.
+     */
+    for (size_t i = 0; i < ARRAY_SIZE (protocols); i++) {
+       if (strcasecmp (protocol, protocols[i].protocol) == 0)
+           return protocols[i].get_context (crypto, ctx);
+    }
+
+    return NOTMUCH_STATUS_UNKNOWN_CRYPTO_PROTOCOL;
+}
+
+void
+_notmuch_crypto_cleanup (_notmuch_crypto_t *crypto)
+{
+    if (crypto->gpgctx) {
+       g_object_unref (crypto->gpgctx);
+       crypto->gpgctx = NULL;
+    }
+
+    if (crypto->pkcs7ctx) {
+       g_object_unref (crypto->pkcs7ctx);
+       crypto->pkcs7ctx = NULL;
+    }
+}
+#else
+void _notmuch_crypto_cleanup (unused(_notmuch_crypto_t *crypto))
+{
+}
+#endif
diff --git a/util/crypto.h b/util/crypto.h
new file mode 100644 (file)
index 0000000..1ff0297
--- /dev/null
@@ -0,0 +1,31 @@
+#ifndef _CRYPTO_H
+#define _CRYPTO_H
+
+#include <stdbool.h>
+#if (GMIME_MAJOR_VERSION < 3)
+#include "gmime-extra.h"
+#include "notmuch.h"
+#endif
+
+typedef struct _notmuch_crypto {
+    bool verify;
+    bool decrypt;
+#if (GMIME_MAJOR_VERSION < 3)
+    GMimeCryptoContext* gpgctx;
+    GMimeCryptoContext* pkcs7ctx;
+    const char *gpgpath;
+#endif
+} _notmuch_crypto_t;
+
+
+#if (GMIME_MAJOR_VERSION < 3)
+notmuch_status_t
+_notmuch_crypto_get_gmime_ctx_for_protocol (_notmuch_crypto_t *crypto,
+                                           const char *protocol,
+                                           GMimeCryptoContext **ctx);
+#endif
+
+void
+_notmuch_crypto_cleanup (_notmuch_crypto_t *crypto);
+
+#endif
index 778bbd522227b73d8aaaa53df64a17e1061e2ba6..e64162c7a885e622fd591b44c9d9a2175342bbd6 100644 (file)
@@ -34,6 +34,7 @@ _internal_error (const char *format, ...)
     fprintf (stderr, "Internal error: ");
     vfprintf (stderr, format, va_args);
 
+    va_end (va_args);
     exit (1);
 }
 
index de275bc16d444626cac78293cc9af6629991d868..40bf1454d00dfee0496fd186b60db9a03c1d93c4 100644 (file)
@@ -18,7 +18,6 @@ GMimeStream *g_mime_stream_stdout_new(void);
 #define g_mime_certificate_get_fpr16(cert) g_mime_certificate_get_key_id (cert)
 #define g_mime_certificate_get_uid(cert) g_mime_certificate_get_name (cert);
 #else /* GMime >= 3.0 */
-typedef GMimeAddressType GMimeRecipientType;
 
 #define GMIME_ENABLE_RFC_2047_WORKAROUNDS 0xdeadbeef
 #define g_mime_certificate_get_uid(cert) g_mime_certificate_get_key_id (cert);
@@ -30,7 +29,6 @@ typedef GMimeAddressType GMimeRecipientType;
 #define g_mime_init(flags) g_mime_init()
 #define g_mime_message_add_recipient(m,t,n,a) g_mime_message_add_mailbox (m,t,n,a)
 #define g_mime_message_set_subject(m,s) g_mime_message_set_subject(m,s,NULL)
-#define g_mime_multipart_encrypted_decrypt(mpe,ctx,out,err) g_mime_multipart_encrypted_decrypt(mpe, GMIME_DECRYPT_NONE, NULL, out, err)
 #define g_mime_multipart_signed_verify(mps,ctx,err) g_mime_multipart_signed_verify(mps, GMIME_ENCRYPT_NONE, err)
 #define g_mime_object_write_to_stream(o,s) g_mime_object_write_to_stream (o,NULL,s)
 #define g_mime_object_set_header(o,h,v) g_mime_object_set_header (o,h,v,NULL)
index 18125309ebe9ed33c048f928e02d1e6b35edca28..b0108811903b27153dcaece1fa10b893234c3b9f 100644 (file)
@@ -255,3 +255,16 @@ strcase_hash (const void *ptr)
 
     return hash;
 }
+
+void
+strip_trailing (char *str, char ch)
+{
+    int i;
+
+    for (i = strlen (str) - 1; i >= 0; i--) {
+       if (str[i] == ch)
+           str[i] = '\0';
+       else
+           break;
+    }
+}
index 87917b8fd279de6e07f2fb919429fb50512a85b4..97770614adf1ab6930e529cd148dea47c71547f6 100644 (file)
@@ -75,6 +75,8 @@ int strcase_equal (const void *a, const void *b);
 /* GLib GHashFunc compatible case insensitive hash function */
 unsigned int strcase_hash (const void *ptr);
 
+void strip_trailing (char *str, char ch);
+
 #ifdef __cplusplus
 }
 #endif