aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDavid Bremner <david@tethera.net>2016-06-28 16:48:30 +0200
committerDavid Bremner <david@tethera.net>2016-06-28 16:48:30 +0200
commit7fe91ddc4f47170724d455e84d5d262410d569f2 (patch)
tree4ba841df17c181ab6a785d408c4e5f8316bda9c1
parent148ceed198f98524db8482e212804c5a510d696b (diff)
parentea5caecec5c50833a6f5a422e217a71eee6324af (diff)
Merge tag 'debian/0.22-1' into jessie-backports
notmuch Debian 0.22-1 upload (same as 0.22)
-rw-r--r--NEWS135
-rw-r--r--bindings/Makefile.local2
-rw-r--r--bindings/python/README80
-rw-r--r--bindings/python/docs/source/filesystem.rst6
-rw-r--r--bindings/python/docs/source/status_and_errors.rst6
-rw-r--r--bindings/python/docs/source/threads.rst8
-rw-r--r--bindings/python/notmuch/__init__.py3
-rw-r--r--bindings/python/notmuch/compat.py4
-rw-r--r--bindings/python/notmuch/database.py22
-rw-r--r--bindings/python/notmuch/errors.py21
-rw-r--r--bindings/python/notmuch/filenames.py23
-rw-r--r--bindings/python/notmuch/globals.py5
-rw-r--r--bindings/python/notmuch/query.py27
-rw-r--r--bindings/python/notmuch/threads.py29
-rw-r--r--bindings/python/notmuch/version.py2
-rwxr-xr-xconfigure48
-rw-r--r--crypto.c80
-rw-r--r--debian/changelog19
-rw-r--r--debian/control4
-rw-r--r--devel/STYLE11
-rw-r--r--devel/nmbug/doc/.gitignore2
-rw-r--r--devel/nmbug/doc/Makefile38
-rw-r--r--devel/nmbug/doc/conf.py67
-rw-r--r--devel/nmbug/doc/index.rst17
-rw-r--r--devel/nmbug/doc/man1/notmuch-report.1.rst54
-rw-r--r--devel/nmbug/doc/man5/notmuch-report.json.5.rst129
-rwxr-xr-xdevel/nmbug/nmbug2
-rwxr-xr-xdevel/nmbug/notmuch-report (renamed from devel/nmbug/nmbug-status)61
-rw-r--r--devel/nmbug/notmuch-report.json (renamed from devel/nmbug/status-config.json)2
-rwxr-xr-xdevel/release-checks.sh15
-rwxr-xr-xdevel/try-emacs-mua157
-rw-r--r--doc/conf.py2
-rw-r--r--doc/man1/notmuch-reply.rst6
-rw-r--r--emacs/Makefile.local5
-rw-r--r--emacs/coolj.el2
-rw-r--r--emacs/make-deps.el4
-rw-r--r--emacs/notmuch-address.el219
-rw-r--r--emacs/notmuch-company.el88
-rw-r--r--emacs/notmuch-crypto.el13
-rw-r--r--emacs/notmuch-hello.el58
-rw-r--r--emacs/notmuch-jump.el6
-rw-r--r--emacs/notmuch-lib.el107
-rw-r--r--emacs/notmuch-maildir-fcc.el9
-rw-r--r--emacs/notmuch-message.el6
-rw-r--r--emacs/notmuch-mua.el198
-rw-r--r--emacs/notmuch-parser.el6
-rw-r--r--emacs/notmuch-print.el6
-rw-r--r--emacs/notmuch-query.el6
-rw-r--r--emacs/notmuch-show.el354
-rw-r--r--emacs/notmuch-tag.el2
-rw-r--r--emacs/notmuch-tree.el6
-rw-r--r--emacs/notmuch-version.el.tmpl5
-rw-r--r--emacs/notmuch-wash.el6
-rw-r--r--emacs/notmuch.el19
-rw-r--r--lib/database.cc22
-rw-r--r--lib/index.cc3
-rw-r--r--lib/message.cc115
-rw-r--r--lib/notmuch-private.h17
-rw-r--r--lib/notmuch.h11
-rw-r--r--lib/query.cc18
-rw-r--r--notmuch-client.h12
-rwxr-xr-xnotmuch-emacs-mua4
-rw-r--r--notmuch-new.c19
-rw-r--r--notmuch-reply.c5
-rw-r--r--notmuch-show.c3
-rw-r--r--notmuch-tag.c4
-rw-r--r--status.c17
-rw-r--r--test/.gitignore1
-rw-r--r--test/Makefile.local6
-rw-r--r--test/README34
-rwxr-xr-xtest/T050-new.sh53
-rwxr-xr-xtest/T060-count.sh4
-rwxr-xr-xtest/T150-tagging.sh29
-rwxr-xr-xtest/T190-multipart.sh52
-rwxr-xr-xtest/T310-emacs.sh36
-rwxr-xr-xtest/T355-smime.sh90
-rwxr-xr-xtest/T360-symbol-hiding.sh4
-rwxr-xr-xtest/T530-upgrade.sh21
-rwxr-xr-xtest/T560-lib-error.sh18
-rwxr-xr-xtest/T580-thread-search.sh42
-rwxr-xr-xtest/T590-thread-breakage.sh124
-rw-r--r--test/atomicity.py23
-rw-r--r--test/emacs-show.expected-output/notmuch-show-elide-non-matching-messages-off3
-rw-r--r--test/emacs-show.expected-output/notmuch-show-elide-non-matching-messages-on3
-rw-r--r--test/emacs-show.expected-output/notmuch-show-indent-thread-content-off3
-rw-r--r--test/emacs-show.expected-output/notmuch-show-process-crypto-mime-parts-off1
-rw-r--r--test/emacs.expected-output/notmuch-hello7
-rw-r--r--test/emacs.expected-output/notmuch-hello-long-names7
-rw-r--r--test/emacs.expected-output/notmuch-hello-no-saved-searches7
-rw-r--r--test/emacs.expected-output/notmuch-hello-with-empty7
-rw-r--r--test/emacs.expected-output/notmuch-show-thread-maildir-storage4
-rw-r--r--test/emacs.expected-output/notmuch-show-thread-maildir-storage-with-fourfold-indentation4
-rw-r--r--test/emacs.expected-output/notmuch-show-thread-maildir-storage-without-indentation4
-rw-r--r--test/ghost-report.cc14
-rw-r--r--test/smime/README7
-rw-r--r--test/smime/key+cert.pem56
-rw-r--r--test/smime/test.crt19
-rw-r--r--test/test-lib-common.sh2
-rw-r--r--test/test-lib.el10
-rw-r--r--test/test-lib.sh27
-rw-r--r--test/tree.expected-output/notmuch-tree-show-window1
-rw-r--r--version2
102 files changed, 2521 insertions, 666 deletions
diff --git a/NEWS b/NEWS
index 6681699d..c945c245 100644
--- a/NEWS
+++ b/NEWS
@@ -1,3 +1,136 @@
+Notmuch 0.22 (2016-04-26)
+=========================
+
+General
+-------
+
+Xapian 1.3 support
+
+ Notmuch should now build (and the test suite should pass) on recent
+ releases of Xapian 1.3.x. It has been tested with Xapian 1.3.5.
+
+Limited support for S/MIME messages
+
+ Signature verification is supported, but not decryption. S/MIME
+ signature creation and S/MIME encryption are supported via built-in
+ support in Emacs. S/MIME support is not extensively tested at this
+ time.
+
+Bug Fixes
+
+ Fix for threading bug involving deleting and re-adding
+ messages. Fix for case-sensitive content disposition headers. Fix
+ handling of 1 character directory names at top level.
+
+Command Line Interface
+----------------------
+
+`notmuch show` now supports verifying S/MIME signatures
+
+ This support relies on an appropriately configured `gpgsm`.
+
+Build System
+------------
+
+Drop dependency on "pkg-config emacs".
+
+Emacs Interface
+---------------
+
+Notmuch replies now include all parts shown in the show view
+
+ There are two main user visible changes. The first is that rfc822
+ parts are now included in replies.
+
+ The second change is that part headers are now included in the reply
+ buffer to provide visible separation of the parts. The choice of
+ which part headers to show is customizable via the variable
+ `notmuch-mua-reply-insert-header-p-function`.
+
+Filtering or Limiting messages is now bound to `l` in the search view
+
+ This binding now matches the analogous binding in show view.
+
+`F` forwards all open messages in a thread
+
+ When viewing a thread of messages, the new binding `F` can be used
+ to generate a new outgoing message which forwards all of the open
+ messages in the thread. This is analogous to the `f` binding, which
+ forwards only the current message.
+
+Preferred content type can be determined from the message content
+
+ More flexibility in choosing which sub-part of a
+ multipart/alternative part is initially shown is available by
+ setting `notmuch-multipart/alternative-discouraged` to a function
+ that returns a list of discouraged types. The function so specified
+ is passed the message as an argument and can examine the message
+ content to determine which content types should be discouraged. This
+ is in addition to the current capabilities (i.e. setting
+ `notmuch-multipart/alternative-discouraged` to a list of discouraged
+ types).
+
+When viewing a thread ("show" mode), queries that match no messages no
+longer generate empty buffers
+
+ Should an attempt be made to view the thread corresponding to a
+ query that matches no messages, a warning message is now displayed
+ and the terminal bell rung rather than displaying an empty buffer
+ (or, in some cases, displaying an empty buffer and throwing an
+ error). This also affects re-display of the current thread.
+
+Handle S/MIME signatures in emacs
+
+ The emacs interface is now capable making and verifying S/MIME
+ signatures.
+
+`notmuch-message-address-insinuate` is now a no-op
+
+ This reduces the amount of interference with non-notmuch uses of
+ message-mode.
+
+Address completion improvements
+
+ An external script is no longer needed for address completion; if
+ you previously configured one, customize the variable
+ `notmuch-address-command` to try the internal completion. If
+ `company-mode` is available, notmuch uses it by default for
+ interactive address completion.
+
+Test and experiment with the emacs MUA available in source tree
+
+ `./devel/try-emacs-mua` runs emacs and fills the window with
+ information how to try the MUA safely. Emacs is configured to use
+ the notmuch (lisp) files located in `./emacs` directory.
+
+Documentation
+-------------
+
+New `notmuch-report(1)` and `notmuch-report.json(5)` man pages
+describe `notmuch-report` and its JSON configuration file. You can
+build these files by running `make` in the `devel/nmbug/doc`
+directory.
+
+notmuch-report
+--------------
+
+Renamed from `nmbug-status`. This script generates reports based on
+notmuch queries, and doesn't really have anything to do with nmbug,
+except for sharing the `NMBGIT` environment variable. The new name
+focuses on the script's action, instead of its historical association
+with the nmbug workflow. This should make it more discoverable for
+users looking for generic notmuch reporting tools.
+
+The default configuration file name (extracted from the `config`
+branch of `NBMGIT` has changed from `status-config.json` to
+`notmuch-report.json` so it is more obviously associated with the
+report-generating script. The configuration file also has a new
+`meta.message-url` setting, which is documented in
+`notmuch-report.json(5)`.
+
+`notmuch-report` now wraps query phrases in parentheses when and-ing
+them together, to avoid confusion about clause grouping.
+
Notmuch 0.21 (2015-10-29)
=========================
@@ -409,7 +542,7 @@ from the config file. Use something like:
...
},
...
- },
+ }
Python Bindings
---------------
diff --git a/bindings/Makefile.local b/bindings/Makefile.local
index 4ecf839d..11d11d4b 100644
--- a/bindings/Makefile.local
+++ b/bindings/Makefile.local
@@ -10,8 +10,6 @@ ifeq ($(HAVE_RUBY_DEV),1)
LIBNOTMUCH="../../lib/$(LINKER_NAME)" \
ruby extconf.rb --vendor
$(MAKE) -C $(dir)/ruby
-else
- @echo Missing dependency, skipping ruby bindings
endif
CLEAN += $(patsubst %,$(dir)/ruby/%, \
diff --git a/bindings/python/README b/bindings/python/README
index e27ede2c..b20ae071 100644
--- a/bindings/python/README
+++ b/bindings/python/README
@@ -1,5 +1,5 @@
-notmuch -- The python interface to notmuch.so
-==============================================
+notmuch -- The python interface to notmuch
+==========================================
This module makes the functionality of the notmuch library
(`http://notmuchmail.org`_) available to python. Successful import of
@@ -10,78 +10,8 @@ If you have downloaded the full source tarball, you can create the
documentation with sphinx installed, go to the docs directory and
"make html". A static version of the documentation is available at:
-http://packages.python.org/notmuch/
+ https://notmuch.readthedocs.org/projects/notmuch-python/
-The current source code is being hosted at
-http://bitbucket.org/spaetz/cnotmuch which also provides an issue
-tracker, and release downloads. This package is tracked by the python
-package index repository at `http://pypi.python.org/pypi/notmuch`_ and can thus be installed on a user's computer easily via "sudo easy_install notmuch" (you will still need to install the notmuch shared library separately as it is not included in this package).
+To build the python bindings, do
-The original source has been provided by (c)Sebastian Spaeth, 2010.
-All code is available under the GNU GPLv3+ (see docs/COPYING) unless specified otherwise.
-
-
-INSTALLATION & DEINSTALL
-------------------------
-
-The notmuch python module is available on pypi.python.org. This means
-you can do "easy_install notmuch" on your linux box and it will get
-installed into:
-
-/usr/local/lib/python2.x/dist-packages/
-
-For uninstalling, you'll need to remove the "notmuch-0.4-py2.x.egg"
-(or similar) directory and delete one entry in the "easy-install.pth"
-file in that directory.
-
-It needs to have a libnotmuch.so or libnotmuch.so.1 available in some
-library folder or will raise an exception when loading.
-"OSError: libnotmuch.so.1: cannot open shared object file: No such file or directory"
-
-
-Usage
------
-For more examples of how to use the notmuch interface, have a look at the
-notmuch "binary" and the generated documentation.
-
-Example session:
->>>import notmuch
->>>db = notmuch.Database("/home/spaetz/mail")
-db.get_path()
-'/home/spaetz/mail'
->>>tags = db.get_all_tags()
->>>for tag in tags:
->>> print tag
-inbox
-...
-maildir::draft
-#---------------------------------------------
-
-q = notmuch.Query(db,'from:Sebastian')
-count = len(q.search_messages())
-1300
-
-#---------------------------------------------
-
->>>db = notmuch.Database("/home/spaetz/mailHAHA")
-NotmuchError: Could not open the specified database
-
-#---------------------------------------------
-
->>>tags = notmuch.Database("/home/spaetz/mail").get_all_tags()
->>>del(tags)
-
-
-Building for a Debian package
-------------------------------
-dpkg-buildpackage -i"\.hg|\/build"
-
-
-Changelog
----------
-0.1 First public release
-0.1.1 Fixed Database.create_query()
-0.2.0 Implemented Thread() and Threads() methods
-0.2.1 Implemented the remaining API methods, notably Directory() and Filenames()
-0.2.2 Bug fixes
-0.3.0 Incorporated in the notmuchmail.org git repository \ No newline at end of file
+ python setup.py install --prefix=path/to/your/preferred/location
diff --git a/bindings/python/docs/source/filesystem.rst b/bindings/python/docs/source/filesystem.rst
index 4eb78107..a23ae41a 100644
--- a/bindings/python/docs/source/filesystem.rst
+++ b/bindings/python/docs/source/filesystem.rst
@@ -8,7 +8,11 @@ Files and directories
.. autoclass:: Filenames
- .. automethod:: Filenames.__len__
+ .. method:: Filenames.__len__
+ .. warning::
+ :meth:`__len__` was removed in version 0.22 as it exhausted the
+ iterator and broke list(Filenames()). Use `len(list(names))`
+ instead.
:class:`Directoy` -- A directory entry in the database
------------------------------------------------------
diff --git a/bindings/python/docs/source/status_and_errors.rst b/bindings/python/docs/source/status_and_errors.rst
index dd6e31f8..68913f16 100644
--- a/bindings/python/docs/source/status_and_errors.rst
+++ b/bindings/python/docs/source/status_and_errors.rst
@@ -47,5 +47,11 @@ The following exceptions are all directly derived from NotmuchError. Each of the
:members:
.. autoexception:: UnbalancedAtomicError(message=None)
:members:
+.. autoexception:: UnsupportedOperationError(message=None)
+ :members:
+.. autoexception:: UpgradeRequiredError(message=None)
+ :members:
+.. autoexception:: PathError(message=None)
+ :members:
.. autoexception:: NotInitializedError(message=None)
:members:
diff --git a/bindings/python/docs/source/threads.rst b/bindings/python/docs/source/threads.rst
index e5a8c8a9..4324ac82 100644
--- a/bindings/python/docs/source/threads.rst
+++ b/bindings/python/docs/source/threads.rst
@@ -5,6 +5,10 @@
.. autoclass:: Threads
- .. automethod:: __len__
+ .. method:: __len__
+ .. warning::
+ :meth:`__len__` was removed in version 0.22 as it exhausted the
+ iterator and broke list(Threads()). Use `len(list(msgs))`
+ instead.
- .. automethod:: __str__
+.. automethod:: __str__
diff --git a/bindings/python/notmuch/__init__.py b/bindings/python/notmuch/__init__.py
index 5561624e..29416a5b 100644
--- a/bindings/python/notmuch/__init__.py
+++ b/bindings/python/notmuch/__init__.py
@@ -75,6 +75,9 @@ from .errors import (
UnbalancedFreezeThawError,
UnbalancedAtomicError,
NotInitializedError,
+ UnsupportedOperationError,
+ UpgradeRequiredError,
+ PathError,
)
from .version import __VERSION__
__LICENSE__ = "GPL v3+"
diff --git a/bindings/python/notmuch/compat.py b/bindings/python/notmuch/compat.py
index adc8d244..daa268c1 100644
--- a/bindings/python/notmuch/compat.py
+++ b/bindings/python/notmuch/compat.py
@@ -65,3 +65,7 @@ else:
raise TypeError('Expected str, got %s' % type(value))
return value.encode('utf-8', 'replace')
+
+# We import the SafeConfigParser class on behalf of other code to cope
+# with the differences between Python 2 and 3.
+SafeConfigParser # avoid warning about unused import
diff --git a/bindings/python/notmuch/database.py b/bindings/python/notmuch/database.py
index 5b58e099..f3045334 100644
--- a/bindings/python/notmuch/database.py
+++ b/bindings/python/notmuch/database.py
@@ -36,7 +36,6 @@ from .errors import (
NotmuchError,
NullPointerError,
NotInitializedError,
- ReadOnlyDatabaseError,
)
from .message import Message
from .tag import Tags
@@ -484,7 +483,10 @@ class Database(object):
removed.
"""
self._assert_db_is_initialized()
- return self._remove_message(self._db, _str(filename))
+ status = self._remove_message(self._db, _str(filename))
+ if status not in [STATUS.SUCCESS, STATUS.DUPLICATE_MESSAGE_ID]:
+ raise NotmuchError(status)
+ return status
def find_message(self, msgid):
"""Returns a :class:`Message` as identified by its message ID
@@ -575,6 +577,22 @@ class Database(object):
"""
return Query(self, querystring)
+ """notmuch_database_status_string"""
+ _status_string = nmlib.notmuch_database_status_string
+ _status_string.argtypes = [NotmuchDatabaseP]
+ _status_string.restype = c_char_p
+
+ def status_string(self):
+ """Returns the status string of the database
+
+ This is sometimes used for additional error reporting
+ """
+ self._assert_db_is_initialized()
+ s = Database._status_string(self._db)
+ if s:
+ return s.decode('utf-8', 'ignore')
+ return s
+
def __repr__(self):
return "'Notmuch DB " + self.get_path() + "'"
diff --git a/bindings/python/notmuch/errors.py b/bindings/python/notmuch/errors.py
index f153a9c5..abca51d7 100644
--- a/bindings/python/notmuch/errors.py
+++ b/bindings/python/notmuch/errors.py
@@ -56,6 +56,9 @@ STATUS = Status(['SUCCESS',
'TAG_TOO_LONG',
'UNBALANCED_FREEZE_THAW',
'UNBALANCED_ATOMIC',
+ 'UNSUPPORTED_OPERATION',
+ 'UPGRADE_REQUIRED',
+ 'PATH_ERROR',
'NOT_INITIALIZED'])
"""STATUS is a class, whose attributes provide constants that serve as return
indicators for notmuch functions. Currently the following ones are defined. For
@@ -73,6 +76,9 @@ description.
* TAG_TOO_LONG
* UNBALANCED_FREEZE_THAW
* UNBALANCED_ATOMIC
+ * UNSUPPORTED_OPERATION
+ * UPGRADE_REQUIRED
+ * PATH_ERROR
* NOT_INITIALIZED
Invoke the class method `notmuch.STATUS.status2str` with a status value as
@@ -101,6 +107,9 @@ class NotmuchError(Exception, Python3StringMixIn):
STATUS.TAG_TOO_LONG: TagTooLongError,
STATUS.UNBALANCED_FREEZE_THAW: UnbalancedFreezeThawError,
STATUS.UNBALANCED_ATOMIC: UnbalancedAtomicError,
+ STATUS.UNSUPPORTED_OPERATION: UnsupportedOperationError,
+ STATUS.UPGRADE_REQUIRED: UpgradeRequiredError,
+ STATUS.PATH_ERROR: PathError,
STATUS.NOT_INITIALIZED: NotInitializedError,
}
assert 0 < status <= len(subclasses)
@@ -175,6 +184,18 @@ class UnbalancedAtomicError(NotmuchError):
status = STATUS.UNBALANCED_ATOMIC
+class UnsupportedOperationError(NotmuchError):
+ status = STATUS.UNSUPPORTED_OPERATION
+
+
+class UpgradeRequiredError(NotmuchError):
+ status = STATUS.UPGRADE_REQUIRED
+
+
+class PathError(NotmuchError):
+ status = STATUS.PATH_ERROR
+
+
class NotInitializedError(NotmuchError):
"""Derived from NotmuchError, this occurs if the underlying data
structure (e.g. database is not initialized (yet) or an iterator has
diff --git a/bindings/python/notmuch/filenames.py b/bindings/python/notmuch/filenames.py
index 229f414d..f8f383e4 100644
--- a/bindings/python/notmuch/filenames.py
+++ b/bindings/python/notmuch/filenames.py
@@ -19,7 +19,6 @@ Copyright 2010 Sebastian Spaeth <Sebastian@SSpaeth.de>
from ctypes import c_char_p
from .globals import (
nmlib,
- NotmuchMessageP,
NotmuchFilenamesP,
Python3StringMixIn,
)
@@ -48,7 +47,7 @@ class Filenames(Python3StringMixIn):
as well as::
- number_of_names = len(names)
+ list_of_names = list(names)
and even a simple::
@@ -123,28 +122,10 @@ class Filenames(Python3StringMixIn):
return "\n".join(self)
_destroy = nmlib.notmuch_filenames_destroy
- _destroy.argtypes = [NotmuchMessageP]
+ _destroy.argtypes = [NotmuchFilenamesP]
_destroy.restype = None
def __del__(self):
"""Close and free the notmuch filenames"""
if self._files_p:
self._destroy(self._files_p)
-
- def __len__(self):
- """len(:class:`Filenames`) returns the number of contained files
-
- .. note::
-
- This method exhausts the iterator object, so you will not be able to
- iterate over them again.
- """
- if not self._files_p:
- raise NotInitializedError()
-
- i = 0
- while self._valid(self._files_p):
- self._move_to_next(self._files_p)
- i += 1
- self._files_p = None
- return i
diff --git a/bindings/python/notmuch/globals.py b/bindings/python/notmuch/globals.py
index 70cfdc3d..6872a291 100644
--- a/bindings/python/notmuch/globals.py
+++ b/bindings/python/notmuch/globals.py
@@ -33,6 +33,11 @@ except:
from .compat import Python3StringMixIn, encode_utf8 as _str
+# We import these on behalf of other modules. Silence warning about
+# these symbols not being used.
+Python3StringMixIn
+_str
+
class Enum(object):
"""Provides ENUMS as "code=Enum(['a','b','c'])" where code.a=0 etc..."""
def __init__(self, names):
diff --git a/bindings/python/notmuch/query.py b/bindings/python/notmuch/query.py
index 378aa47d..43270072 100644
--- a/bindings/python/notmuch/query.py
+++ b/bindings/python/notmuch/query.py
@@ -28,6 +28,7 @@ from .globals import (
NotmuchMessagesP,
)
from .errors import (
+ NotmuchError,
NullPointerError,
NotInitializedError,
)
@@ -133,10 +134,10 @@ class Query(object):
self._assert_query_is_initialized()
self._exclude_tag(self._query, _str(tagname))
- """notmuch_query_search_threads"""
- _search_threads = nmlib.notmuch_query_search_threads
- _search_threads.argtypes = [NotmuchQueryP]
- _search_threads.restype = NotmuchThreadsP
+ """notmuch_query_search_threads_st"""
+ _search_threads_st = nmlib.notmuch_query_search_threads_st
+ _search_threads_st.argtypes = [NotmuchQueryP, POINTER(NotmuchThreadsP)]
+ _search_threads_st.restype = c_uint
def search_threads(self):
"""Execute a query for threads
@@ -153,16 +154,19 @@ class Query(object):
:raises: :exc:`NullPointerError` if search_threads failed
"""
self._assert_query_is_initialized()
- threads_p = Query._search_threads(self._query)
+ threads_p = NotmuchThreadsP() # == NULL
+ status = Query._search_threads_st(self._query, byref(threads_p))
+ if status != 0:
+ raise NotmuchError(status)
if not threads_p:
raise NullPointerError
return Threads(threads_p, self)
- """notmuch_query_search_messages"""
- _search_messages = nmlib.notmuch_query_search_messages
- _search_messages.argtypes = [NotmuchQueryP]
- _search_messages.restype = NotmuchMessagesP
+ """notmuch_query_search_messages_st"""
+ _search_messages_st = nmlib.notmuch_query_search_messages_st
+ _search_messages_st.argtypes = [NotmuchQueryP, POINTER(NotmuchMessagesP)]
+ _search_messages_st.restype = c_uint
def search_messages(self):
"""Filter messages according to the query and return
@@ -172,7 +176,10 @@ class Query(object):
:raises: :exc:`NullPointerError` if search_messages failed
"""
self._assert_query_is_initialized()
- msgs_p = Query._search_messages(self._query)
+ msgs_p = NotmuchMessagesP() # == NULL
+ status = Query._search_messages_st(self._query, byref(msgs_p))
+ if status != 0:
+ raise NotmuchError(status)
if not msgs_p:
raise NullPointerError
diff --git a/bindings/python/notmuch/threads.py b/bindings/python/notmuch/threads.py
index f8ca34a9..a550523f 100644
--- a/bindings/python/notmuch/threads.py
+++ b/bindings/python/notmuch/threads.py
@@ -46,7 +46,7 @@ class Threads(Python3StringMixIn):
as well as::
- number_of_msgs = len(threads)
+ list_of_threads = list(threads)
will "exhaust" the threads. If you need to re-iterate over a list of
messages you will need to retrieve a new :class:`Threads` object.
@@ -64,8 +64,7 @@ class Threads(Python3StringMixIn):
for thread in threads:
threadlist.append(thread)
- # threads is "exhausted" now and even len(threads) will raise an
- # exception.
+ # threads is "exhausted" now.
# However it will be kept around until all retrieved Thread() objects are
# also deleted. If you did e.g. an explicit del(threads) here, the
# following lines would fail.
@@ -132,30 +131,6 @@ class Threads(Python3StringMixIn):
return thread
next = __next__ # python2.x iterator protocol compatibility
- def __len__(self):
- """len(:class:`Threads`) returns the number of contained Threads
-
- .. note:: As this iterates over the threads, we will not be able to
- iterate over them again! So this will fail::
-
- #THIS FAILS
- threads = Database().create_query('').search_threads()
- if len(threads) > 0: #this 'exhausts' threads
- # next line raises :exc:`NotInitializedError`!!!
- for thread in threads: print thread
- """
- if not self._threads:
- raise NotInitializedError()
-
- i = 0
- # returns 'bool'. On out-of-memory it returns None
- while self._valid(self._threads):
- self._move_to_next(self._threads)
- i += 1
- # reset self._threads to mark as "exhausted"
- self._threads = None
- return i
-
def __nonzero__(self):
'''
Implement truth value testing. If __nonzero__ is not
diff --git a/bindings/python/notmuch/version.py b/bindings/python/notmuch/version.py
index db9d0764..c1d472be 100644
--- a/bindings/python/notmuch/version.py
+++ b/bindings/python/notmuch/version.py
@@ -1,3 +1,3 @@
# this file should be kept in sync with ../../../version
-__VERSION__ = '0.21'
+__VERSION__ = '0.22'
SOVERSION = '4'
diff --git a/configure b/configure
index 440d678c..4fc31ccf 100755
--- a/configure
+++ b/configure
@@ -51,7 +51,7 @@ CPPFLAGS=${CPPFLAGS:-}
CXXFLAGS_for_sh=${CXXFLAGS:-${CFLAGS}}
CXXFLAGS=${CXXFLAGS:-\$(CFLAGS)}
LDFLAGS=${LDFLAGS:-}
-XAPIAN_CONFIG=${XAPIAN_CONFIG:-xapian-config}
+XAPIAN_CONFIG=${XAPIAN_CONFIG:-}
PYTHON=${PYTHON:-}
# We don't allow the EMACS or GZIP Makefile variables inherit values
@@ -341,7 +341,7 @@ fi
printf "Checking for Xapian development files... "
have_xapian=0
-for xapian_config in ${XAPIAN_CONFIG}; do
+for xapian_config in ${XAPIAN_CONFIG} xapian-config xapian-config-1.3; do
if ${xapian_config} --version > /dev/null 2>&1; then
xapian_version=$(${xapian_config} --version | sed -e 's/.* //')
printf "Yes (%s).\n" ${xapian_version}
@@ -371,7 +371,25 @@ if [ ${have_xapian} = "1" ]; then
esac
fi
-
+default_xapian_backend=""
+if [ ${have_xapian} = "1" ]; then
+ printf "Testing default Xapian backend... "
+ cat >_default_backend.cc <<EOF
+#include <xapian.h>
+int main(int argc, char** argv) {
+ Xapian::WritableDatabase db("test.db",Xapian::DB_CREATE_OR_OPEN);
+}
+EOF
+ ${CXX} ${CXXLAGS} ${xapian_cxxflags} _default_backend.cc -o _default_backend ${xapian_ldflags}
+ ./_default_backend
+ if [ -f test.db/iamglass ]; then
+ default_xapian_backend=glass
+ else
+ default_xapian_backend=chert
+ fi
+ printf "${default_xapian_backend}\n";
+ rm -rf test.db _default_backend _default_backend.cc
+fi
# we need to have a version >= 2.6.5 to avoid a crypto bug. We need
# 2.6.7 for permissive "From " header handling.
GMIME_MINVER=2.6.7
@@ -472,19 +490,11 @@ else
fi
if [ -z "${EMACSLISPDIR}" ]; then
- if pkg-config --exists emacs; then
- EMACSLISPDIR=$(pkg-config emacs --variable sitepkglispdir)
- else
- EMACSLISPDIR='$(prefix)/share/emacs/site-lisp'
- fi
+ EMACSLISPDIR='$(prefix)/share/emacs/site-lisp'
fi
if [ -z "${EMACSETCDIR}" ]; then
- if pkg-config --exists emacs; then
- EMACSETCDIR=$(pkg-config emacs --variable sitepkglispdir)
- else
- EMACSETCDIR='$(prefix)/share/emacs/site-lisp'
- fi
+ EMACSETCDIR='$(prefix)/share/emacs/site-lisp'
fi
printf "Checking if emacs is available... "
@@ -977,6 +987,10 @@ HAVE_STRCASESTR = ${have_strcasestr}
# build its own version)
HAVE_STRSEP = ${have_strsep}
+# Whether the timegm function is available (if not, then notmuch will
+# build its own version)
+HAVE_TIMEGM = ${have_timegm}
+
# Whether struct dirent has d_type (if not, then notmuch will use stat)
HAVE_D_TYPE = ${have_d_type}
@@ -1005,6 +1019,9 @@ LINKER_RESOLVES_LIBRARY_DEPENDENCIES = ${linker_resolves_library_dependencies}
XAPIAN_CXXFLAGS = ${xapian_cxxflags}
XAPIAN_LDFLAGS = ${xapian_ldflags}
+# Which backend will Xapian use by default?
+DEFAULT_XAPIAN_BACKEND = ${default_xapian_backend}
+
# Flags needed to compile and link against GMime
GMIME_CFLAGS = ${gmime_cflags}
GMIME_LDFLAGS = ${gmime_ldflags}
@@ -1049,6 +1066,7 @@ CONFIGURE_CFLAGS = -DHAVE_GETLINE=\$(HAVE_GETLINE) \$(GMIME_CFLAGS) \\
\$(VALGRIND_CFLAGS) \\
-DHAVE_STRCASESTR=\$(HAVE_STRCASESTR) \\
-DHAVE_STRSEP=\$(HAVE_STRSEP) \\
+ -DHAVE_TIMEGM=\$(HAVE_TIMEGM) \\
-DHAVE_D_TYPE=\$(HAVE_D_TYPE) \\
-DSTD_GETPWUID=\$(STD_GETPWUID) \\
-DSTD_ASCTIME=\$(STD_ASCTIME) \\
@@ -1062,6 +1080,7 @@ CONFIGURE_CXXFLAGS = -DHAVE_GETLINE=\$(HAVE_GETLINE) \$(GMIME_CFLAGS) \\
\$(VALGRIND_CFLAGS) \$(XAPIAN_CXXFLAGS) \\
-DHAVE_STRCASESTR=\$(HAVE_STRCASESTR) \\
-DHAVE_STRSEP=\$(HAVE_STRSEP) \\
+ -DHAVE_TIMEGM=\$(HAVE_TIMEGM) \\
-DHAVE_D_TYPE=\$(HAVE_D_TYPE) \\
-DSTD_GETPWUID=\$(STD_GETPWUID) \\
-DSTD_ASCTIME=\$(STD_ASCTIME) \\
@@ -1079,6 +1098,9 @@ cat > sh.config <<EOF
# Whether the Xapian version in use supports compaction
NOTMUCH_HAVE_XAPIAN_COMPACT=${have_xapian_compact}
+# Which backend will Xapian use by default?
+NOTMUCH_DEFAULT_XAPIAN_BACKEND=${default_xapian_backend}
+
# do we have man pages?
NOTMUCH_HAVE_MAN=$((have_sphinx))
diff --git a/crypto.c b/crypto.c
index a6eb27db..3dabc97b 100644
--- a/crypto.c
+++ b/crypto.c
@@ -22,14 +22,20 @@
/* Create a GPG context (GMime 2.6) */
static notmuch_crypto_context_t *
-create_gpg_context (const char *gpgpath)
+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, gpgpath ? gpgpath : "gpg");
- if (! gpgctx)
+ 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);
@@ -37,12 +43,57 @@ create_gpg_context (const char *gpgpath)
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");
@@ -55,19 +106,15 @@ notmuch_crypto_get_context (notmuch_crypto_t *crypto, const char *protocol)
* parameter names as defined in this document are
* case-insensitive." Thus, we use strcasecmp for the protocol.
*/
- if (strcasecmp (protocol, "application/pgp-signature") == 0 ||
- strcasecmp (protocol, "application/pgp-encrypted") == 0) {
- if (! crypto->gpgctx) {
- crypto->gpgctx = create_gpg_context (crypto->gpgpath);
- if (! crypto->gpgctx)
- fprintf (stderr, "Failed to construct gpg context.\n");
- }
- cryptoctx = crypto->gpgctx;
- } else {
- fprintf (stderr, "Unknown or unsupported cryptographic protocol.\n");
+ for (i = 0; i < ARRAY_SIZE (protocols); i++) {
+ if (strcasecmp (protocol, protocols[i].protocol) == 0)
+ return protocols[i].get_context (crypto);
}
- return cryptoctx;
+ fprintf (stderr, "Unknown or unsupported cryptographic protocol %s.\n",
+ protocol);
+
+ return NULL;
}
int
@@ -78,5 +125,10 @@ notmuch_crypto_cleanup (notmuch_crypto_t *crypto)
crypto->gpgctx = NULL;
}
+ if (crypto->pkcs7ctx) {
+ g_object_unref (crypto->pkcs7ctx);
+ crypto->pkcs7ctx = NULL;
+ }
+
return 0;
}
diff --git a/debian/changelog b/debian/changelog
index 16ad765a..6faabd0b 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,22 @@
+notmuch (0.22-1) unstable; urgency=medium
+
+ * New upstream release. See /usr/share/doc/notmuch/NEWS for new
+ features and bug fixes.
+
+ -- David Bremner <bremner@debian.org> Tue, 26 Apr 2016 21:31:44 -0300
+
+notmuch (0.22~rc1-1) experimental; urgency=medium
+
+ * Upstream release candidate
+
+ -- David Bremner <bremner@debian.org> Sun, 24 Apr 2016 18:03:15 -0300
+
+notmuch (0.22~rc0-1) experimental; urgency=medium
+
+ * Upstream release candidate
+
+ -- David Bremner <bremner@debian.org> Sat, 16 Apr 2016 08:45:32 -0300
+
notmuch (0.21-3~bpo8+1) jessie-backports; urgency=medium
* Rebuild for jessie-backports.
diff --git a/debian/control b/debian/control
index 7e6a548c..a46e391b 100644
--- a/debian/control
+++ b/debian/control
@@ -7,6 +7,7 @@ Uploaders:
David Bremner <bremner@debian.org>
Build-Conflicts: ruby1.8, gdb-minimal, gdb [s390x ia64 armel ppc64el mips mipsel mips64el]
Build-Depends:
+ dpkg-dev (>= 1.17.14),
debhelper (>= 9),
pkg-config,
libxapian-dev,
@@ -22,6 +23,7 @@ Build-Depends:
emacs23-nox | emacs23 (>=23~) | emacs23-lucid (>=23~),
gdb [!s390x !ia64 !armel !ppc64el !mips !mipsel !mips64el],
dtach (>= 0.8),
+ gpgsm <!nocheck>,
bash-completion (>=1.9.0~)
Standards-Version: 3.9.6
Homepage: http://notmuchmail.org/
@@ -31,7 +33,7 @@ Vcs-Browser: http://git.notmuchmail.org/git/notmuch
Package: notmuch
Architecture: any
Depends: libnotmuch4 (= ${binary:Version}), ${shlibs:Depends}, ${misc:Depends}
-Recommends: notmuch-emacs | notmuch-vim | notmuch-mutt | alot, gnupg-agent
+Recommends: notmuch-emacs | notmuch-vim | notmuch-mutt | alot, gnupg-agent, gpgsm
Description: thread-based email index, search and tagging
Notmuch is a system for indexing, searching, reading, and tagging
large collections of email messages in maildir or mh format. It uses
diff --git a/devel/STYLE b/devel/STYLE
index 24bd5482..da653124 100644
--- a/devel/STYLE
+++ b/devel/STYLE
@@ -25,9 +25,7 @@ The following nonsense code demonstrates many aspects of the style:
static some_type
function (param_type param, param_type param)
{
- int i;
-
- for (i = 0; i < 10; i++) {
+ for (int i = 0; i < 10; i++) {
int j;
j = i + 10;
@@ -64,12 +62,19 @@ function (param_type param, param_type param)
* Code lines should be less than 80 columns and comments should be
wrapped at 70 columns.
+* Variable declarations should be at the top of a block; C99 style
+ control variable declarations in for loops are also OK.
+
Naming
------
* Use lowercase_with_underscores for function, variable, and type
names.
+* Except for variables with extremely small scope, and perhaps loop
+ indices, when naming variables and functions, err on the side of
+ verbosity.
+
* All structs should be typedef'd to a name ending with _t. If the
struct has a tag, it should be the same as the typedef name, minus
the trailing _t.
diff --git a/devel/nmbug/doc/.gitignore b/devel/nmbug/doc/.gitignore
new file mode 100644
index 00000000..4930881a
--- /dev/null
+++ b/devel/nmbug/doc/.gitignore
@@ -0,0 +1,2 @@
+*.pyc
+_build
diff --git a/devel/nmbug/doc/Makefile b/devel/nmbug/doc/Makefile
new file mode 100644
index 00000000..7ea3ae71
--- /dev/null
+++ b/devel/nmbug/doc/Makefile
@@ -0,0 +1,38 @@
+# Makefile for Sphinx documentation
+#
+
+# You can set these variables from the command line.
+SPHINXOPTS =
+SPHINXBUILD = sphinx-build
+DOCBUILDDIR := _build
+
+SRCDIR ?= .
+ALLSPHINXOPTS := -d $(DOCBUILDDIR)/doctrees $(SPHINXOPTS) $(SRCDIR)
+
+MAN_RST_FILES := $(shell find $(SRCDIR)/man* -name '*.rst')
+MAN_ROFF_FILES := $(patsubst $(SRCDIR)/man%.rst,$(DOCBUILDDIR)/man/man%,$(MAN_RST_FILES))
+MAN_GZIP_FILES := $(addsuffix .gz,$(MAN_ROFF_FILES))
+
+.PHONY: build-man
+build-man: $(MAN_GZIP_FILES)
+
+%.gz: %
+ rm -f $@ && gzip --stdout $^ > $@
+
+$(MAN_ROFF_FILES): $(DOCBUILDDIR)/.roff.stamp
+
+# By using $(DOCBUILDDIR)/.roff.stamp instead of $(MAN_ROFF_FILES), we
+# convey to make that a single invocation of this recipe builds all
+# of the roff files. This prevents parallel make from starting an
+# instance of this recipe for each roff file.
+$(DOCBUILDDIR)/.roff.stamp $(MAN_ROFF_FILES): $(MAN_RST_FILES)
+ mkdir -p $(DOCBUILDDIR)
+ touch $(DOCBUILDDIR)/.roff.stamp
+ $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(DOCBUILDDIR)/man
+ for section in 1 5; do \
+ mkdir -p $(DOCBUILDDIR)/man/man$${section}; \
+ mv $(DOCBUILDDIR)/man/*.$${section} $(DOCBUILDDIR)/man/man$${section}; \
+ done
+
+clean:
+ rm -rf $(DOCBUILDDIR) $(SRCDIR)/conf.pyc
diff --git a/devel/nmbug/doc/conf.py b/devel/nmbug/doc/conf.py
new file mode 100644
index 00000000..29379d03
--- /dev/null
+++ b/devel/nmbug/doc/conf.py
@@ -0,0 +1,67 @@
+# -*- coding: utf-8 -*-
+
+import os.path
+
+# The suffix of source filenames.
+source_suffix = '.rst'
+
+# The master toctree document.
+master_doc = 'index'
+
+# General information about the project.
+project = 'notmuch'
+authors = 'Carl Worth and many others'
+copyright = '2009-2015, {0}'.format(authors)
+
+location = os.path.dirname(__file__)
+
+dirname = location
+while True:
+ version_file = os.path.join(dirname, 'version')
+ if os.path.exists(version_file):
+ with open(version_file,'r') as f:
+ version = f.read().strip()
+ break
+ if dirname == '/':
+ raise ValueError(
+ 'no version file found in this directory or its ancestors')
+ dirname = os.path.dirname(dirname)
+
+# The full version, including alpha/beta/rc tags.
+release = version
+
+# List of patterns, relative to source directory, that match files and
+# directories to ignore when looking for source files.
+exclude_patterns = ['_build']
+
+# -- Options for manual page output ---------------------------------------
+
+# One entry per manual page. List of tuples
+# (source start file, name, description, authors, manual section).
+
+man_pages = [
+ ('man1/notmuch-report.1', 'notmuch-report',
+ 'generate reports from notmuch queries', [authors], 1),
+ ('man5/notmuch-report.json.5', 'notmuch-report.json',
+ 'configure notmuch-report', [authors], 5),
+]
+
+# If true, show URL addresses after external links.
+#man_show_urls = False
+
+# -- Options for Texinfo output -------------------------------------------
+
+# Grouping the document tree into Texinfo files. List of tuples
+# (source start file, target name, title, author,
+# dir menu entry, description, category)
+# If true, do not generate a @detailmenu in the "Top" node's menu.
+texinfo_no_detailmenu = True
+
+texinfo_documents = [
+ ('man1/notmuch-report.1', 'notmuch-report',
+ 'generate reports from notmuch queries', authors, 'notmuch-report',
+ 'generate reports from notmuch queries', 'Miscellaneous'),
+ ('man5/notmuch-report.json.5', 'notmuch-report.json',
+ 'configure notmuch-report', authors, 'notmuch-report.json',
+ 'configure notmuch-report', 'Miscellaneous'),
+]
diff --git a/devel/nmbug/doc/index.rst b/devel/nmbug/doc/index.rst
new file mode 100644
index 00000000..51ac59ec
--- /dev/null
+++ b/devel/nmbug/doc/index.rst
@@ -0,0 +1,17 @@
+Welcome to notmuch's dev-tool documentation!
+============================================
+
+Contents:
+
+.. toctree::
+ :titlesonly:
+
+ man1/notmuch-report.1
+ man5/notmuch-report.json.5
+
+Indices and tables
+==================
+
+* :ref:`genindex`
+* :ref:`modindex`
+* :ref:`search`
diff --git a/devel/nmbug/doc/man1/notmuch-report.1.rst b/devel/nmbug/doc/man1/notmuch-report.1.rst
new file mode 100644
index 00000000..dfd82df3
--- /dev/null
+++ b/devel/nmbug/doc/man1/notmuch-report.1.rst
@@ -0,0 +1,54 @@
+==============
+notmuch-report
+==============
+
+SYNOPSIS
+========
+
+**notmuch-report** [options ...]
+
+DESCRIPTION
+===========
+
+Generate HTML or plain-text reports showing query results.
+
+OPTIONS
+=======
+
+ ``-h``, ``--help``
+
+ Show a help message, including a list of available options, and
+ exit.
+
+ ``--text``
+ Output plain text instead of HTML.
+
+ ``--config`` <PATH>
+ Load config from given file. The format is described in
+ **notmuch-report.json(5)**. If this option is not set,
+ **notmuch-report** loads the config from the Git repository at
+ ``NMBGIT``. See :ref:`NMBGIT <NMBGIT>` for details.
+
+ ``--list-views``
+ List available views (by title) and exit.
+
+ ``--get-query`` <VIEW>
+ Print the configured query for view matching the given title.
+
+ENVIRONMENT
+===========
+
+.. _NMBGIT:
+
+ ``NMBGIT``
+ If ``--config PATH`` is not set, **notmuch-report** will attempt
+ to load a config file named ``notmuch-report.json`` from the
+ ``config`` branch of the ``NMBGIT`` repository (defaulting to
+ ``~/.nmbug``).
+
+SEE ALSO
+========
+
+**notmuch(1)**, **notmuch-report.json(5)**, **notmuch-search(1)**,
+ **notmuch-tag(1)**
+
diff --git a/devel/nmbug/doc/man5/notmuch-report.json.5.rst b/devel/nmbug/doc/man5/notmuch-report.json.5.rst
new file mode 100644
index 00000000..4b5f84a8
--- /dev/null
+++ b/devel/nmbug/doc/man5/notmuch-report.json.5.rst
@@ -0,0 +1,129 @@
+==============
+notmuch-report
+==============
+
+NAME
+====
+
+notmuch-report.json - configure output for **notmuch-report(1)**
+
+DESCRIPTION
+===========
+
+The config file is JSON_ with the following fields:
+
+meta
+ An object with page-wide information
+
+ title
+ Page title used in the default header.
+
+ blurb
+ Introduction paragraph used in the default header.
+
+ header
+ `Python format string`_ for the HTML header. Optional. It is
+ formatted with the following context:
+
+ date
+ The current UTC date.
+
+ datetime
+ The current UTC date-time.
+
+ title
+ The **meta.title** value.
+
+ blurb
+ The **meta.blurb** value.
+
+ encoding
+ The encoding used for the output file.
+
+ inter_message_padding
+ 0.25em, for consistent CSS generation.
+
+ border_radius
+ 0.5em, for consistent CSS generation.
+
+ footer
+ `Python format string`_ for the HTML footer. It is formatted with
+ the same context used for **meta.header**. Optional.
+
+ message-url
+ `Python format string`_ for message-linking URLs. Optional.
+ Defaults to linking Gmane_. It is formatted with the following
+ context:
+
+ message-id
+ The quoted_ message ID.
+
+ subject
+ The message subject.
+
+views
+ An array of view objects, where each object has the following
+ fields:
+
+ title
+ Header text for the view.
+
+ comment
+ Paragraph describing the view in more detail. Optional.
+
+ id
+ Anchor string for the view. Optional, defaulting to a slugged
+ form of the view title
+
+ query
+ An array of strings, which will be joined with 'and' to form the
+ view query.
+
+.. _Gmane: http://gmane.org/
+.. _JSON: http://json.org/
+.. _Python format string: https://docs.python.org/3/library/string.html#formatstrings
+.. _quoted: https://docs.python.org/3/library/urllib.parse.html#urllib.parse.quote
+
+EXAMPLE
+=======
+
+::
+
+ {
+ "meta": {
+ "title": "Notmuch Patches",
+ "blurb": "For more information see <a href=\"http://notmuchmail.org/nmbug\">nmbug</a>",
+ "header": "<html><head></head><body><h1>{title}</h1><p>{blurb}</p><h2>Views</h2>",
+ "footer": "<hr><p>Generated: {datetime}</p></html>",
+ "message-url": "http://mid.gmane.org/{message-id}"
+ },
+ "views": [
+ {
+ "title": "Bugs",
+ "comment": "Unresolved bugs.",
+ "query": [
+ "tag:notmuch::bug",
+ "not tag:notmuch::fixed",
+ "not tag:notmuch::wontfix"
+ ]
+ },
+ {
+ "title": "Review",
+ "comment": "These patches are under review, or waiting for feedback.",
+ "id": "under-review",
+ "query": [
+ "tag:notmuch::patch",
+ "not tag:notmuch::pushed",
+ "not tag:notmuch::obsolete",
+ "not tag:notmuch::stale",
+ "not tag:notmuch::wontfix",
+ "(tag:notmuch::moreinfo or tag:notmuch::needs-review)"
+ ]
+ }
+ ]
+ }
+
+SEE ALSO
+========
+
+**notmuch(1)**, **notmuch-report(1)**, **notmuch-search(1)**, **notmuch-tag(1)**
diff --git a/devel/nmbug/nmbug b/devel/nmbug/nmbug
index 81f582ce..0787b2ba 100755
--- a/devel/nmbug/nmbug
+++ b/devel/nmbug/nmbug
@@ -608,6 +608,8 @@ def _index_tags():
stdin=_subprocess.PIPE,
additional_env={'GIT_INDEX_FILE': path}) as git:
for line in notmuch.stdout:
+ if line.strip().startswith('#'):
+ continue
(tags_string, id) = [_.strip() for _ in line.split(' -- id:')]
tags = [
_unquote(tag[len(prefix):])
diff --git a/devel/nmbug/nmbug-status b/devel/nmbug/notmuch-report
index b36b6ad3..87390c1e 100755
--- a/devel/nmbug/nmbug-status
+++ b/devel/nmbug/notmuch-report
@@ -19,11 +19,11 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see http://www.gnu.org/licenses/ .
-"""Generate HTML for one or more notmuch searches.
+"""Generate text and/or HTML for one or more notmuch searches.
Messages matching each search are grouped by thread. Each message
that contains both a subject and message-id will have the displayed
-subject link to the Gmane view of the message.
+subject link to an archive view of the message (defaulting to Gmane).
"""
from __future__ import print_function
@@ -88,7 +88,7 @@ def read_config(path=None, encoding=None):
else:
nmbhome = os.getenv('NMBGIT', os.path.expanduser('~/.nmbug'))
branch = 'config'
- filename = 'status-config.json'
+ filename = 'notmuch-report.json'
# read only the first line from the pipe
sha1_bytes = subprocess.Popen(
@@ -109,9 +109,9 @@ def read_config(path=None, encoding=None):
status = p.wait()
if status != 0:
raise ConfigError(
- ("Missing status-config.json in branch '{branch}' of"
- '{nmbgit}. Add the file or explicitly set --config.'
- ).format(branch=branch, nmbgit=nmbhome))
+ ("Missing {filename} in branch '{branch}' of {nmbgit}. "
+ 'Add the file or explicitly set --config.'
+ ).format(filename=filename, branch=branch, nmbgit=nmbhome))
config_json = config_bytes.decode(encoding)
try:
@@ -167,7 +167,8 @@ class Page (object):
view['title'], sort_key))
if 'query-string' not in view:
query = view['query']
- view['query-string'] = ' and '.join(query)
+ view['query-string'] = ' and '.join(
+ '( {} )'.format(q) for q in query)
q = notmuch.Query(database, view['query-string'])
q.set_sort(sort)
threads = self._get_threads(messages=q.search_messages())
@@ -232,6 +233,10 @@ class Page (object):
class HtmlPage (Page):
_slug_regexp = re.compile('\W+')
+ def __init__(self, message_url_template, **kwargs):
+ self.message_url_template = message_url_template
+ super(HtmlPage, self).__init__(**kwargs)
+
def _write_header(self, views, stream):
super(HtmlPage, self)._write_header(views=views, stream=stream)
stream.write('<ul>\n')
@@ -292,8 +297,9 @@ class HtmlPage (Page):
'message-id': quote(display_data['message-id']),
'subject': xml.sax.saxutils.escape(display_data['subject']),
}
+ d['url'] = self.message_url_template.format(**d)
display_data['subject'] = (
- '<a href="http://mid.gmane.org/{message-id}">{subject}</a>'
+ '<a href="{url}">{subject}</a>'
).format(**d)
for key in ['message-id', 'from']:
if key in display_data:
@@ -304,14 +310,17 @@ class HtmlPage (Page):
return self._slug_regexp.sub('-', string)
parser = argparse.ArgumentParser(description=__doc__)
-parser.add_argument('--text', help='output plain text format',
- action='store_true')
-parser.add_argument('--config', help='load config from given file',
- metavar='PATH')
-parser.add_argument('--list-views', help='list views',
- action='store_true')
-parser.add_argument('--get-query', help='get query for view',
- metavar='VIEW')
+parser.add_argument(
+ '--text', action='store_true', help='output plain text format')
+parser.add_argument(
+ '--config', metavar='PATH',
+ help='load config from given file. '
+ 'The format is described in notmuch-report.json(5).')
+parser.add_argument(
+ '--list-views', action='store_true', help='list views')
+parser.add_argument(
+ '--get-query', metavar='VIEW', help='get query for view')
+
args = parser.parse_args()
@@ -327,6 +336,15 @@ header_template = config['meta'].get('header', '''<!DOCTYPE html>
<meta http-equiv="Content-Type" content="text/html; charset={encoding}" />
<title>{title}</title>
<style media="screen" type="text/css">
+ h1 {{
+ font-size: 1.5em;
+ }}
+ h2 {{
+ font-size: 1.17em;
+ }}
+ h3 {{
+ font-size: 100%;
+ }}
table {{
border-spacing: 0;
}}
@@ -367,15 +385,16 @@ header_template = config['meta'].get('header', '''<!DOCTYPE html>
</style>
</head>
<body>
-<h2>{title}</h2>
+<h1>{title}</h1>
+<p>
{blurb}
</p>
-<h3>Views</h3>
+<h2>Views</h2>
''')
footer_template = config['meta'].get('footer', '''
<hr>
-<p>Generated: {datetime}
+<p>Generated: {datetime}</p>
</body>
</html>
''')
@@ -395,6 +414,8 @@ _PAGES['text'] = Page()
_PAGES['html'] = HtmlPage(
header=header_template.format(**context),
footer=footer_template.format(**context),
+ message_url_template=config['meta'].get(
+ 'message-url', 'http://mid.gmane.org/{message-id}'),
)
if args.list_views:
@@ -404,7 +425,7 @@ if args.list_views:
elif args.get_query != None:
for view in config['views']:
if args.get_query == view['title']:
- print(' and '.join(view['query']))
+ print(' and '.join('( {} )'.format(q) for q in view['query']))
sys.exit(0)
else:
# only import notmuch if needed
diff --git a/devel/nmbug/status-config.json b/devel/nmbug/notmuch-report.json
index b9269462..48b6f19f 100644
--- a/devel/nmbug/status-config.json
+++ b/devel/nmbug/notmuch-report.json
@@ -62,7 +62,7 @@
"not tag:notmuch::obsolete",
"not tag:notmuch::stale",
"not tag:notmuch::wontfix",
- "(tag:notmuch::moreinfo or tag:notmuch::needs-review)"
+ "tag:notmuch::moreinfo or tag:notmuch::needs-review"
],
"title": "Review"
}
diff --git a/devel/release-checks.sh b/devel/release-checks.sh
index 8604a9f7..5a7578b8 100755
--- a/devel/release-checks.sh
+++ b/devel/release-checks.sh
@@ -175,6 +175,21 @@ case $news_date in
append_emsg "Date '$news_date' in NEWS file is not in format (yyyy-mm-dd)"
esac
+year=`exec date +%Y`
+echo -n "Checking that copyright in documentation contains 2009-$year... "
+# Read the value of variable `copyright' defined in 'doc/conf.py'.
+# As __file__ is not defined when python command is given from command line,
+# it is defined before contents of 'doc/conf.py' (which dereferences __file__)
+# is executed.
+copyrightline=`exec python -c "with open('doc/conf.py') as cf: __file__ = ''; exec(cf.read()); print(copyright)"`
+case $copyrightline in
+ *2009-$year*)
+ echo Yes. ;;
+ *)
+ echo No.
+ append_emsg "The copyright in doc/conf.py line '$copyrightline' does not contain '2009-$year'"
+esac
+
if [ -n "$emsgs" ]
then
echo
diff --git a/devel/try-emacs-mua b/devel/try-emacs-mua
new file mode 100755
index 00000000..b0a62c25
--- /dev/null
+++ b/devel/try-emacs-mua
@@ -0,0 +1,157 @@
+#!/bin/sh
+:; set -x; exec "${EMACS:-emacs}" --debug-init --load "$0" "$@"; exit
+;;
+;; Try the notmuch emacs client located in ../emacs/ directory
+;;
+;; Run this without arguments; emacs window opens with some usage information
+;;
+;; Authors: Tomi Ollila <tomi.ollila@iki.fi>
+;;
+;; http://www.emacswiki.org/emacs/EmacsScripts was a useful starting point...
+;;
+;; Licence: GPLv3+
+;;
+
+(message "Starting '%s'" load-file-name)
+
+(set-buffer "*scratch*")
+
+(setq initial-buffer-choice nil
+ inhibit-startup-screen t)
+
+(when (featurep 'notmuch)
+ (insert "
+Notmuch has been loaded to this emacs (during processing of the init file)
+which means it is (most probably) loaded from different source than expected.
+
+Please run \"" (file-name-nondirectory load-file-name)
+"\" with '-q' (or '-Q') as an argument, to disable
+processing of the init file -- you can load it after emacs has started\n
+exit emacs (y or n)? ")
+ (if (y-or-n-p "exit emacs")
+ (kill-emacs)
+ (error "Stopped reading %s" load-file-name)))
+
+(let ((pdir (file-name-directory
+ (directory-file-name (file-name-directory load-file-name)))))
+ (unless (file-exists-p (concat pdir "emacs/notmuch-lib.el"))
+ (insert "Cannot find notmuch-emacs source directory
+while looking at: " pdir "emacs\n\nexit emacs (y or n)? ")
+ (if (y-or-n-p "exit emacs")
+ (kill-emacs)
+ (error "Stopped reading %s" load-file-name)))
+ (setq try-notmuch-source-directory (directory-file-name pdir)
+ try-notmuch-emacs-directory (concat pdir "emacs/")
+ load-path (cons try-notmuch-emacs-directory load-path)))
+
+;; they say advice doesn't work for primitives (functions from c source)
+;; well, these 'before' advice works for emacs 23.1 - 24.5 (at least)
+;; ...and for our purposes 24.3 is enough (there is no load-prefer-newer there)
+;; note also that the old, "obsolete" defadvice mechanism was used, but that
+;; is the only one available for emacs 23 and 24 up to 24.3.
+
+(if (boundp 'load-prefer-newer)
+ (defadvice require (before before-require activate)
+ (unless (featurep feature)
+ (message "require: %s" feature)))
+ ;; else: special require "short-circuit"; after load feature is provided...
+ ;; ... in notmuch sources we always use require and there are no loops
+ (defadvice require (before before-require activate)
+ (unless (featurep feature)
+ (message "require: %s" feature)
+ (let ((name (symbol-name feature)))
+ (if (and (string-match "^notmuch" name)
+ (file-newer-than-file-p
+ (concat try-notmuch-emacs-directory name ".el")
+ (concat try-notmuch-emacs-directory name ".elc")))
+ (load (concat try-notmuch-emacs-directory name ".el") nil nil t t)
+ )))))
+
+(insert "Found notmuch emacs client in " try-notmuch-emacs-directory "\n")
+
+(let ((notmuch-path (executable-find "notmuch")))
+ (insert "Notmuch CLI executable "
+ (if notmuch-path (concat "is " notmuch-path) "not found!") "\n"))
+
+(condition-case err
+;; "opportunistic" load-prefer-newer -- will be effective since emacs 24.4
+ (let ((load-prefer-newer t)
+ (force-load-messages t))
+ (require 'notmuch))
+ ;; specifying `debug' here lets the debugger run
+ ;; if `debug-on-error' is non-nil.
+ ((debug error)
+ (let ((error-message-string (error-message-string err)))
+ (insert "\nLoading notmuch failed: " error-message-string "\n")
+ (message "Loading notmuch failed: %s" error-message-string)
+ (insert "See *Messages* buffer for more information.\n")
+ (if init-file-user
+ (message "Hint: %s -q (or -Q) may help" load-file-name))
+ (pop-to-buffer "*Messages*")
+ (error "Stopped reading %s" load-file-name))))
+
+(insert "
+Go to the end of the following lines and type C-x C-e to evaluate
+(or C-j which is shorter but inserts evaluation results into buffer)
+
+To \"disable\" mail sending, evaluate
+* (setq message-send-mail-function (lambda () t))
+")
+
+(if (file-exists-p (concat try-notmuch-source-directory "/notmuch"))
+ (insert "
+To use accompanied notmuch binary from the same source, evaluate
+* (setq exec-path (cons \"" try-notmuch-source-directory "\" exec-path))
+Note: Evaluating the above may be followed by unintended database
+upgrade and getting back to old version may require dump & restore.
+"))
+
+(if init-file-user ;; nil, if '-q' or '-Q' is given, but no '-u' 'USER'
+ (insert "
+Your init file was processed during emacs startup. If you want to test
+notmuch emacs mail client without your emacs init file interfering, Run\n\""
+(file-name-nondirectory load-file-name) "\" with '-q' (or '-Q') as an argument.
+")
+ (let ((emacs-init-file-name) (notmuch-init-file-name))
+ ;; determining init file name in startup.el/command-line is too complicated
+ ;; to be duplicated here; these 3 file names covers most of the users
+ (mapc (lambda (fn) (if (file-exists-p fn) (setq emacs-init-file-name fn)))
+ '("~/.emacs.d/init.el" "~/.emacs" "~/.emacs.el"))
+ (setq notmuch-init-file-name "~/.emacs.d/notmuch-config.el")
+ (unless (file-exists-p notmuch-init-file-name)
+ (setq notmuch-init-file-name nil))
+ (if (and emacs-init-file-name notmuch-init-file-name)
+ (insert "
+If you want to load your initialization files now, evaluate\n* (progn")
+ (if (or emacs-init-file-name notmuch-init-file-name)
+ (insert "
+If you want to load your initialization file now, evaluate\n*")))
+ (if emacs-init-file-name
+ (insert " (load \"" emacs-init-file-name "\")"))
+ (if notmuch-init-file-name
+ (insert " (load \"" notmuch-init-file-name "\")"))
+ (if (and emacs-init-file-name notmuch-init-file-name)
+ (insert ")"))
+ (if (or emacs-init-file-name notmuch-init-file-name)
+ (insert "\n")))
+ (if (>= emacs-major-version 24)
+ (insert "
+If you want to use packages (e.g. company from elpa) evaluate
+* (progn (require 'package) (package-initialize))
+")))
+
+(insert "
+To start notmuch (hello) screen, evaluate
+* (notmuch-hello)")
+
+(add-hook 'emacs-startup-hook
+ (lambda ()
+ (with-current-buffer "*scratch*"
+ (lisp-interaction-mode)
+ (goto-char (point-min))
+ (forward-line 2)
+ (set-buffer-modified-p nil))))
+
+;; Local Variables:
+;; mode: emacs-lisp
+;; End:
diff --git a/doc/conf.py b/doc/conf.py
index 65adafeb..8b932966 100644
--- a/doc/conf.py
+++ b/doc/conf.py
@@ -12,7 +12,7 @@ master_doc = 'index'
# General information about the project.
project = u'notmuch'
-copyright = u'2009-2015, Carl Worth and many others'
+copyright = u'2009-2016, Carl Worth and many others'
location = os.path.dirname(__file__)
diff --git a/doc/man1/notmuch-reply.rst b/doc/man1/notmuch-reply.rst
index cfbd4ea8..d73f8f1c 100644
--- a/doc/man1/notmuch-reply.rst
+++ b/doc/man1/notmuch-reply.rst
@@ -13,8 +13,10 @@ DESCRIPTION
Constructs a reply template for a set of messages.
To make replying to email easier, **notmuch reply** takes an existing
-set of messages and constructs a suitable mail template. The Reply-to:
-header (if any, otherwise From:) is used for the To: address. Unless
+set of messages and constructs a suitable mail template. Its To:
+address is set according to the original email in this way: if the
+Reply-to: header is present and different from any To:/Cc: address it
+is used, otherwise From: header is used. Unless
``--reply-to=sender`` is specified, values from the To: and Cc: headers
are copied, but not including any of the current user's email addresses
(as configured in primary\_mail or other\_email in the .notmuch-config
diff --git a/emacs/Makefile.local b/emacs/Makefile.local
index 1109cfa6..2d6aedbd 100644
--- a/emacs/Makefile.local
+++ b/emacs/Makefile.local
@@ -20,6 +20,7 @@ emacs_sources := \
$(dir)/notmuch-print.el \
$(dir)/notmuch-version.el \
$(dir)/notmuch-jump.el \
+ $(dir)/notmuch-company.el
$(dir)/notmuch-version.el: $(dir)/Makefile.local version.stamp
$(dir)/notmuch-version.el: $(srcdir)/$(dir)/notmuch-version.el.tmpl
@@ -52,6 +53,10 @@ $(dir)/.eldeps: $(dir)/Makefile.local $(dir)/make-deps.el $(emacs_sources)
$(dir)/.eldeps.x: $(dir)/.eldeps
@cmp -s $^ $@ || cp $^ $@
-include $(dir)/.eldeps.x
+
+# Add the one dependency make-deps.el does not have visibility to.
+$(dir)/notmuch-lib.elc: $(dir)/notmuch-version.elc
+
endif
CLEAN+=$(dir)/.eldeps $(dir)/.eldeps.tmp $(dir)/.eldeps.x
diff --git a/emacs/coolj.el b/emacs/coolj.el
index 60af60af..77550602 100644
--- a/emacs/coolj.el
+++ b/emacs/coolj.el
@@ -143,3 +143,5 @@ If no break point is found, return nil."
t)))
(provide 'coolj)
+
+;;; coolj.el ends here
diff --git a/emacs/make-deps.el b/emacs/make-deps.el
index a1cd731f..24c1a457 100644
--- a/emacs/make-deps.el
+++ b/emacs/make-deps.el
@@ -19,6 +19,8 @@
;;
;; Authors: Austin Clements <aclements@csail.mit.edu>
+;;; Code:
+
(defun batch-make-deps ()
"Invoke `make-deps' for each file on the command line."
@@ -64,3 +66,5 @@ rules will be given relative to DIR, or `default-directory'."
(file-name-sans-extension
(file-relative-name fname dir)))))))))
(end-of-file nil))))
+
+;;; make-deps.el ends here
diff --git a/emacs/notmuch-address.el b/emacs/notmuch-address.el
index fde3c1b2..aafbe5fb 100644
--- a/emacs/notmuch-address.el
+++ b/emacs/notmuch-address.el
@@ -1,4 +1,4 @@
-;; notmuch-address.el --- address completion with notmuch
+;;; notmuch-address.el --- address completion with notmuch
;;
;; Copyright © David Edmondson
;;
@@ -19,15 +19,24 @@
;;
;; Authors: David Edmondson <dme@dme.org>
-(require 'message)
+;;; Code:
+(require 'message)
+(require 'notmuch-parser)
+(require 'notmuch-lib)
+(require 'notmuch-company)
;;
+(declare-function company-manual-begin "company")
-(defcustom notmuch-address-command "notmuch-addresses"
+(defcustom notmuch-address-command 'internal
"The command which generates possible addresses. It must take a
single argument and output a list of possible matches, one per
-line."
- :type 'string
+line. The default value of `internal' uses built-in address
+completion."
+ :type '(radio
+ (const :tag "Use internal address completion" internal)
+ (const :tag "Disable address completion" nil)
+ (string :tag "Use external completion command" "notmuch-addresses"))
:group 'notmuch-send
:group 'notmuch-external)
@@ -42,53 +51,105 @@ to know how address selection is made by default."
:group 'notmuch-send
:group 'notmuch-external)
+(defvar notmuch-address-last-harvest 0
+ "Time of last address harvest")
+
+(defvar notmuch-address-completions (make-hash-table :test 'equal)
+ "Hash of email addresses for completion during email composition.
+ This variable is set by calling `notmuch-address-harvest'.")
+
+(defvar notmuch-address-full-harvest-finished nil
+ "t indicates that full completion address harvesting has been
+finished")
+
(defun notmuch-address-selection-function (prompt collection initial-input)
"Call (`completing-read'
PROMPT COLLECTION nil nil INITIAL-INPUT 'notmuch-address-history)"
(completing-read
prompt collection nil nil initial-input 'notmuch-address-history))
-(defvar notmuch-address-message-alist-member
- '("^\\(Resent-\\)?\\(To\\|B?Cc\\|Reply-To\\|From\\|Mail-Followup-To\\|Mail-Copies-To\\):"
- . notmuch-address-expand-name))
+(defvar notmuch-address-completion-headers-regexp
+ "^\\(Resent-\\)?\\(To\\|B?Cc\\|Reply-To\\|From\\|Mail-Followup-To\\|Mail-Copies-To\\):")
(defvar notmuch-address-history nil)
(defun notmuch-address-message-insinuate ()
- (unless (memq notmuch-address-message-alist-member message-completion-alist)
- (setq message-completion-alist
- (push notmuch-address-message-alist-member message-completion-alist))))
+ (message "calling notmuch-address-message-insinuate is no longer needed"))
+
+(defcustom notmuch-address-use-company t
+ "If available, use company mode for address completion"
+ :type 'boolean
+ :group 'notmuch-send)
+
+(defun notmuch-address-setup ()
+ (let* ((use-company (and notmuch-address-use-company
+ (eq notmuch-address-command 'internal)
+ (require 'company nil t)))
+ (pair (cons notmuch-address-completion-headers-regexp
+ (if use-company
+ #'company-manual-begin
+ #'notmuch-address-expand-name))))
+ (when use-company
+ (notmuch-company-setup))
+ (unless (memq pair message-completion-alist)
+ (setq message-completion-alist
+ (push pair message-completion-alist)))))
+
+(defun notmuch-address-matching (substring)
+ "Returns a list of completion candidates matching SUBSTRING.
+The candidates are taken from `notmuch-address-completions'."
+ (let ((candidates)
+ (re (regexp-quote substring)))
+ (maphash (lambda (key val)
+ (when (string-match re key)
+ (push key candidates)))
+ notmuch-address-completions)
+ candidates))
(defun notmuch-address-options (original)
- (process-lines notmuch-address-command original))
+ "Returns a list of completion candidates. Uses either
+elisp-based implementation or older implementation requiring
+external commands."
+ (cond
+ ((eq notmuch-address-command 'internal)
+ (when (not notmuch-address-full-harvest-finished)
+ ;; First, run quick synchronous harvest based on what the user
+ ;; entered so far
+ (notmuch-address-harvest (format "to:%s*" original) t))
+ (prog1 (notmuch-address-matching original)
+ ;; Then start the (potentially long-running) full asynchronous harvest if necessary
+ (notmuch-address-harvest-trigger)))
+ (t
+ (process-lines notmuch-address-command original))))
(defun notmuch-address-expand-name ()
- (let* ((end (point))
- (beg (save-excursion
- (re-search-backward "\\(\\`\\|[\n:,]\\)[ \t]*")
- (goto-char (match-end 0))
- (point)))
- (orig (buffer-substring-no-properties beg end))
- (completion-ignore-case t)
- (options (with-temp-message "Looking for completion candidates..."
- (notmuch-address-options orig)))
- (num-options (length options))
- (chosen (cond
- ((eq num-options 0)
- nil)
- ((eq num-options 1)
- (car options))
- (t
- (funcall notmuch-address-selection-function
- (format "Address (%s matches): " num-options)
- (cdr options) (car options))))))
- (if chosen
- (progn
- (push chosen notmuch-address-history)
- (delete-region beg end)
- (insert chosen))
- (message "No matches.")
- (ding))))
+ (when notmuch-address-command
+ (let* ((end (point))
+ (beg (save-excursion
+ (re-search-backward "\\(\\`\\|[\n:,]\\)[ \t]*")
+ (goto-char (match-end 0))
+ (point)))
+ (orig (buffer-substring-no-properties beg end))
+ (completion-ignore-case t)
+ (options (with-temp-message "Looking for completion candidates..."
+ (notmuch-address-options orig)))
+ (num-options (length options))
+ (chosen (cond
+ ((eq num-options 0)
+ nil)
+ ((eq num-options 1)
+ (car options))
+ (t
+ (funcall notmuch-address-selection-function
+ (format "Address (%s matches): " num-options)
+ (cdr options) (car options))))))
+ (if chosen
+ (progn
+ (push chosen notmuch-address-history)
+ (delete-region beg end)
+ (insert chosen))
+ (message "No matches.")
+ (ding)))))
;; Copied from `w3m-which-command'.
(defun notmuch-address-locate-command (command)
@@ -109,11 +170,85 @@ to know how address selection is made by default."
(not (file-directory-p bin))))
(throw 'found-command bin))))))))
-;; If we can find the program specified by `notmuch-address-command',
-;; insinuate ourselves into `message-mode'.
-(when (notmuch-address-locate-command notmuch-address-command)
- (notmuch-address-message-insinuate))
+(defun notmuch-address-harvest-addr (result)
+ (let ((name-addr (plist-get result :name-addr)))
+ (puthash name-addr t notmuch-address-completions)))
+
+(defun notmuch-address-harvest-handle-result (obj)
+ (notmuch-address-harvest-addr obj))
+
+(defun notmuch-address-harvest-filter (proc string)
+ (when (buffer-live-p (process-buffer proc))
+ (with-current-buffer (process-buffer proc)
+ (save-excursion
+ (goto-char (point-max))
+ (insert string))
+ (notmuch-sexp-parse-partial-list
+ 'notmuch-address-harvest-handle-result (process-buffer proc)))))
+
+(defvar notmuch-address-harvest-procs '(nil . nil)
+ "The currently running harvests.
+
+The car is a partial harvest, and the cdr is a full harvest")
+
+(defun notmuch-address-harvest (&optional filter-query synchronous callback)
+ "Collect addresses completion candidates. It queries the
+notmuch database for all messages sent by the user optionally
+matching FILTER-QUERY (if not nil). It collects the destination
+addresses from those messages and stores them in
+`notmuch-address-completions'. Address harvesting may take some
+time so the address collection runs asynchronously unless
+SYNCHRONOUS is t. In case of asynchronous execution, CALLBACK is
+called when harvesting finishes."
+ (let* ((from-me-query (mapconcat (lambda (x) (concat "from:" x)) (notmuch-user-emails) " or "))
+ (query (if filter-query
+ (format "(%s) and (%s)" from-me-query filter-query)
+ from-me-query))
+ (args `("address" "--format=sexp" "--format-version=2"
+ "--output=recipients"
+ "--deduplicate=address"
+ ,query)))
+ (if synchronous
+ (mapc #'notmuch-address-harvest-addr
+ (apply 'notmuch-call-notmuch-sexp args))
+ ;; Asynchronous
+ (let* ((current-proc (if filter-query
+ (car notmuch-address-harvest-procs)
+ (cdr notmuch-address-harvest-procs)))
+ (proc-name (format "notmuch-address-%s-harvest"
+ (if filter-query "partial" "full")))
+ (proc-buf (concat " *" proc-name "*")))
+ ;; Kill any existing process
+ (when current-proc
+ (kill-buffer (process-buffer current-proc))) ; this also kills the process
+
+ (setq current-proc
+ (apply 'notmuch-start-notmuch proc-name proc-buf
+ callback ; process sentinel
+ args))
+ (set-process-filter current-proc 'notmuch-address-harvest-filter)
+ (set-process-query-on-exit-flag current-proc nil)
+ (if filter-query
+ (setcar notmuch-address-harvest-procs current-proc)
+ (setcdr notmuch-address-harvest-procs current-proc)))))
+ ;; return value
+ nil)
+
+(defun notmuch-address-harvest-trigger ()
+ (let ((now (float-time)))
+ (when (> (- now notmuch-address-last-harvest) 86400)
+ (setq notmuch-address-last-harvest now)
+ (notmuch-address-harvest nil nil
+ (lambda (proc event)
+ ;; If harvest fails, we want to try
+ ;; again when the trigger is next
+ ;; called
+ (if (string= event "finished\n")
+ (setq notmuch-address-full-harvest-finished t)
+ (setq notmuch-address-last-harvest 0)))))))
;;
(provide 'notmuch-address)
+
+;;; notmuch-address.el ends here
diff --git a/emacs/notmuch-company.el b/emacs/notmuch-company.el
new file mode 100644
index 00000000..b881d6dc
--- /dev/null
+++ b/emacs/notmuch-company.el
@@ -0,0 +1,88 @@
+;;; notmuch-company.el --- Mail address completion for notmuch via company-mode -*- lexical-binding: t -*-
+
+;; Authors: Trevor Jim <tjim@mac.com>
+;; Michal Sojka <sojkam1@fel.cvut.cz>
+;;
+;; Keywords: mail, completion
+
+;; 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/>.
+
+;;; Commentary:
+
+;; To enable this, install company mode (https://company-mode.github.io/)
+;;
+;; NB company-minimum-prefix-length defaults to 3 so you don't get
+;; completion unless you type 3 characters
+
+;;; Code:
+
+(eval-when-compile (require 'cl))
+
+(defvar notmuch-company-last-prefix nil)
+(make-variable-buffer-local 'notmuch-company-last-prefix)
+(declare-function company-begin-backend "company")
+(declare-function company-grab "company")
+(declare-function company-mode "company")
+(declare-function company-manual-begin "company")
+(defvar company-backends)
+
+(declare-function notmuch-address-harvest "notmuch-address")
+(declare-function notmuch-address-harvest-trigger "notmuch-address")
+(declare-function notmuch-address-matching "notmuch-address")
+(defvar notmuch-address-full-harvest-finished)
+(defvar notmuch-address-completion-headers-regexp)
+
+;;;###autoload
+(defun notmuch-company-setup ()
+ (company-mode)
+ (make-local-variable 'company-backends)
+ (setq company-backends '(notmuch-company)))
+
+;;;###autoload
+(defun notmuch-company (command &optional arg &rest _ignore)
+ "`company-mode' completion back-end for `notmuch'."
+ (interactive (list 'interactive))
+ (require 'company)
+ (let ((case-fold-search t)
+ (completion-ignore-case t))
+ (case command
+ (interactive (company-begin-backend 'notmuch-company))
+ (prefix (and (derived-mode-p 'message-mode)
+ (looking-back (concat notmuch-address-completion-headers-regexp ".*")
+ (line-beginning-position))
+ (setq notmuch-company-last-prefix (company-grab "[:,][ \t]*\\(.*\\)" 1 (point-at-bol)))))
+ (candidates (cond
+ (notmuch-address-full-harvest-finished
+ ;; Update harvested addressed from time to time
+ (notmuch-address-harvest-trigger)
+ (notmuch-address-matching arg))
+ (t
+ (cons :async
+ (lambda (callback)
+ ;; First run quick asynchronous harvest based on what the user entered so far
+ (notmuch-address-harvest
+ (format "to:%s*" arg) nil
+ (lambda (_proc _event)
+ (funcall callback (notmuch-address-matching arg))
+ ;; Then start the (potentially long-running) full asynchronous harvest if necessary
+ (notmuch-address-harvest-trigger))))))))
+ (match (if (string-match notmuch-company-last-prefix arg)
+ (match-end 0)
+ 0))
+ (no-cache t))))
+
+
+(provide 'notmuch-company)
+
+;;; notmuch-company.el ends here
diff --git a/emacs/notmuch-crypto.el b/emacs/notmuch-crypto.el
index 52338249..004463c3 100644
--- a/emacs/notmuch-crypto.el
+++ b/emacs/notmuch-crypto.el
@@ -1,4 +1,4 @@
-;; notmuch-crypto.el --- functions for handling display of cryptographic metadata.
+;;; notmuch-crypto.el --- functions for handling display of cryptographic metadata.
;;
;; Copyright © Jameson Rollins
;;
@@ -19,6 +19,8 @@
;;
;; Authors: Jameson Rollins <jrollins@finestructure.net>
+;;; Code:
+
(require 'notmuch-lib)
(defcustom notmuch-crypto-process-mime nil
@@ -110,8 +112,8 @@ mode."
(setq label (concat "Bad signature (claimed key ID " keyid ")"))
(setq face 'notmuch-crypto-signature-bad)))
(t
- (setq label "Unknown signature status")
- (if status (setq label (concat label " \"" status "\"")))))
+ (setq label (concat "Unknown signature status"
+ (if status (concat ": " status))))))
(insert-button
(concat "[ " label " ]")
:type 'notmuch-crypto-status-button-type
@@ -161,7 +163,8 @@ mode."
((string= status "bad")
(setq label "Decryption error"))
(t
- (setq label (concat "Unknown encstatus \"" status "\""))))
+ (setq label (concat "Unknown encryption status"
+ (if status (concat ": " status))))))
(insert-button
(concat "[ " label " ]")
:type 'notmuch-crypto-status-button-type
@@ -173,3 +176,5 @@ mode."
;;
(provide 'notmuch-crypto)
+
+;;; notmuch-crypto.el ends here
diff --git a/emacs/notmuch-hello.el b/emacs/notmuch-hello.el
index 8bde808f..9495c1a4 100644
--- a/emacs/notmuch-hello.el
+++ b/emacs/notmuch-hello.el
@@ -1,4 +1,4 @@
-;; notmuch-hello.el --- welcome to notmuch, a frontend
+;;; notmuch-hello.el --- welcome to notmuch, a frontend
;;
;; Copyright © David Edmondson
;;
@@ -19,6 +19,8 @@
;;
;; Authors: David Edmondson <dme@dme.org>
+;;; Code:
+
(eval-when-compile (require 'cl))
(require 'widget)
(require 'wid-edit) ; For `widget-forward'.
@@ -652,8 +654,12 @@ with `notmuch-hello-query-counts'."
(defvar notmuch-hello-mode-map
(let ((map (if (fboundp 'make-composed-keymap)
- ;; Inherit both widget-keymap and notmuch-common-keymap
- (make-composed-keymap widget-keymap)
+ ;; Inherit both widget-keymap and
+ ;; notmuch-common-keymap. We have to use
+ ;; make-sparse-keymap to force this to be a new
+ ;; keymap (so that when we modify map it does not
+ ;; modify widget-keymap).
+ (make-composed-keymap (list (make-sparse-keymap) widget-keymap))
;; Before Emacs 24, keymaps didn't support multiple
;; inheritance,, so just copy the widget keymap since
;; it's unlikely to change.
@@ -668,6 +674,31 @@ with `notmuch-hello-query-counts'."
(defun notmuch-hello-mode ()
"Major mode for convenient notmuch navigation. This is your entry portal into notmuch.
+Saved searches are \"bookmarks\" for arbitrary queries. Hit RET
+or click on a saved search to view matching threads. Edit saved
+searches with the `edit' button. Type `\\[notmuch-jump-search]'
+in any Notmuch screen for quick access to saved searches that
+have shortcut keys.
+
+Type new searches in the search box and hit RET to view matching
+threads. Hit RET in a recent search box to re-submit a previous
+search. Edit it first if you like. Save a recent search to saved
+searches with the `save' button.
+
+Hit `\\[notmuch-search]' or `\\[notmuch-tree]' in any Notmuch
+screen to search for messages and view matching threads or
+messages, respectively. Recent searches are available in the
+minibuffer history.
+
+Expand the all tags view with the `show' button (and collapse
+again with the `hide' button). Hit RET or click on a tag name to
+view matching threads.
+
+Hit `\\[notmuch-refresh-this-buffer]' to refresh the screen and
+`\\[notmuch-bury-or-kill-this-buffer]' to quit.
+
+The screen may be customized via `\\[customize]'.
+
Complete list of currently available key bindings:
\\{notmuch-hello-mode-map}"
@@ -903,20 +934,19 @@ following:
(defun notmuch-hello-insert-footer ()
"Insert the notmuch-hello footer."
(let ((start (point)))
- (widget-insert "Type a search query and hit RET to view matching threads.\n")
- (when notmuch-search-history
- (widget-insert "Hit RET to re-submit a previous search. Edit it first if you like.\n")
- (widget-insert "Save recent searches with the `save' button.\n"))
- (when notmuch-saved-searches
- (widget-insert "Edit saved searches with the `edit' button.\n"))
- (widget-insert "Hit RET or click on a saved search or tag name to view matching threads.\n")
- (widget-insert "`=' to refresh this screen. `s' to search messages. `q' to quit.\n")
+ (widget-insert "Hit `?' for context-sensitive help in any Notmuch screen.\n")
+ (widget-insert "Customize ")
+ (widget-create 'link
+ :notify (lambda (&rest ignore)
+ (customize-group 'notmuch))
+ :button-prefix "" :button-suffix ""
+ "Notmuch")
+ (widget-insert " or ")
(widget-create 'link
:notify (lambda (&rest ignore)
(customize-variable 'notmuch-hello-sections))
:button-prefix "" :button-suffix ""
- "Customize")
- (widget-insert " this page.")
+ "this page.")
(let ((fill-column (- (window-width) notmuch-hello-indent)))
(center-region start (point)))))
@@ -988,3 +1018,5 @@ following:
;;
(provide 'notmuch-hello)
+
+;;; notmuch-hello.el ends here
diff --git a/emacs/notmuch-jump.el b/emacs/notmuch-jump.el
index 506ae2c8..fd770f1e 100644
--- a/emacs/notmuch-jump.el
+++ b/emacs/notmuch-jump.el
@@ -1,4 +1,4 @@
-;; notmuch-jump.el --- User-friendly shortcut keys
+;;; notmuch-jump.el --- User-friendly shortcut keys
;;
;; Copyright © Austin Clements
;;
@@ -20,6 +20,8 @@
;; Authors: Austin Clements <aclements@csail.mit.edu>
;; David Edmondson <dme@dme.org>
+;;; Code:
+
(eval-when-compile (require 'cl))
(require 'notmuch-lib)
@@ -176,3 +178,5 @@ buffer."
;;
(provide 'notmuch-jump)
+
+;;; notmuch-jump.el ends here
diff --git a/emacs/notmuch-lib.el b/emacs/notmuch-lib.el
index 201d7ec8..78978ee3 100644
--- a/emacs/notmuch-lib.el
+++ b/emacs/notmuch-lib.el
@@ -1,4 +1,4 @@
-;; notmuch-lib.el --- common variables, functions and function declarations
+;;; notmuch-lib.el --- common variables, functions and function declarations
;;
;; Copyright © Carl Worth
;;
@@ -21,6 +21,8 @@
;; This is an part of an emacs-based interface to the notmuch mail system.
+;;; Code:
+
(require 'mm-view)
(require 'mm-decode)
(require 'cl)
@@ -232,6 +234,9 @@ on the command line, and then retry your notmuch command")))
"Return the user.other_email value (as a list) from the notmuch configuration."
(split-string (notmuch-config-get "user.other_email") "\n" t))
+(defun notmuch-user-emails ()
+ (cons (notmuch-user-primary-email) (notmuch-user-other-email)))
+
(defun notmuch-poll ()
"Run \"notmuch new\" or an external script to import mail.
@@ -240,8 +245,9 @@ depending on the value of `notmuch-poll-script'."
(interactive)
(if (stringp notmuch-poll-script)
(unless (string= notmuch-poll-script "")
- (call-process notmuch-poll-script nil nil))
- (call-process notmuch-command nil nil nil "new")))
+ (unless (equal (call-process notmuch-poll-script nil nil) 0)
+ (error "Notmuch: poll script `%s' failed!" notmuch-poll-script)))
+ (notmuch-call-notmuch-process "new")))
(defun notmuch-bury-or-kill-this-buffer ()
"Undisplay the current buffer.
@@ -516,11 +522,23 @@ This replaces spaces, percents, and double quotes in STR with
"multipart/related"
))
-(defun notmuch-multipart/alternative-choose (types)
- "Return a list of preferred types from the given list of types"
+(defun notmuch-multipart/alternative-determine-discouraged (msg)
+ "Return the discouraged alternatives for the specified message."
+ ;; If a function, return the result of calling it.
+ (if (functionp notmuch-multipart/alternative-discouraged)
+ (funcall notmuch-multipart/alternative-discouraged msg)
+ ;; Otherwise simply return the value of the variable, which is
+ ;; assumed to be a list of discouraged alternatives. This is the
+ ;; default behaviour.
+ notmuch-multipart/alternative-discouraged))
+
+(defun notmuch-multipart/alternative-choose (msg types)
+ "Return a list of preferred types from the given list of types
+for this message, if present."
;; Based on `mm-preferred-alternative-precedence'.
- (let ((seq types))
- (dolist (pref (reverse notmuch-multipart/alternative-discouraged))
+ (let ((discouraged (notmuch-multipart/alternative-determine-discouraged msg))
+ (seq types))
+ (dolist (pref (reverse discouraged))
(dolist (elem (copy-sequence seq))
(when (string-match pref elem)
(setq seq (nconc (delete elem seq) (list elem))))))
@@ -533,6 +551,34 @@ the given type."
(lambda (part) (notmuch-match-content-type (plist-get part :content-type) type))
parts))
+(defun notmuch--get-bodypart-raw (msg part process-crypto binaryp cache)
+ (let* ((plist-elem (if binaryp :content-binary :content))
+ (data (or (plist-get part plist-elem)
+ (with-temp-buffer
+ ;; Emacs internally uses a UTF-8-like multibyte string
+ ;; representation by default (regardless of the coding
+ ;; system, which only affects how it goes from outside data
+ ;; to this internal representation). This *almost* never
+ ;; matters. Annoyingly, it does matter if we use this data
+ ;; in an image descriptor, since Emacs will use its internal
+ ;; data buffer directly and this multibyte representation
+ ;; corrupts binary image formats. Since the caller is
+ ;; asking for binary data, a unibyte string is a more
+ ;; appropriate representation anyway.
+ (when binaryp
+ (set-buffer-multibyte nil))
+ (let ((args `("show" "--format=raw"
+ ,(format "--part=%s" (plist-get part :id))
+ ,@(when process-crypto '("--decrypt"))
+ ,(notmuch-id-to-query (plist-get msg :id))))
+ (coding-system-for-read
+ (if binaryp 'no-conversion 'utf-8)))
+ (apply #'call-process notmuch-command nil '(t nil) nil args)
+ (buffer-string))))))
+ (when (and cache data)
+ (plist-put part plist-elem data))
+ data))
+
(defun notmuch-get-bodypart-binary (msg part process-crypto &optional cache)
"Return the unprocessed content of PART in MSG as a unibyte string.
@@ -543,57 +589,18 @@ this does no charset conversion.
If CACHE is non-nil, the content of this part will be saved in
MSG (if it isn't already)."
- (let ((data (plist-get part :binary-content)))
- (when (not data)
- (let ((args `("show" "--format=raw"
- ,(format "--part=%d" (plist-get part :id))
- ,@(when process-crypto '("--decrypt"))
- ,(notmuch-id-to-query (plist-get msg :id)))))
- (with-temp-buffer
- ;; Emacs internally uses a UTF-8-like multibyte string
- ;; representation by default (regardless of the coding
- ;; system, which only affects how it goes from outside data
- ;; to this internal representation). This *almost* never
- ;; matters. Annoyingly, it does matter if we use this data
- ;; in an image descriptor, since Emacs will use its internal
- ;; data buffer directly and this multibyte representation
- ;; corrupts binary image formats. Since the caller is
- ;; asking for binary data, a unibyte string is a more
- ;; appropriate representation anyway.
- (set-buffer-multibyte nil)
- (let ((coding-system-for-read 'no-conversion))
- (apply #'call-process notmuch-command nil '(t nil) nil args)
- (setq data (buffer-string)))))
- (when cache
- ;; Cheat. part is non-nil, and `plist-put' always modifies
- ;; the list in place if it's non-nil.
- (plist-put part :binary-content data)))
- data))
+ (notmuch--get-bodypart-raw msg part process-crypto t cache))
(defun notmuch-get-bodypart-text (msg part process-crypto &optional cache)
"Return the text content of PART in MSG.
This returns the content of the given part as a multibyte Lisp
string after performing content transfer decoding and any
-necessary charset decoding. It is an error to use this for
-non-text/* parts.
+necessary charset decoding.
If CACHE is non-nil, the content of this part will be saved in
MSG (if it isn't already)."
- (let ((content (plist-get part :content)))
- (when (not content)
- ;; Use show --format=sexp to fetch decoded content
- (let* ((args `("show" "--format=sexp" "--include-html"
- ,(format "--part=%s" (plist-get part :id))
- ,@(when process-crypto '("--decrypt"))
- ,(notmuch-id-to-query (plist-get msg :id))))
- (npart (apply #'notmuch-call-notmuch-sexp args)))
- (setq content (plist-get npart :content))
- (when (not content)
- (error "Internal error: No :content from %S" args)))
- (when cache
- (plist-put part :content content)))
- content))
+ (notmuch--get-bodypart-raw msg part process-crypto nil cache))
;; Workaround: The call to `mm-display-part' below triggers a bug in
;; Emacs 24 if it attempts to use the shr renderer to display an HTML
@@ -926,3 +933,5 @@ status."
;; Local Variables:
;; byte-compile-warnings: (not cl-functions)
;; End:
+
+;;; notmuch-lib.el ends here
diff --git a/emacs/notmuch-maildir-fcc.el b/emacs/notmuch-maildir-fcc.el
index c2f2f4cb..bbf61320 100644
--- a/emacs/notmuch-maildir-fcc.el
+++ b/emacs/notmuch-maildir-fcc.el
@@ -1,3 +1,5 @@
+;;; notmuch-maildir-fcc.el ---
+
;; This file 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 2, or (at your
@@ -12,10 +14,14 @@
;; along with GNU Emacs; see the file COPYING. If not, write to the
;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
;; Boston, MA 02110-1301, USA.
-;;
+
+;;; Commentary:
+
;; To use this as the fcc handler for message-mode,
;; customize the notmuch-fcc-dirs variable
+;;; Code:
+
(eval-when-compile (require 'cl))
(require 'message)
@@ -211,3 +217,4 @@ return t if successful, and nil otherwise."
(provide 'notmuch-maildir-fcc)
+;;; notmuch-maildir-fcc.el ends here
diff --git a/emacs/notmuch-message.el b/emacs/notmuch-message.el
index 914bdd18..d437b857 100644
--- a/emacs/notmuch-message.el
+++ b/emacs/notmuch-message.el
@@ -1,4 +1,4 @@
-;; notmuch-message.el --- message-mode functions specific to notmuch
+;;; notmuch-message.el --- message-mode functions specific to notmuch
;;
;; Copyright © Jesse Rosenthal
;;
@@ -19,6 +19,8 @@
;;
;; Authors: Jesse Rosenthal <jrosenthal@jhu.edu>
+;;; Code:
+
(require 'message)
(require 'notmuch-tag)
(require 'notmuch-mua)
@@ -46,3 +48,5 @@ the \"inbox\" and \"todo\" tags, you would set:
(add-hook 'message-send-hook 'notmuch-message-mark-replied)
(provide 'notmuch-message)
+
+;;; notmuch-message.el ends here
diff --git a/emacs/notmuch-mua.el b/emacs/notmuch-mua.el
index 57465b20..04459750 100644
--- a/emacs/notmuch-mua.el
+++ b/emacs/notmuch-mua.el
@@ -1,4 +1,4 @@
-;; notmuch-mua.el --- emacs style mail-user-agent
+;;; notmuch-mua.el --- emacs style mail-user-agent
;;
;; Copyright © David Edmondson
;;
@@ -19,6 +19,8 @@
;;
;; Authors: David Edmondson <dme@dme.org>
+;;; Code:
+
(require 'message)
(require 'mm-view)
(require 'format-spec)
@@ -28,7 +30,9 @@
(eval-when-compile (require 'cl))
-(declare-function notmuch-show-insert-bodypart "notmuch-show" (msg part depth &optional hide))
+(declare-function notmuch-show-insert-body "notmuch-show" (msg body depth))
+(declare-function notmuch-fcc-header-setup "notmuch-maildir-fcc" ())
+(declare-function notmuch-fcc-handler "notmuch-maildir-fcc" (destdir))
;;
@@ -91,6 +95,23 @@ Note that these functions use `mail-citation-hook' if that is non-nil."
:link '(custom-manual "(message)Insertion Variables")
:group 'notmuch-reply)
+(defcustom notmuch-mua-reply-insert-header-p-function
+ 'notmuch-show-reply-insert-header-p-never
+ "Function to decide which parts get a header when replying.
+
+This function specifies which parts of a mime message with
+mutiple parts get a header."
+ :type '(radio (const :tag "No part headers"
+ notmuch-show-reply-insert-header-p-never)
+ (const :tag "All except multipart/* and hidden parts"
+ notmuch-show-reply-insert-header-p-trimmed)
+ (const :tag "Only for included text parts"
+ notmuch-show-reply-insert-header-p-minimal)
+ (const :tag "Exactly as in show view"
+ notmuch-show-insert-header-p)
+ (function :tag "Other"))
+ :group 'notmuch-reply)
+
;;
(defun notmuch-mua-get-switch-function ()
@@ -142,31 +163,6 @@ Note that these functions use `mail-citation-hook' if that is non-nil."
else if (notmuch-match-content-type (plist-get part :content-type) "multipart/*")
do (notmuch-mua-reply-crypto (plist-get part :content))))
-(defun notmuch-mua-get-quotable-parts (parts)
- (loop for part in parts
- if (notmuch-match-content-type (plist-get part :content-type) "multipart/alternative")
- collect (let* ((subparts (plist-get part :content))
- (types (mapcar (lambda (part) (plist-get part :content-type)) subparts))
- (chosen-type (car (notmuch-multipart/alternative-choose types))))
- (loop for part in (reverse subparts)
- if (notmuch-match-content-type (plist-get part :content-type) chosen-type)
- return part))
- else if (notmuch-match-content-type (plist-get part :content-type) "multipart/*")
- append (notmuch-mua-get-quotable-parts (plist-get part :content))
- else if (notmuch-match-content-type (plist-get part :content-type) "text/*")
- collect part))
-
-(defun notmuch-mua-insert-quotable-part (message part)
- ;; We don't want text properties leaking from the show renderer into
- ;; the reply so we use a temp buffer. Also we don't want hooks, such
- ;; as notmuch-wash-*, to be run on the quotable part so we set
- ;; notmuch-show-insert-text/plain-hook to nil.
- (insert (with-temp-buffer
- (let ((notmuch-show-insert-text/plain-hook nil))
- ;; Show the part but do not add buttons.
- (notmuch-show-insert-bodypart message part 0 'no-buttons))
- (buffer-substring-no-properties (point-min) (point-max)))))
-
;; There is a bug in emacs 23's message.el that results in a newline
;; not being inserted after the References header, so the next header
;; is concatenated to the end of it. This function fixes the problem,
@@ -245,10 +241,20 @@ Note that these functions use `mail-citation-hook' if that is non-nil."
(insert "From: " from "\n")
(insert "Date: " date "\n\n")
- ;; Get the parts of the original message that should be quoted; this includes
- ;; all the text parts, except the non-preferred ones in a multipart/alternative.
- (let ((quotable-parts (notmuch-mua-get-quotable-parts (plist-get original :body))))
- (mapc (apply-partially 'notmuch-mua-insert-quotable-part original) quotable-parts))
+ (insert (with-temp-buffer
+ (let
+ ;; Don't attempt to clean up messages, excerpt
+ ;; citations, etc. in the original message before
+ ;; quoting.
+ ((notmuch-show-insert-text/plain-hook nil)
+ ;; Don't omit long parts.
+ (notmuch-show-max-text-part-size 0)
+ ;; Insert headers for parts as appropriate for replying.
+ (notmuch-show-insert-header-p-function notmuch-mua-reply-insert-header-p-function)
+ ;; Don't indent multipart sub-parts.
+ (notmuch-show-indent-multipart nil))
+ (notmuch-show-insert-body original (plist-get original :body) 0)
+ (buffer-substring-no-properties (point-min) (point-max)))))
(set-mark (point))
(goto-char start)
@@ -269,15 +275,45 @@ Note that these functions use `mail-citation-hook' if that is non-nil."
(set-buffer-modified-p nil))
(define-derived-mode notmuch-message-mode message-mode "Message[Notmuch]"
- "Notmuch message composition mode. Mostly like `message-mode'")
+ "Notmuch message composition mode. Mostly like `message-mode'"
+ (when notmuch-address-command
+ (notmuch-address-setup)))
+
+(put 'notmuch-message-mode 'flyspell-mode-predicate 'mail-mode-flyspell-verify)
(define-key notmuch-message-mode-map (kbd "C-c C-c") #'notmuch-mua-send-and-exit)
(define-key notmuch-message-mode-map (kbd "C-c C-s") #'notmuch-mua-send)
-(defun notmuch-mua-mail (&optional to subject other-headers &rest other-args)
- "Invoke the notmuch mail composition window.
+(defun notmuch-mua-pop-to-buffer (name switch-function)
+ "Pop to buffer NAME, and warn if it already exists and is
+modified. This function is notmuch addaptation of
+`message-pop-to-buffer'."
+ (let ((buffer (get-buffer name)))
+ (if (and buffer
+ (buffer-name buffer))
+ (let ((window (get-buffer-window buffer 0)))
+ (if window
+ ;; Raise the frame already displaying the message buffer.
+ (progn
+ (gnus-select-frame-set-input-focus (window-frame window))
+ (select-window window))
+ (funcall switch-function buffer)
+ (set-buffer buffer))
+ (when (and (buffer-modified-p)
+ (not (prog1
+ (y-or-n-p
+ "Message already being composed; erase? ")
+ (message nil))))
+ (error "Message being composed")))
+ (funcall switch-function name)
+ (set-buffer name))
+ (erase-buffer)
+ (notmuch-message-mode)))
-OTHER-ARGS are passed through to `message-mail'."
+(defun notmuch-mua-mail (&optional to subject other-headers continue
+ switch-function yank-action send-actions
+ return-action &rest ignored)
+ "Invoke the notmuch mail composition window."
(interactive)
(when notmuch-mua-user-agent-function
@@ -286,11 +322,29 @@ OTHER-ARGS are passed through to `message-mail'."
(push (cons 'User-Agent user-agent) other-headers))))
(unless (assq 'From other-headers)
- (push (cons 'From (concat
- (notmuch-user-name) " <" (notmuch-user-primary-email) ">")) other-headers))
+ (push (cons 'From (message-make-from
+ (notmuch-user-name) (notmuch-user-primary-email))) other-headers))
- (apply #'message-mail to subject other-headers other-args)
- (notmuch-message-mode)
+ (notmuch-mua-pop-to-buffer (message-buffer-name "mail" to)
+ (or switch-function (notmuch-mua-get-switch-function)))
+ (let ((headers
+ (append
+ ;; The following is copied from `message-mail'
+ `((To . ,(or to "")) (Subject . ,(or subject "")))
+ ;; C-h f compose-mail says that headers should be specified as
+ ;; (string . value); however all the rest of message expects
+ ;; headers to be symbols, not strings (eg message-header-format-alist).
+ ;; http://lists.gnu.org/archive/html/emacs-devel/2011-01/msg00337.html
+ ;; We need to convert any string input, eg from rmail-start-mail.
+ (dolist (h other-headers other-headers)
+ (if (stringp (car h)) (setcar h (intern (capitalize (car h))))))))
+ (args (list yank-action send-actions)))
+ ;; message-setup-1 in Emacs 23 does not accept return-action
+ ;; argument. Pass it only if it is supplied by the caller. This
+ ;; will never be the case when we're called by `compose-mail' in
+ ;; Emacs 23.
+ (when return-action (nconc args '(return-action)))
+ (apply 'message-setup-1 headers args))
(notmuch-fcc-header-setup)
(message-sort-headers)
(message-hide-headers)
@@ -343,7 +397,7 @@ the From: header is already filled in by notmuch."
(ido-completing-read (concat "Sender address for " name ": ") addrs
nil nil nil 'notmuch-mua-sender-history
(car addrs))))
- (concat name " <" address ">"))))
+ (message-make-from name address))))
(put 'notmuch-mua-new-mail 'notmuch-prefix-doc "... and prompt for sender")
(defun notmuch-mua-new-mail (&optional prompt-for-sender)
@@ -357,25 +411,53 @@ the From: address first."
(list (cons 'From (notmuch-mua-prompt-for-sender))))))
(notmuch-mua-mail nil nil other-headers nil (notmuch-mua-get-switch-function))))
-(defun notmuch-mua-new-forward-message (&optional prompt-for-sender)
- "Invoke the notmuch message forwarding window.
+(defun notmuch-mua-new-forward-messages (messages &optional prompt-for-sender)
+ "Compose a new message forwarding MESSAGES.
-The current buffer must contain an RFC2822 message to forward.
-
-If PROMPT-FOR-SENDER is non-nil, the user will be prompted for
-the From: address first."
- (let* ((cur (current-buffer))
- (message-forward-decoded-p nil)
- (subject (message-make-forward-subject))
- (other-headers
+If PROMPT-FOR-SENDER is non-nil, the user will be prompteed for
+the From: address."
+ (let* ((other-headers
(when (or prompt-for-sender notmuch-always-prompt-for-sender)
- (list (cons 'From (notmuch-mua-prompt-for-sender))))))
- (notmuch-mua-mail nil subject other-headers nil (notmuch-mua-get-switch-function))
- (message-forward-make-body cur)
- ;; `message-forward-make-body' shows the User-agent header. Hide
- ;; it again.
- (message-hide-headers)
- (set-buffer-modified-p nil)))
+ (list (cons 'From (notmuch-mua-prompt-for-sender)))))
+ forward-subject) ;; Comes from the first message and is
+ ;; applied later.
+
+ ;; Generate the template for the outgoing message.
+ (notmuch-mua-mail nil "" other-headers nil (notmuch-mua-get-switch-function))
+
+ (save-excursion
+ ;; Insert all of the forwarded messages.
+ (mapc (lambda (id)
+ (let ((temp-buffer (get-buffer-create
+ (concat "*notmuch-fwd-raw-" id "*"))))
+ ;; Get the raw version of this message in the buffer.
+ (with-current-buffer temp-buffer
+ (erase-buffer)
+ (let ((coding-system-for-read 'no-conversion))
+ (call-process notmuch-command nil t nil "show" "--format=raw" id))
+ ;; Because we process the messages in reverse order,
+ ;; always generate a forwarded subject, then use the
+ ;; last (i.e. first) one.
+ (setq forward-subject (message-make-forward-subject)))
+ ;; Make a copy ready to be forwarded in the
+ ;; composition buffer.
+ (message-forward-make-body temp-buffer)
+ ;; Kill the temporary buffer.
+ (kill-buffer temp-buffer)))
+ ;; `message-forward-make-body' always puts the message at
+ ;; the top, so do them in reverse order.
+ (reverse messages))
+
+ ;; Add in the appropriate subject.
+ (save-restriction
+ (message-narrow-to-headers)
+ (message-remove-header "Subject")
+ (message-add-header (concat "Subject: " forward-subject)))
+
+ ;; `message-forward-make-body' shows the User-agent header. Hide
+ ;; it again.
+ (message-hide-headers)
+ (set-buffer-modified-p nil))))
(defun notmuch-mua-new-reply (query-string &optional prompt-for-sender reply-all)
"Compose a reply to the message identified by QUERY-STRING.
@@ -435,3 +517,5 @@ simply runs the corresponding `message-mode' hook functions."
;;
(provide 'notmuch-mua)
+
+;;; notmuch-mua.el ends here
diff --git a/emacs/notmuch-parser.el b/emacs/notmuch-parser.el
index d59c0e1c..620ca89d 100644
--- a/emacs/notmuch-parser.el
+++ b/emacs/notmuch-parser.el
@@ -1,4 +1,4 @@
-;; notmuch-parser.el --- streaming S-expression parser
+;;; notmuch-parser.el --- streaming S-expression parser
;;
;; Copyright © Austin Clements
;;
@@ -19,6 +19,8 @@
;;
;; Authors: Austin Clements <aclements@csail.mit.edu>
+;;; Code:
+
(require 'cl)
(defun notmuch-sexp-create-parser ()
@@ -205,3 +207,5 @@ move point in the input buffer."
;; Local Variables:
;; byte-compile-warnings: (not cl-functions)
;; End:
+
+;;; notmuch-parser.el ends here
diff --git a/emacs/notmuch-print.el b/emacs/notmuch-print.el
index 8c18f4bd..480a0cfe 100644
--- a/emacs/notmuch-print.el
+++ b/emacs/notmuch-print.el
@@ -1,4 +1,4 @@
-;; notmuch-print.el --- printing messages from notmuch.
+;;; notmuch-print.el --- printing messages from notmuch.
;;
;; Copyright © David Edmondson
;;
@@ -19,6 +19,8 @@
;;
;; Authors: David Edmondson <dme@dme.org>
+;;; Code:
+
(require 'notmuch-lib)
(declare-function notmuch-show-get-prop "notmuch-show" (prop &optional props))
@@ -90,3 +92,5 @@ Optional OUTPUT allows passing a list of flags to muttprint."
(funcall notmuch-print-mechanism msg))
(provide 'notmuch-print)
+
+;;; notmuch-print.el ends here
diff --git a/emacs/notmuch-query.el b/emacs/notmuch-query.el
index d1daffce..8587d881 100644
--- a/emacs/notmuch-query.el
+++ b/emacs/notmuch-query.el
@@ -1,4 +1,4 @@
-;; notmuch-query.el --- provide an emacs api to query notmuch
+;;; notmuch-query.el --- provide an emacs api to query notmuch
;;
;; Copyright © David Bremner
;;
@@ -19,6 +19,8 @@
;;
;; Authors: David Bremner <david@tethera.net>
+;;; Code:
+
(require 'notmuch-lib)
(defun notmuch-query-get-threads (search-terms)
@@ -74,3 +76,5 @@ See the function notmuch-query-get-threads for more information."
(notmuch-query-get-threads search-terms)))
(provide 'notmuch-query)
+
+;;; notmuch-query.el ends here
diff --git a/emacs/notmuch-show.el b/emacs/notmuch-show.el
index 49fd198a..5d9b7b45 100644
--- a/emacs/notmuch-show.el
+++ b/emacs/notmuch-show.el
@@ -1,4 +1,4 @@
-;; notmuch-show.el --- displaying notmuch forests.
+;;; notmuch-show.el --- displaying notmuch forests.
;;
;; Copyright © Carl Worth
;; Copyright © David Edmondson
@@ -21,6 +21,8 @@
;; Authors: Carl Worth <cworth@cworth.org>
;; David Edmondson <dme@dme.org>
+;;; Code:
+
(eval-when-compile (require 'cl))
(require 'mm-view)
(require 'message)
@@ -153,27 +155,21 @@ indentation."
(defvar notmuch-show-thread-id nil)
(make-variable-buffer-local 'notmuch-show-thread-id)
-(put 'notmuch-show-thread-id 'permanent-local t)
(defvar notmuch-show-parent-buffer nil)
(make-variable-buffer-local 'notmuch-show-parent-buffer)
-(put 'notmuch-show-parent-buffer 'permanent-local t)
(defvar notmuch-show-query-context nil)
(make-variable-buffer-local 'notmuch-show-query-context)
-(put 'notmuch-show-query-context 'permanent-local t)
(defvar notmuch-show-process-crypto nil)
(make-variable-buffer-local 'notmuch-show-process-crypto)
-(put 'notmuch-show-process-crypto 'permanent-local t)
(defvar notmuch-show-elide-non-matching-messages nil)
(make-variable-buffer-local 'notmuch-show-elide-non-matching-messages)
-(put 'notmuch-show-elide-non-matching-messages 'permanent-local t)
(defvar notmuch-show-indent-content t)
(make-variable-buffer-local 'notmuch-show-indent-content)
-(put 'notmuch-show-indent-content 'permanent-local t)
(defvar notmuch-show-attachment-debug nil
"If t log stdout and stderr from attachment handlers
@@ -353,8 +349,6 @@ operation on the contents of the current buffer."
'message-header-cc)
((looking-at "[Ss]ubject:")
'message-header-subject)
- ((looking-at "[Ff]rom:")
- 'message-header-from)
(t
'message-header-other))))
@@ -509,36 +503,37 @@ message at DEPTH in the current thread."
(defun notmuch-show-toggle-part-invisibility (&optional button)
(interactive)
- (let* ((button (or button (button-at (point))))
- (overlay (button-get button 'overlay))
- (lazy-part (button-get button :notmuch-lazy-part)))
- ;; We have a part to toggle if there is an overlay or if there is a lazy part.
- ;; If neither is present we cannot toggle the part so we just return nil.
- (when (or overlay lazy-part)
- (let* ((show (button-get button :notmuch-part-hidden))
- (new-start (button-start button))
- (button-label (button-get button :base-label))
- (old-point (point))
- (properties (text-properties-at (button-start button)))
- (inhibit-read-only t))
- ;; Toggle the button itself.
- (button-put button :notmuch-part-hidden (not show))
- (goto-char new-start)
- (insert "[ " button-label (if show " ]" " (hidden) ]"))
- (set-text-properties new-start (point) properties)
- (let ((old-end (button-end button)))
- (move-overlay button new-start (point))
- (delete-region (point) old-end))
- (goto-char (min old-point (1- (button-end button))))
- ;; Return nil if there is a lazy-part, it is empty, and we are
- ;; trying to show it. In all other cases return t.
- (if lazy-part
- (when show
- (button-put button :notmuch-lazy-part nil)
- (notmuch-show-lazy-part lazy-part button))
- ;; else there must be an overlay.
- (overlay-put overlay 'invisible (not show))
- t)))))
+ (let ((button (or button (button-at (point)))))
+ (when button
+ (let ((overlay (button-get button 'overlay))
+ (lazy-part (button-get button :notmuch-lazy-part)))
+ ;; We have a part to toggle if there is an overlay or if there is a lazy part.
+ ;; If neither is present we cannot toggle the part so we just return nil.
+ (when (or overlay lazy-part)
+ (let* ((show (button-get button :notmuch-part-hidden))
+ (new-start (button-start button))
+ (button-label (button-get button :base-label))
+ (old-point (point))
+ (properties (text-properties-at (button-start button)))
+ (inhibit-read-only t))
+ ;; Toggle the button itself.
+ (button-put button :notmuch-part-hidden (not show))
+ (goto-char new-start)
+ (insert "[ " button-label (if show " ]" " (hidden) ]"))
+ (set-text-properties new-start (point) properties)
+ (let ((old-end (button-end button)))
+ (move-overlay button new-start (point))
+ (delete-region (point) old-end))
+ (goto-char (min old-point (1- (button-end button))))
+ ;; Return nil if there is a lazy-part, it is empty, and we are
+ ;; trying to show it. In all other cases return t.
+ (if lazy-part
+ (when show
+ (button-put button :notmuch-lazy-part nil)
+ (notmuch-show-lazy-part lazy-part button))
+ ;; else there must be an overlay.
+ (overlay-put overlay 'invisible (not show))
+ t)))))))
;; Part content ID handling
@@ -614,7 +609,7 @@ will return nil if the CID is unknown or cannot be retrieved."
(plist-get part :content)))
(defun notmuch-show-insert-part-multipart/alternative (msg part content-type nth depth button)
- (let ((chosen-type (car (notmuch-multipart/alternative-choose (notmuch-show-multipart/*-to-list part))))
+ (let ((chosen-type (car (notmuch-multipart/alternative-choose msg (notmuch-show-multipart/*-to-list part))))
(inner-parts (plist-get part :content))
(start (point)))
;; This inserts all parts of the chosen type rather than just one,
@@ -647,14 +642,12 @@ will return nil if the CID is unknown or cannot be retrieved."
t)
(defun notmuch-show-insert-part-multipart/signed (msg part content-type nth depth button)
- (button-put button 'face 'notmuch-crypto-part-header)
- ;; add signature status button if sigstatus provided
- (if (plist-member part :sigstatus)
- (let* ((from (notmuch-show-get-header :From msg))
- (sigstatus (car (plist-get part :sigstatus))))
- (notmuch-crypto-insert-sigstatus-button sigstatus from))
- ;; if we're not adding sigstatus, tell the user how they can get it
- (button-put button 'help-echo "Set notmuch-crypto-process-mime to process cryptographic MIME parts."))
+ (when button
+ (button-put button 'face 'notmuch-crypto-part-header))
+
+ ;; Insert a button detailing the signature status.
+ (notmuch-crypto-insert-sigstatus-button (car (plist-get part :sigstatus))
+ (notmuch-show-get-header :From msg))
(let ((inner-parts (plist-get part :content))
(start (point)))
@@ -668,18 +661,15 @@ will return nil if the CID is unknown or cannot be retrieved."
t)
(defun notmuch-show-insert-part-multipart/encrypted (msg part content-type nth depth button)
- (button-put button 'face 'notmuch-crypto-part-header)
- ;; add encryption status button if encstatus specified
- (if (plist-member part :encstatus)
- (let ((encstatus (car (plist-get part :encstatus))))
- (notmuch-crypto-insert-encstatus-button encstatus)
- ;; add signature status button if sigstatus specified
- (if (plist-member part :sigstatus)
- (let* ((from (notmuch-show-get-header :From msg))
- (sigstatus (car (plist-get part :sigstatus))))
- (notmuch-crypto-insert-sigstatus-button sigstatus from))))
- ;; if we're not adding encstatus, tell the user how they can get it
- (button-put button 'help-echo "Set notmuch-crypto-process-mime to process cryptographic MIME parts."))
+ (when button
+ (button-put button 'face 'notmuch-crypto-part-header))
+
+ ;; Insert a button detailing the encryption status.
+ (notmuch-crypto-insert-encstatus-button (car (plist-get part :encstatus)))
+
+ ;; Insert a button detailing the signature status.
+ (notmuch-crypto-insert-sigstatus-button (car (plist-get part :sigstatus))
+ (notmuch-show-get-header :From msg))
(let ((inner-parts (plist-get part :content))
(start (point)))
@@ -848,21 +838,16 @@ will return nil if the CID is unknown or cannot be retrieved."
;;
(defun notmuch-show-insert-bodypart-internal (msg part content-type nth depth button)
- (let ((handlers (notmuch-show-handlers-for content-type)))
- ;; Run the content handlers until one of them returns a non-nil
- ;; value.
- (while (and handlers
- (not (condition-case err
- (funcall (car handlers) msg part content-type nth depth button)
- ;; Specifying `debug' here lets the debugger
- ;; run if `debug-on-error' is non-nil.
- ((debug error)
- (progn
- (insert "!!! Bodypart insert error: ")
- (insert (error-message-string err))
- (insert " !!!\n") nil)))))
- (setq handlers (cdr handlers))))
- t)
+ ;; Run the handlers until one of them succeeds.
+ (loop for handler in (notmuch-show-handlers-for content-type)
+ until (condition-case err
+ (funcall handler msg part content-type nth depth button)
+ ;; Specifying `debug' here lets the debugger run if
+ ;; `debug-on-error' is non-nil.
+ ((debug error)
+ (insert "!!! Bodypart handler `" (prin1-to-string handler) "' threw an error:\n"
+ "!!! " (error-message-string err) "\n")
+ nil))))
(defun notmuch-show-create-part-overlays (button beg end)
"Add an overlay to the part between BEG and END"
@@ -929,34 +914,62 @@ will return nil if the CID is unknown or cannot be retrieved."
;; showable this returns nil.
(notmuch-show-create-part-overlays button part-beg part-end))))
+(defun notmuch-show-mime-type (part)
+ "Return the correct mime-type to use for PART."
+ (let ((content-type (downcase (plist-get part :content-type))))
+ (or (and (string= content-type "application/octet-stream")
+ (notmuch-show-get-mime-type-of-application/octet-stream part))
+ (and (string= content-type "inline patch")
+ "text/x-diff")
+ content-type)))
+
+;; The following variable can be overridden by let bindings.
+(defvar notmuch-show-insert-header-p-function 'notmuch-show-insert-header-p
+ "Specify which function decides which part headers get inserted.
+
+The function should take two parameters, PART and HIDE, and
+should return non-NIL if a header button should be inserted for
+this part.")
+
+(defun notmuch-show-insert-header-p (part hide)
+ ;; Show all part buttons except for the first part if it is text/plain.
+ (let ((mime-type (notmuch-show-mime-type part)))
+ (not (and (string= mime-type "text/plain")
+ (<= (plist-get part :id) 1)))))
+
+(defun notmuch-show-reply-insert-header-p-never (part hide)
+ nil)
+
+(defun notmuch-show-reply-insert-header-p-trimmed (part hide)
+ (let ((mime-type (notmuch-show-mime-type part)))
+ (and (not (notmuch-match-content-type mime-type "multipart/*"))
+ (not hide))))
+
+(defun notmuch-show-reply-insert-header-p-minimal (part hide)
+ (let ((mime-type (notmuch-show-mime-type part)))
+ (and (notmuch-match-content-type mime-type "text/*")
+ (not hide))))
+
(defun notmuch-show-insert-bodypart (msg part depth &optional hide)
"Insert the body part PART at depth DEPTH in the current thread.
HIDE determines whether to show or hide the part and the button
as follows: If HIDE is nil, show the part and the button. If HIDE
-is t, hide the part initially and show the button. If HIDE is
-'no-buttons, show the part but do not add any buttons (this is
-useful for quoting in replies)."
+is t, hide the part initially and show the button."
(let* ((content-type (downcase (plist-get part :content-type)))
- (mime-type (or (and (string= content-type "application/octet-stream")
- (notmuch-show-get-mime-type-of-application/octet-stream part))
- (and (string= content-type "inline patch")
- "text/x-diff")
- content-type))
+ (mime-type (notmuch-show-mime-type part))
(nth (plist-get part :id))
(long (and (notmuch-match-content-type mime-type "text/*")
(> notmuch-show-max-text-part-size 0)
(> (length (plist-get part :content)) notmuch-show-max-text-part-size)))
(beg (point))
- ;; We omit the part button for the first (or only) part if
- ;; this is text/plain, or HIDE is 'no-buttons.
- (button (unless (or (equal hide 'no-buttons)
- (and (string= mime-type "text/plain") (<= nth 1)))
+ ;; This default header-p function omits the part button for
+ ;; the first (or only) part if this is text/plain.
+ (button (when (funcall notmuch-show-insert-header-p-function part hide)
(notmuch-show-insert-part-header nth mime-type content-type (plist-get part :filename))))
;; Hide the part initially if HIDE is t, or if it is too long
- ;; and we have a button to allow toggling (thus reply which
- ;; uses 'no-buttons automatically includes long parts)
+ ;; and we have a button to allow toggling.
(show-part (not (or (equal hide t)
(and long button))))
(content-beg (point)))
@@ -966,8 +979,9 @@ useful for quoting in replies)."
(if show-part
(notmuch-show-insert-bodypart-internal msg part mime-type nth depth button)
- (button-put button :notmuch-lazy-part
- (list msg part mime-type nth depth button)))
+ (when button
+ (button-put button :notmuch-lazy-part
+ (list msg part mime-type nth depth button))))
;; Some of the body part handlers leave point somewhere up in the
;; part, so we make sure that we're down at the end.
@@ -1199,71 +1213,101 @@ non-nil.
The optional BUFFER-NAME provides the name of the buffer in
which the message thread is shown. If it is nil (which occurs
when the command is called interactively) the argument to the
-function is used."
+function is used.
+
+Returns the buffer containing the messages, or NIL if no messages
+matched."
(interactive "sNotmuch show: \nP")
(let ((buffer-name (generate-new-buffer-name
(or buffer-name
(concat "*notmuch-" thread-id "*")))))
(switch-to-buffer (get-buffer-create buffer-name))
- ;; Set the default value for `notmuch-show-process-crypto' in this
- ;; buffer.
- (setq notmuch-show-process-crypto notmuch-crypto-process-mime)
- ;; Set the default value for
- ;; `notmuch-show-elide-non-matching-messages' in this buffer. If
- ;; elide-toggle is set, invert the default.
- (setq notmuch-show-elide-non-matching-messages notmuch-show-only-matching-messages)
- (if elide-toggle
- (setq notmuch-show-elide-non-matching-messages (not notmuch-show-elide-non-matching-messages)))
+ ;; No need to track undo information for this buffer.
+ (setq buffer-undo-list t)
+ (notmuch-show-mode)
+
+ ;; Set various buffer local variables to their appropriate initial
+ ;; state. Do this after enabling `notmuch-show-mode' so that they
+ ;; 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-build-buffer)
- (notmuch-show-goto-first-wanted-message)
- (current-buffer)))
+ notmuch-show-query-context query-context
-(defun notmuch-show-build-buffer ()
- (let ((inhibit-read-only t))
+ notmuch-show-process-crypto notmuch-crypto-process-mime
+ ;; If `elide-toggle', invert the default value.
+ notmuch-show-elide-non-matching-messages
+ (if elide-toggle
+ (not notmuch-show-only-matching-messages)
+ notmuch-show-only-matching-messages))
- (notmuch-show-mode)
(add-hook 'post-command-hook #'notmuch-show-command-hook nil t)
-
- ;; Don't track undo information for this buffer
- (set 'buffer-undo-list t)
+ (jit-lock-register #'notmuch-show-buttonise-links)
(notmuch-tag-clear-cache)
- (erase-buffer)
- (goto-char (point-min))
- (save-excursion
- (let* ((basic-args (list notmuch-show-thread-id))
- (args (if notmuch-show-query-context
- (append (list "\'") basic-args
- (list "and (" notmuch-show-query-context ")\'"))
- (append (list "\'") basic-args (list "\'"))))
- (cli-args (cons "--exclude=false"
- (when notmuch-show-elide-non-matching-messages
- (list "--entire-thread=false")))))
- (notmuch-show-insert-forest (notmuch-query-get-threads (append cli-args args)))
- ;; If the query context reduced the results to nothing, run
- ;; the basic query.
- (when (and (eq (buffer-size) 0)
- notmuch-show-query-context)
- (notmuch-show-insert-forest
- (notmuch-query-get-threads (append cli-args basic-args)))))
+ (let ((inhibit-read-only t))
+ (if (notmuch-show--build-buffer)
+ ;; Messages were inserted into the buffer.
+ (current-buffer)
+
+ ;; No messages were inserted - presumably none matched the
+ ;; query.
+ (kill-buffer (current-buffer))
+ (ding)
+ (message "No messages matched the query!")
+ nil))))
+
+(defun notmuch-show--build-buffer (&optional state)
+ "Display messages matching the current buffer context.
+
+Apply the previously saved STATE if supplied, otherwise show the
+first relevant message.
- (jit-lock-register #'notmuch-show-buttonise-links)
+If no messages match the query return NIL."
+ (let* ((basic-args (list notmuch-show-thread-id))
+ (args (if notmuch-show-query-context
+ (append (list "\'") basic-args
+ (list "and (" notmuch-show-query-context ")\'"))
+ (append (list "\'") basic-args (list "\'"))))
+ (cli-args (cons "--exclude=false"
+ (when notmuch-show-elide-non-matching-messages
+ (list "--entire-thread=false"))))
- (notmuch-show-mapc (lambda () (notmuch-show-set-prop :orig-tags (notmuch-show-get-tags))))
+ (forest (or (notmuch-query-get-threads (append cli-args args))
+ ;; If a query context reduced the number of
+ ;; results to zero, try again without it.
+ (and notmuch-show-query-context
+ (notmuch-query-get-threads (append cli-args basic-args)))))
+
+ ;; Must be reset every time we are going to start inserting
+ ;; messages into the buffer.
+ (notmuch-show-previous-subject ""))
+
+ (when forest
+ (notmuch-show-insert-forest forest)
+
+ ;; Store the original tags for each message so that we can
+ ;; display changes.
+ (notmuch-show-mapc
+ (lambda () (notmuch-show-set-prop :orig-tags (notmuch-show-get-tags))))
;; Set the header line to the subject of the first message.
(setq header-line-format
(replace-regexp-in-string "%" "%%"
- (notmuch-sanitize
- (notmuch-show-strip-re
- (notmuch-show-get-subject)))))
+ (notmuch-sanitize
+ (notmuch-show-strip-re
+ (notmuch-show-get-subject)))))
+
+ (run-hooks 'notmuch-show-hook)
+
+ (if state
+ (notmuch-show-apply-state state)
+ ;; With no state to apply, just go to the first message.
+ (notmuch-show-goto-first-wanted-message)))
- (run-hooks 'notmuch-show-hook))))
+ ;; Report back to the caller whether any messages matched.
+ forest))
(defun notmuch-show-capture-state ()
"Capture the state of the current buffer.
@@ -1322,17 +1366,17 @@ reset based on the original query."
(let ((inhibit-read-only t)
(state (unless reset-state
(notmuch-show-capture-state))))
- ;; erase-buffer does not seem to remove overlays, which can lead
+ ;; `erase-buffer' does not seem to remove overlays, which can lead
;; to weird effects such as remaining images, so remove them
;; manually.
(remove-overlays)
(erase-buffer)
- (notmuch-show-build-buffer)
- (if state
- (notmuch-show-apply-state state)
- ;; We're resetting state, so navigate to the first open message
- ;; and mark it read, just like opening a new show buffer.
- (notmuch-show-goto-first-wanted-message))))
+
+ (unless (notmuch-show--build-buffer state)
+ ;; No messages were inserted.
+ (kill-buffer (current-buffer))
+ (ding)
+ (message "Refreshing the buffer resulted in no messages!"))))
(defvar notmuch-show-stash-map
(let ((map (make-sparse-keymap)))
@@ -1373,6 +1417,7 @@ reset based on the original query."
(define-key map (kbd "<backtab>") 'notmuch-show-previous-button)
(define-key map (kbd "TAB") 'notmuch-show-next-button)
(define-key map "f" 'notmuch-show-forward-message)
+ (define-key map "F" 'notmuch-show-forward-open-messages)
(define-key map "l" 'notmuch-show-filter-thread)
(define-key map "r" 'notmuch-show-reply-sender)
(define-key map "R" 'notmuch-show-reply)
@@ -1797,8 +1842,18 @@ any effects from previous calls to
(defun notmuch-show-forward-message (&optional prompt-for-sender)
"Forward the current message."
(interactive "P")
- (with-current-notmuch-show-message
- (notmuch-mua-new-forward-message prompt-for-sender)))
+ (notmuch-mua-new-forward-messages (list (notmuch-show-get-message-id))
+ prompt-for-sender))
+
+(put 'notmuch-show-forward-open-messages 'notmuch-prefix-doc
+ "... and prompt for sender")
+(defun notmuch-show-forward-open-messages (&optional prompt-for-sender)
+ "Forward the currently open messages."
+ (interactive "P")
+ (let ((open-messages (notmuch-show-get-message-ids-for-open-messages)))
+ (unless open-messages
+ (error "No open messages to forward."))
+ (notmuch-mua-new-forward-messages open-messages prompt-for-sender)))
(defun notmuch-show-next-message (&optional pop-at-end)
"Show the next message.
@@ -1880,12 +1935,15 @@ to show, nil otherwise."
"View the original source of the current message."
(interactive)
(let* ((id (notmuch-show-get-message-id))
- (buf (get-buffer-create (concat "*notmuch-raw-" id "*"))))
- (let ((coding-system-for-read 'no-conversion))
- (call-process notmuch-command nil buf nil "show" "--format=raw" id))
+ (buf (get-buffer-create (concat "*notmuch-raw-" id "*")))
+ (inhibit-read-only t))
(switch-to-buffer buf)
+ (erase-buffer)
+ (let ((coding-system-for-read 'no-conversion))
+ (call-process notmuch-command nil t nil "show" "--format=raw" id))
(goto-char (point-min))
(set-buffer-modified-p nil)
+ (setq buffer-read-only t)
(view-buffer buf 'kill-buffer-if-not-modified)))
(put 'notmuch-show-pipe-message 'notmuch-doc
@@ -2323,3 +2381,5 @@ is destroyed when FN returns."
(provide 'notmuch-show)
+
+;;; notmuch-show.el ends here
diff --git a/emacs/notmuch-tag.el b/emacs/notmuch-tag.el
index c7f62c90..98064a3b 100644
--- a/emacs/notmuch-tag.el
+++ b/emacs/notmuch-tag.el
@@ -1,4 +1,4 @@
-;; notmuch-tag.el --- tag messages within emacs
+;;; notmuch-tag.el --- tag messages within emacs
;;
;; Copyright © Damien Cassou
;; Copyright © Carl Worth
diff --git a/emacs/notmuch-tree.el b/emacs/notmuch-tree.el
index 384cb76b..4f9ca2de 100644
--- a/emacs/notmuch-tree.el
+++ b/emacs/notmuch-tree.el
@@ -1,4 +1,4 @@
-;; notmuch-tree.el --- displaying notmuch forests.
+;;; notmuch-tree.el --- displaying notmuch forests.
;;
;; Copyright © Carl Worth
;; Copyright © David Edmondson
@@ -22,6 +22,8 @@
;; Authors: David Edmondson <dme@dme.org>
;; Mark Walters <markwalters1009@gmail.com>
+;;; Code:
+
(require 'mail-parse)
(require 'notmuch-lib)
@@ -945,3 +947,5 @@ The arguments are:
;;
(provide 'notmuch-tree)
+
+;;; notmuch-tree.el ends here
diff --git a/emacs/notmuch-version.el.tmpl b/emacs/notmuch-version.el.tmpl
index 236aaf7d..88cc01ce 100644
--- a/emacs/notmuch-version.el.tmpl
+++ b/emacs/notmuch-version.el.tmpl
@@ -1,3 +1,4 @@
+;;; notmuch-version.el --- Version of notmuch
;; -*- emacs-lisp -*-
;;
;; %AG%
@@ -17,7 +18,11 @@
;; You should have received a copy of the GNU General Public License
;; along with Notmuch. If not, see <http://www.gnu.org/licenses/>.
+;;; Code:
+
(defconst notmuch-emacs-version %VERSION%
"Version of Notmuch Emacs MUA.")
(provide 'notmuch-version)
+
+;;; notmuch-version.el ends here
diff --git a/emacs/notmuch-wash.el b/emacs/notmuch-wash.el
index a76b4f5b..065af16f 100644
--- a/emacs/notmuch-wash.el
+++ b/emacs/notmuch-wash.el
@@ -1,4 +1,4 @@
-;; notmuch-wash.el --- cleaning up message bodies
+;;; notmuch-wash.el --- cleaning up message bodies
;;
;; Copyright © Carl Worth
;; Copyright © David Edmondson
@@ -21,6 +21,8 @@
;; Authors: Carl Worth <cworth@cworth.org>
;; David Edmondson <dme@dme.org>
+;;; Code:
+
(require 'coolj)
(declare-function notmuch-show-insert-bodypart "notmuch-show" (msg part depth &optional hide))
@@ -423,3 +425,5 @@ for error."
;;
(provide 'notmuch-wash)
+
+;;; notmuch-wash.el ends here
diff --git a/emacs/notmuch.el b/emacs/notmuch.el
index 463b9262..a8a4c8e5 100644
--- a/emacs/notmuch.el
+++ b/emacs/notmuch.el
@@ -1,4 +1,4 @@
-;; notmuch.el --- run notmuch within emacs
+;;; notmuch.el --- run notmuch within emacs
;;
;; Copyright © Carl Worth
;;
@@ -18,6 +18,9 @@
;; along with Notmuch. If not, see <http://www.gnu.org/licenses/>.
;;
;; Authors: Carl Worth <cworth@cworth.org>
+;; Homepage: https://notmuchmail.org/
+
+;;; Commentary:
;; This is an emacs-based interface to the notmuch mail system.
;;
@@ -47,6 +50,8 @@
;; kudos: Notmuch list <notmuch@notmuchmail.org> (subscription is not
;; required, but is available from http://notmuchmail.org).
+;;; Code:
+
(eval-when-compile (require 'cl))
(require 'mm-view)
(require 'message)
@@ -162,7 +167,7 @@ there will be called at other points of notmuch execution."
(define-key map "o" 'notmuch-search-toggle-order)
(define-key map "c" 'notmuch-search-stash-map)
(define-key map "t" 'notmuch-search-filter-by-tag)
- (define-key map "f" 'notmuch-search-filter)
+ (define-key map "l" 'notmuch-search-filter)
(define-key map [mouse-1] 'notmuch-search-show-thread)
(define-key map "*" 'notmuch-search-tag-all)
(define-key map "a" 'notmuch-search-archive-thread)
@@ -458,7 +463,11 @@ no messages in the region then return nil."
(notmuch-search-properties-in-region :subject beg end))
(defun notmuch-search-show-thread (&optional elide-toggle)
- "Display the currently selected thread."
+ "Display the currently selected thread.
+
+With a prefix argument, invert the default value of
+`notmuch-show-only-matching-messages' when displaying the
+thread."
(interactive "P")
(let ((thread-id (notmuch-search-find-thread-id))
(subject (notmuch-search-find-subject)))
@@ -988,7 +997,7 @@ Enclose QUERY-STRING in parentheses if it matches
query-string))
(defun notmuch-search-filter (query)
- "Filter the current search results based on an additional query string.
+ "Filter or LIMIT the current search results based on an additional query string.
Runs a new search matching only messages that match both the
current search results AND the additional query string provided."
@@ -1060,3 +1069,5 @@ notmuch buffers exist, run `notmuch'."
(let ((init-file (locate-file notmuch-init-file '("/")
(get-load-suffixes))))
(if init-file (load init-file nil t t))))
+
+;;; notmuch.el ends here
diff --git a/lib/database.cc b/lib/database.cc
index 5e86955d..c8c5e261 100644
--- a/lib/database.cc
+++ b/lib/database.cc
@@ -1635,6 +1635,9 @@ notmuch_database_begin_atomic (notmuch_database_t *notmuch)
notmuch->atomic_nesting > 0)
goto DONE;
+ if (notmuch_database_needs_upgrade(notmuch))
+ return NOTMUCH_STATUS_UPGRADE_REQUIRED;
+
try {
(static_cast <Xapian::WritableDatabase *> (notmuch->xapian_db))->begin_transaction (false);
} catch (const Xapian::Error &error) {
@@ -1758,18 +1761,11 @@ _notmuch_database_split_path (void *ctx,
slash = path + strlen (path) - 1;
/* First, skip trailing slashes. */
- while (slash != path) {
- if (*slash != '/')
- break;
-
+ while (slash != path && *slash == '/')
--slash;
- }
/* Then, find a slash. */
- while (slash != path) {
- if (*slash == '/')
- break;
-
+ while (slash != path && *slash != '/') {
if (basename)
*basename = slash;
@@ -1777,12 +1773,8 @@ _notmuch_database_split_path (void *ctx,
}
/* Finally, skip multiple slashes. */
- while (slash != path) {
- if (*slash != '/')
- break;
-
+ while (slash != path && *(slash - 1) == '/')
--slash;
- }
if (slash == path) {
if (directory)
@@ -1791,7 +1783,7 @@ _notmuch_database_split_path (void *ctx,
*basename = path;
} else {
if (directory)
- *directory = talloc_strndup (ctx, path, slash - path + 1);
+ *directory = talloc_strndup (ctx, path, slash - path);
}
return NOTMUCH_STATUS_SUCCESS;
diff --git a/lib/index.cc b/lib/index.cc
index e81aa819..f166aefd 100644
--- a/lib/index.cc
+++ b/lib/index.cc
@@ -377,7 +377,8 @@ _index_mime_part (notmuch_message_t *message,
disposition = g_mime_object_get_content_disposition (part);
if (disposition &&
- strcmp (disposition->disposition, GMIME_DISPOSITION_ATTACHMENT) == 0)
+ strcasecmp (g_mime_content_disposition_get_disposition (disposition),
+ GMIME_DISPOSITION_ATTACHMENT) == 0)
{
const char *filename = g_mime_part_get_filename (GMIME_PART (part));
diff --git a/lib/message.cc b/lib/message.cc
index 26b5e76e..68393055 100644
--- a/lib/message.cc
+++ b/lib/message.cc
@@ -728,7 +728,7 @@ _notmuch_message_add_filename (notmuch_message_t *message,
* Note: This function does not remove a document from the database,
* even if the specified filename is the only filename for this
* message. For that functionality, see
- * _notmuch_database_remove_message. */
+ * notmuch_database_remove_message. */
notmuch_status_t
_notmuch_message_remove_filename (notmuch_message_t *message,
const char *filename)
@@ -1037,20 +1037,90 @@ _notmuch_message_sync (notmuch_message_t *message)
message->modified = FALSE;
}
-/* Delete a message document from the database. */
+/* Delete a message document from the database, leaving a ghost
+ * message in its place */
notmuch_status_t
_notmuch_message_delete (notmuch_message_t *message)
{
notmuch_status_t status;
Xapian::WritableDatabase *db;
+ const char *mid, *tid, *query_string;
+ notmuch_message_t *ghost;
+ notmuch_private_status_t private_status;
+ notmuch_database_t *notmuch;
+ notmuch_query_t *query;
+ unsigned int count = 0;
+ notmuch_bool_t is_ghost;
+
+ mid = notmuch_message_get_message_id (message);
+ tid = notmuch_message_get_thread_id (message);
+ notmuch = message->notmuch;
status = _notmuch_database_ensure_writable (message->notmuch);
if (status)
return status;
- db = static_cast <Xapian::WritableDatabase *> (message->notmuch->xapian_db);
+ db = static_cast <Xapian::WritableDatabase *> (notmuch->xapian_db);
db->delete_document (message->doc_id);
- return NOTMUCH_STATUS_SUCCESS;
+
+ /* if this was a ghost to begin with, we are done */
+ private_status = _notmuch_message_has_term (message, "type", "ghost", &is_ghost);
+ if (private_status)
+ return COERCE_STATUS (private_status,
+ "Error trying to determine whether message was a ghost");
+ if (is_ghost)
+ return NOTMUCH_STATUS_SUCCESS;
+
+ query_string = talloc_asprintf (message, "thread:%s", tid);
+ query = notmuch_query_create (notmuch, query_string);
+ if (query == NULL)
+ return NOTMUCH_STATUS_OUT_OF_MEMORY;
+ status = notmuch_query_count_messages_st (query, &count);
+ if (status) {
+ notmuch_query_destroy (query);
+ return status;
+ }
+
+ if (count > 0) {
+ /* reintroduce a ghost in its place because there are still
+ * other active messages in this thread: */
+ ghost = _notmuch_message_create_for_message_id (notmuch, mid, &private_status);
+ if (private_status == NOTMUCH_PRIVATE_STATUS_NO_DOCUMENT_FOUND) {
+ private_status = _notmuch_message_initialize_ghost (ghost, tid);
+ if (! private_status)
+ _notmuch_message_sync (ghost);
+ } else if (private_status == NOTMUCH_PRIVATE_STATUS_SUCCESS) {
+ /* this is deeply weird, and we should not have gotten
+ into this state. is there a better error message to
+ return here? */
+ status = NOTMUCH_STATUS_DUPLICATE_MESSAGE_ID;
+ }
+
+ notmuch_message_destroy (ghost);
+ status = COERCE_STATUS (private_status, "Error converting to ghost message");
+ } else {
+ /* the thread is empty; drop all ghost messages from it */
+ notmuch_messages_t *messages;
+ status = _notmuch_query_search_documents (query,
+ "ghost",
+ &messages);
+ if (status == NOTMUCH_STATUS_SUCCESS) {
+ notmuch_status_t last_error = NOTMUCH_STATUS_SUCCESS;
+ while (notmuch_messages_valid (messages)) {
+ message = notmuch_messages_get (messages);
+ status = _notmuch_message_delete (message);
+ if (status) /* we'll report the last failure we see;
+ * if there is more than one failure, we
+ * forget about previous ones */
+ last_error = status;
+ notmuch_message_destroy (message);
+ notmuch_messages_move_to_next (messages);
+ }
+ status = last_error;
+ }
+ }
+ notmuch_query_destroy (query);
+ return status;
}
/* Transform a blank message into a ghost message. The caller must
@@ -1180,7 +1250,7 @@ _notmuch_message_remove_term (notmuch_message_t *message,
message->doc.remove_term (term);
message->modified = TRUE;
} catch (const Xapian::InvalidArgumentError) {
- /* We'll let the philosopher's try to wrestle with the
+ /* We'll let the philosophers try to wrestle with the
* question of whether failing to remove that which was not
* there in the first place is failure. For us, we'll silently
* consider it all good. */
@@ -1193,6 +1263,41 @@ _notmuch_message_remove_term (notmuch_message_t *message,
return NOTMUCH_PRIVATE_STATUS_SUCCESS;
}
+notmuch_private_status_t
+_notmuch_message_has_term (notmuch_message_t *message,
+ const char *prefix_name,
+ const char *value,
+ notmuch_bool_t *result)
+{
+ char *term;
+ notmuch_bool_t out = FALSE;
+ notmuch_private_status_t status = NOTMUCH_PRIVATE_STATUS_SUCCESS;
+
+ if (value == NULL)
+ return NOTMUCH_PRIVATE_STATUS_NULL_POINTER;
+
+ term = talloc_asprintf (message, "%s%s",
+ _find_prefix (prefix_name), value);
+
+ if (strlen (term) > NOTMUCH_TERM_MAX)
+ return NOTMUCH_PRIVATE_STATUS_TERM_TOO_LONG;
+
+ try {
+ /* Look for the exact term */
+ Xapian::TermIterator i = message->doc.termlist_begin ();
+ i.skip_to (term);
+ if (i != message->doc.termlist_end () &&
+ !strcmp ((*i).c_str (), term))
+ out = TRUE;
+ } catch (Xapian::Error &error) {
+ status = NOTMUCH_PRIVATE_STATUS_XAPIAN_EXCEPTION;
+ }
+ talloc_free (term);
+
+ *result = out;
+ return status;
+}
+
notmuch_status_t
notmuch_message_add_tag (notmuch_message_t *message, const char *tag)
{
diff --git a/lib/notmuch-private.h b/lib/notmuch-private.h
index 5dd4770e..92807975 100644
--- a/lib/notmuch-private.h
+++ b/lib/notmuch-private.h
@@ -280,6 +280,12 @@ _notmuch_message_remove_term (notmuch_message_t *message,
const char *value);
notmuch_private_status_t
+_notmuch_message_has_term (notmuch_message_t *message,
+ const char *prefix_name,
+ const char *value,
+ notmuch_bool_t *result);
+
+notmuch_private_status_t
_notmuch_message_gen_terms (notmuch_message_t *message,
const char *prefix_name,
const char *text);
@@ -477,6 +483,17 @@ void
_notmuch_doc_id_set_remove (notmuch_doc_id_set_t *doc_ids,
unsigned int doc_id);
+/* querying xapian documents by type (e.g. "mail" or "ghost"): */
+notmuch_status_t
+_notmuch_query_search_documents (notmuch_query_t *query,
+ const char *type,
+ notmuch_messages_t **out);
+
+notmuch_status_t
+_notmuch_query_count_documents (notmuch_query_t *query,
+ const char *type,
+ unsigned *count_out);
+
/* message.cc */
void
diff --git a/lib/notmuch.h b/lib/notmuch.h
index 85b56bf1..cb46fc05 100644
--- a/lib/notmuch.h
+++ b/lib/notmuch.h
@@ -59,8 +59,17 @@ NOTMUCH_BEGIN_DECLS
#define LIBNOTMUCH_MINOR_VERSION 3
#define LIBNOTMUCH_MICRO_VERSION 0
+
+#if defined (__clang_major__) && __clang_major__ >= 3 \
+ || defined (__GNUC__) && __GNUC__ >= 5 \
+ || defined (__GNUC__) && __GNUC__ == 4 && __GNUC_MINOR__ >= 5
#define NOTMUCH_DEPRECATED(major,minor) \
__attribute__ ((deprecated ("function deprecated as of libnotmuch " #major "." #minor)))
+#else
+#define NOTMUCH_DEPRECATED(major,minor) __attribute__ ((deprecated))
+#endif
+
+
#endif /* __DOXYGEN__ */
/**
@@ -1752,7 +1761,7 @@ notmuch_filenames_t *
notmuch_directory_get_child_files (notmuch_directory_t *directory);
/**
- * Get a notmuch_filenams_t iterator listing all the filenames of
+ * Get a notmuch_filenames_t iterator listing all the filenames of
* sub-directories in the database within the given directory.
*
* The returned filenames will be the basename-entries only (not
diff --git a/lib/query.cc b/lib/query.cc
index e627bfc2..77a7926b 100644
--- a/lib/query.cc
+++ b/lib/query.cc
@@ -187,6 +187,14 @@ notmuch_status_t
notmuch_query_search_messages_st (notmuch_query_t *query,
notmuch_messages_t **out)
{
+ return _notmuch_query_search_documents (query, "mail", out);
+}
+
+notmuch_status_t
+_notmuch_query_search_documents (notmuch_query_t *query,
+ const char *type,
+ notmuch_messages_t **out)
+{
notmuch_database_t *notmuch = query->notmuch;
const char *query_string = query->query_string;
notmuch_mset_messages_t *messages;
@@ -208,7 +216,7 @@ notmuch_query_search_messages_st (notmuch_query_t *query,
Xapian::Enquire enquire (*notmuch->xapian_db);
Xapian::Query mail_query (talloc_asprintf (query, "%s%s",
_find_prefix ("type"),
- "mail"));
+ type));
Xapian::Query string_query, final_query, exclude_query;
Xapian::MSet mset;
Xapian::MSetIterator iterator;
@@ -554,6 +562,12 @@ notmuch_query_count_messages (notmuch_query_t *query)
notmuch_status_t
notmuch_query_count_messages_st (notmuch_query_t *query, unsigned *count_out)
{
+ return _notmuch_query_count_documents (query, "mail", count_out);
+}
+
+notmuch_status_t
+_notmuch_query_count_documents (notmuch_query_t *query, const char *type, unsigned *count_out)
+{
notmuch_database_t *notmuch = query->notmuch;
const char *query_string = query->query_string;
Xapian::doccount count = 0;
@@ -562,7 +576,7 @@ notmuch_query_count_messages_st (notmuch_query_t *query, unsigned *count_out)
Xapian::Enquire enquire (*notmuch->xapian_db);
Xapian::Query mail_query (talloc_asprintf (query, "%s%s",
_find_prefix ("type"),
- "mail"));
+ type));
Xapian::Query string_query, final_query, exclude_query;
Xapian::MSet mset;
unsigned int flags = (Xapian::QueryParser::FLAG_BOOLEAN |
diff --git a/notmuch-client.h b/notmuch-client.h
index 3bd2903e..b3d0b668 100644
--- a/notmuch-client.h
+++ b/notmuch-client.h
@@ -31,6 +31,8 @@
#include <gmime/gmime.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"
@@ -70,6 +72,7 @@ typedef struct notmuch_show_format {
typedef struct notmuch_crypto {
notmuch_crypto_context_t* gpgctx;
+ notmuch_crypto_context_t* pkcs7ctx;
notmuch_bool_t verify;
notmuch_bool_t decrypt;
const char *gpgpath;
@@ -407,8 +410,8 @@ struct mime_node {
/* Construct a new MIME node pointing to the root message part of
* message. If crypto->verify is true, signed child parts will be
* verified. If crypto->decrypt is true, encrypted child parts will be
- * decrypted. If crypto->gpgctx is NULL, it will be lazily
- * initialized.
+ * decrypted. If the crypto contexts (crypto->gpgctx or
+ * crypto->pkcs7) are NULL, they will be lazily initialized.
*
* Return value:
*
@@ -459,6 +462,11 @@ print_status_query (const char *loc,
const notmuch_query_t *query,
notmuch_status_t status);
+notmuch_status_t
+print_status_database (const char *loc,
+ const notmuch_database_t *database,
+ notmuch_status_t status);
+
#include "command-line-arguments.h"
extern char *notmuch_requested_db_uuid;
diff --git a/notmuch-emacs-mua b/notmuch-emacs-mua
index 016fa126..4404cd7c 100755
--- a/notmuch-emacs-mua
+++ b/notmuch-emacs-mua
@@ -30,8 +30,8 @@ escape ()
printf -v $2 '%s' "${__escape_arg__//\"/\\\"}"
}
-EMACS=${EMACS-emacs}
-EMACSCLIENT=${EMACSCLIENT-emacsclient}
+EMACS=${EMACS:-emacs}
+EMACSCLIENT=${EMACSCLIENT:-emacsclient}
PRINT_ONLY=
NO_WINDOW=
diff --git a/notmuch-new.c b/notmuch-new.c
index d45d0af8..04cb5cac 100644
--- a/notmuch-new.c
+++ b/notmuch-new.c
@@ -281,6 +281,10 @@ add_file (notmuch_database_t *notmuch, const char *filename,
fprintf (stderr, "Note: Ignoring non-mail file: %s\n", filename);
break;
/* Fatal issues. Don't process anymore. */
+ case NOTMUCH_STATUS_FILE_ERROR:
+ fprintf (stderr, "Unexpected error with file %s\n", filename);
+ (void) print_status_database ("add_file", notmuch, status);
+ goto DONE;
case NOTMUCH_STATUS_READ_ONLY_DATABASE:
case NOTMUCH_STATUS_XAPIAN_EXCEPTION:
case NOTMUCH_STATUS_OUT_OF_MEMORY:
@@ -445,7 +449,7 @@ add_files (notmuch_database_t *notmuch,
*/
if (_entry_in_ignore_list (entry->d_name, state)) {
if (state->debug)
- printf ("(D) add_files_recursive, pass 1: explicitly ignoring %s/%s\n",
+ printf ("(D) add_files, pass 1: explicitly ignoring %s/%s\n",
path, entry->d_name);
continue;
}
@@ -513,9 +517,8 @@ add_files (notmuch_database_t *notmuch,
/* Ignore files & directories user has configured to be ignored */
if (_entry_in_ignore_list (entry->d_name, state)) {
if (state->debug)
- printf ("(D) add_files_recursive, pass 2: explicitly ignoring %s/%s\n",
- path,
- entry->d_name);
+ printf ("(D) add_files, pass 2: explicitly ignoring %s/%s\n",
+ path, entry->d_name);
continue;
}
@@ -529,7 +532,7 @@ add_files (notmuch_database_t *notmuch,
notmuch_filenames_get (db_files));
if (state->debug)
- printf ("(D) add_files_recursive, pass 2: queuing passed file %s for deletion from database\n",
+ printf ("(D) add_files, pass 2: queuing passed file %s for deletion from database\n",
absolute);
_filename_list_add (state->removed_files, absolute);
@@ -547,7 +550,7 @@ add_files (notmuch_database_t *notmuch,
char *absolute = talloc_asprintf (state->removed_directories,
"%s/%s", path, filename);
if (state->debug)
- printf ("(D) add_files_recursive, pass 2: queuing passed directory %s for deletion from database\n",
+ printf ("(D) add_files, pass 2: queuing passed directory %s for deletion from database\n",
absolute);
_filename_list_add (state->removed_directories, absolute);
@@ -618,7 +621,7 @@ add_files (notmuch_database_t *notmuch,
"%s/%s", path,
notmuch_filenames_get (db_files));
if (state->debug)
- printf ("(D) add_files_recursive, pass 3: queuing leftover file %s for deletion from database\n",
+ printf ("(D) add_files, pass 3: queuing leftover file %s for deletion from database\n",
absolute);
_filename_list_add (state->removed_files, absolute);
@@ -633,7 +636,7 @@ add_files (notmuch_database_t *notmuch,
notmuch_filenames_get (db_subdirs));
if (state->debug)
- printf ("(D) add_files_recursive, pass 3: queuing leftover directory %s for deletion from database\n",
+ printf ("(D) add_files, pass 3: queuing leftover directory %s for deletion from database\n",
absolute);
_filename_list_add (state->removed_directories, absolute);
diff --git a/notmuch-reply.c b/notmuch-reply.c
index 13571429..3c6d685c 100644
--- a/notmuch-reply.c
+++ b/notmuch-reply.c
@@ -80,7 +80,8 @@ format_part_reply (mime_node_t *node)
show_text_part_content (node->part, stream_stdout, NOTMUCH_SHOW_TEXT_PART_REPLY);
g_object_unref(stream_stdout);
} else if (disposition &&
- strcmp (disposition->disposition, GMIME_DISPOSITION_ATTACHMENT) == 0) {
+ strcasecmp (g_mime_content_disposition_get_disposition (disposition),
+ GMIME_DISPOSITION_ATTACHMENT) == 0) {
const char *filename = g_mime_part_get_filename (GMIME_PART (node->part));
printf ("Attachment: %s (%s)\n", filename,
g_mime_content_type_to_string (content_type));
@@ -331,7 +332,7 @@ add_recipients_from_message (GMimeMessage *reply,
* field and use the From header. This ensures the original sender
* will get the reply even if not subscribed to the list. Note
* that the address in the Reply-To header will always appear in
- * the reply.
+ * the reply if reply_all is true.
*/
if (reply_to_header_is_redundant (message)) {
reply_to_map[0].header = "from";
diff --git a/notmuch-show.c b/notmuch-show.c
index 5a83c605..87e52bbc 100644
--- a/notmuch-show.c
+++ b/notmuch-show.c
@@ -456,7 +456,8 @@ format_part_text (const void *ctx, sprinter_t *sp, mime_node_t *node,
g_mime_part_get_filename (GMIME_PART (node->part)) : NULL;
if (disposition &&
- strcmp (disposition->disposition, GMIME_DISPOSITION_ATTACHMENT) == 0)
+ strcasecmp (g_mime_content_disposition_get_disposition (disposition),
+ GMIME_DISPOSITION_ATTACHMENT) == 0)
part_type = "attachment";
else
part_type = "part";
diff --git a/notmuch-tag.c b/notmuch-tag.c
index c020cb6f..0d153282 100644
--- a/notmuch-tag.c
+++ b/notmuch-tag.c
@@ -237,10 +237,6 @@ notmuch_tag_command (notmuch_config_t *config, int argc, char *argv[])
fprintf (stderr, "Can't specify both cmdline and stdin!\n");
return EXIT_FAILURE;
}
- if (remove_all) {
- fprintf (stderr, "Can't specify both --remove-all and --batch\n");
- return EXIT_FAILURE;
- }
} else {
tag_ops = tag_op_list_create (config);
if (tag_ops == NULL) {
diff --git a/status.c b/status.c
index 8fa81cbf..45d3fb4e 100644
--- a/status.c
+++ b/status.c
@@ -19,3 +19,20 @@ print_status_query (const char *loc,
}
return status;
}
+
+notmuch_status_t
+print_status_database (const char *loc,
+ const notmuch_database_t *notmuch,
+ notmuch_status_t status)
+{
+ if (status) {
+ const char *msg;
+
+ fprintf (stderr, "%s: %s\n", loc,
+ notmuch_status_to_string (status));
+ msg = notmuch_database_status_string (notmuch);
+ if (msg)
+ fputs (msg, stderr);
+ }
+ return status;
+}
diff --git a/test/.gitignore b/test/.gitignore
index 0f7d5bfc..0579feef 100644
--- a/test/.gitignore
+++ b/test/.gitignore
@@ -7,4 +7,5 @@ smtp-dummy
symbol-test
make-db-version
test-results
+ghost-report
tmp.*
diff --git a/test/Makefile.local b/test/Makefile.local
index 2b186914..022f2cf7 100644
--- a/test/Makefile.local
+++ b/test/Makefile.local
@@ -38,6 +38,9 @@ $(dir)/parse-time: $(dir)/parse-time.o parse-time-string/parse-time-string.o
$(dir)/make-db-version: $(dir)/make-db-version.o
$(call quiet,CXX) $^ -o $@ $(LDFLAGS) $(XAPIAN_LDFLAGS)
+$(dir)/ghost-report: $(dir)/ghost-report.o
+ $(call quiet,CXX) $^ -o $@ $(LDFLAGS) $(XAPIAN_LDFLAGS)
+
.PHONY: test check
test_main_srcs=$(dir)/arg-test.c \
@@ -47,6 +50,7 @@ test_main_srcs=$(dir)/arg-test.c \
$(dir)/smtp-dummy.c \
$(dir)/symbol-test.cc \
$(dir)/make-db-version.cc \
+ $(dir)/ghost-report.cc
test_srcs=$(test_main_srcs) $(dir)/database-test.c
@@ -57,7 +61,7 @@ test-binaries: $(TEST_BINARIES)
test: all test-binaries
ifeq ($V,)
- @echo 'Use "$(MAKE) V=1" to print test headings and PASSIng results.'
+ @echo 'Use "$(MAKE) V=1" to print test headings and PASSing results.'
@env NOTMUCH_TEST_QUIET=1 ${test_src_dir}/notmuch-test $(OPTIONS)
else
# The user has explicitly enabled quiet execution.
diff --git a/test/README b/test/README
index e54e36b7..bd9ab547 100644
--- a/test/README
+++ b/test/README
@@ -8,10 +8,17 @@ enhance.
Prerequisites
-------------
+The test system itself requires:
+
+ - bash(1) version 4.0 or newer
+
+Without bash 4.0+ the tests just refuse to run.
+
Some tests require external dependencies to run. Without them, they
will be skipped, or (rarely) marked failed. Please install these, so
that you know if you break anything.
+ - GNU tar(1)
- dtach(1)
- emacs(1)
- emacsclient(1)
@@ -19,14 +26,21 @@ that you know if you break anything.
- gpg(1)
- python(1)
+If your system lacks these tools or have older, non-upgreable versions
+of these, please (possibly compile and) install these to some other
+path, for example /usr/local/bin or /opt/gnu/bin. Then prepend the
+chosen directory to your PATH before running the tests.
+
+e.g. env PATH=/opt/gnu/bin:$PATH make test
+
Running Tests
-------------
The easiest way to run tests is to say "make test", (or simply run the
notmuch-test script). Either command will run all available tests.
Alternately, you can run a specific subset of tests by simply invoking
-one of the executable scripts in this directory, (such as ./search,
-./reply, etc). Note that you will probably want "make test-binaries"
+one of the executable scripts in this directory, (such as ./T*-search.sh,
+./T*-reply.sh, etc). Note that you will probably want "make test-binaries"
before running individual tests.
The following command-line options are available when running tests:
@@ -80,9 +94,9 @@ can be specified as follows:
You can choose an emacs binary (and corresponding emacsclient) to run
the tests in one of the following ways.
- TEST_EMACS=my-special-emacs TEST_EMACSCLIENT=my-emacsclient make test
- TEST_EMACS=my-special-emacs TEST_EMACSCLIENT=my-emacsclient ./emacs
- make test TEST_EMACS=my-special-emacs TEST_EMACSCLIENT=my-emacsclient
+ TEST_EMACS=my-emacs TEST_EMACSCLIENT=my-emacsclient make test
+ TEST_EMACS=my-emacs TEST_EMACSCLIENT=my-emacsclient ./T*-emacs.sh
+ make test TEST_EMACS=my-emacs TEST_EMACSCLIENT=my-emacsclient
Some tests may require a c compiler. You can choose the name and flags similarly
to with emacs, e.g.
@@ -126,9 +140,13 @@ skipped by the user, as failures.
Writing Tests
-------------
-The test script is written as a shell script. It should start with
-the standard "#!/usr/bin/env bash" with copyright notices, and an
-assignment to variable 'test_description', like this:
+The test script is written as a shell script. It is to be named as
+Tddd-testname.sh where 'ddd' is three digits and 'testname' the "bare"
+name of your test. Tests will be run in order the 'ddd' part determines.
+
+The test script should start with the standard "#!/usr/bin/env bash"
+with copyright notices, and an assignment to variable 'test_description',
+like this:
#!/usr/bin/env bash
#
diff --git a/test/T050-new.sh b/test/T050-new.sh
index 81cf2fad..beeb574a 100755
--- a/test/T050-new.sh
+++ b/test/T050-new.sh
@@ -64,7 +64,7 @@ generate_message
notmuch new > /dev/null
mv "$gen_msg_filename" "${gen_msg_filename}"-renamed
output=$(NOTMUCH_NEW --debug)
-test_expect_equal "$output" "(D) add_files_recursive, pass 2: queuing passed file ${gen_msg_filename} for deletion from database
+test_expect_equal "$output" "(D) add_files, pass 2: queuing passed file ${gen_msg_filename} for deletion from database
No new mail. Detected 1 file rename."
@@ -72,7 +72,7 @@ test_begin_subtest "Deleted message"
rm "${gen_msg_filename}"-renamed
output=$(NOTMUCH_NEW --debug)
-test_expect_equal "$output" "(D) add_files_recursive, pass 3: queuing leftover file ${gen_msg_filename}-renamed for deletion from database
+test_expect_equal "$output" "(D) add_files, pass 3: queuing leftover file ${gen_msg_filename}-renamed for deletion from database
No new mail. Removed 1 message."
@@ -88,7 +88,7 @@ notmuch new > /dev/null
mv "${MAIL_DIR}"/dir "${MAIL_DIR}"/dir-renamed
output=$(NOTMUCH_NEW --debug)
-test_expect_equal "$output" "(D) add_files_recursive, pass 2: queuing passed directory ${MAIL_DIR}/dir for deletion from database
+test_expect_equal "$output" "(D) add_files, pass 2: queuing passed directory ${MAIL_DIR}/dir for deletion from database
No new mail. Detected 3 file renames."
@@ -96,7 +96,7 @@ test_begin_subtest "Deleted directory"
rm -rf "${MAIL_DIR}"/dir-renamed
output=$(NOTMUCH_NEW --debug)
-test_expect_equal "$output" "(D) add_files_recursive, pass 2: queuing passed directory ${MAIL_DIR}/dir-renamed for deletion from database
+test_expect_equal "$output" "(D) add_files, pass 2: queuing passed directory ${MAIL_DIR}/dir-renamed for deletion from database
No new mail. Removed 3 messages."
@@ -115,7 +115,7 @@ test_begin_subtest "Deleted directory (end of list)"
rm -rf "${MAIL_DIR}"/zzz
output=$(NOTMUCH_NEW --debug)
-test_expect_equal "$output" "(D) add_files_recursive, pass 3: queuing leftover directory ${MAIL_DIR}/zzz for deletion from database
+test_expect_equal "$output" "(D) add_files, pass 3: queuing leftover directory ${MAIL_DIR}/zzz for deletion from database
No new mail. Removed 3 messages."
@@ -166,9 +166,18 @@ test_begin_subtest "Deleted two-level directory"
rm -rf "${MAIL_DIR}"/two
output=$(NOTMUCH_NEW --debug)
-test_expect_equal "$output" "(D) add_files_recursive, pass 3: queuing leftover directory ${MAIL_DIR}/two for deletion from database
+test_expect_equal "$output" "(D) add_files, pass 3: queuing leftover directory ${MAIL_DIR}/two for deletion from database
No new mail. Removed 3 messages."
+test_begin_subtest "One character directory at top level"
+
+generate_message [dir]=A
+generate_message [dir]=A/B
+generate_message [dir]=A/B/C
+
+output=$(NOTMUCH_NEW --debug)
+test_expect_equal "$output" "Added 3 new messages to the database."
+
test_begin_subtest "Support single-message mbox"
cat > "${MAIL_DIR}"/mbox_file1 <<EOF
From test_suite@notmuchmail.org Fri Jan 5 15:43:57 2001
@@ -227,20 +236,20 @@ mkdir -p "${MAIL_DIR}"/one/two/three/.git
touch "${MAIL_DIR}"/{one,one/two,one/two/three}/ignored_file
output=$(NOTMUCH_NEW --debug 2>&1 | sort)
test_expect_equal "$output" \
-"(D) add_files_recursive, pass 1: explicitly ignoring ${MAIL_DIR}/.git
-(D) add_files_recursive, pass 1: explicitly ignoring ${MAIL_DIR}/.ignored_hidden_file
-(D) add_files_recursive, pass 1: explicitly ignoring ${MAIL_DIR}/ignored_file
-(D) add_files_recursive, pass 1: explicitly ignoring ${MAIL_DIR}/one/ignored_file
-(D) add_files_recursive, pass 1: explicitly ignoring ${MAIL_DIR}/one/two/ignored_file
-(D) add_files_recursive, pass 1: explicitly ignoring ${MAIL_DIR}/one/two/three/.git
-(D) add_files_recursive, pass 1: explicitly ignoring ${MAIL_DIR}/one/two/three/ignored_file
-(D) add_files_recursive, pass 2: explicitly ignoring ${MAIL_DIR}/.git
-(D) add_files_recursive, pass 2: explicitly ignoring ${MAIL_DIR}/.ignored_hidden_file
-(D) add_files_recursive, pass 2: explicitly ignoring ${MAIL_DIR}/ignored_file
-(D) add_files_recursive, pass 2: explicitly ignoring ${MAIL_DIR}/one/ignored_file
-(D) add_files_recursive, pass 2: explicitly ignoring ${MAIL_DIR}/one/two/ignored_file
-(D) add_files_recursive, pass 2: explicitly ignoring ${MAIL_DIR}/one/two/three/.git
-(D) add_files_recursive, pass 2: explicitly ignoring ${MAIL_DIR}/one/two/three/ignored_file
+"(D) add_files, pass 1: explicitly ignoring ${MAIL_DIR}/.git
+(D) add_files, pass 1: explicitly ignoring ${MAIL_DIR}/.ignored_hidden_file
+(D) add_files, pass 1: explicitly ignoring ${MAIL_DIR}/ignored_file
+(D) add_files, pass 1: explicitly ignoring ${MAIL_DIR}/one/ignored_file
+(D) add_files, pass 1: explicitly ignoring ${MAIL_DIR}/one/two/ignored_file
+(D) add_files, pass 1: explicitly ignoring ${MAIL_DIR}/one/two/three/.git
+(D) add_files, pass 1: explicitly ignoring ${MAIL_DIR}/one/two/three/ignored_file
+(D) add_files, pass 2: explicitly ignoring ${MAIL_DIR}/.git
+(D) add_files, pass 2: explicitly ignoring ${MAIL_DIR}/.ignored_hidden_file
+(D) add_files, pass 2: explicitly ignoring ${MAIL_DIR}/ignored_file
+(D) add_files, pass 2: explicitly ignoring ${MAIL_DIR}/one/ignored_file
+(D) add_files, pass 2: explicitly ignoring ${MAIL_DIR}/one/two/ignored_file
+(D) add_files, pass 2: explicitly ignoring ${MAIL_DIR}/one/two/three/.git
+(D) add_files, pass 2: explicitly ignoring ${MAIL_DIR}/one/two/three/ignored_file
No new mail."
@@ -284,9 +293,9 @@ notmuch config set new.tags $OLDCONFIG
test_begin_subtest "Xapian exception: read only files"
-chmod u-w ${MAIL_DIR}/.notmuch/xapian/*.DB
+chmod u-w ${MAIL_DIR}/.notmuch/xapian/*.${db_ending}
output=$(NOTMUCH_NEW --debug 2>&1 | sed 's/: .*$//' )
-chmod u+w ${MAIL_DIR}/.notmuch/xapian/*.DB
+chmod u+w ${MAIL_DIR}/.notmuch/xapian/*.${db_ending}
test_expect_equal "$output" "A Xapian exception occurred opening database"
test_done
diff --git a/test/T060-count.sh b/test/T060-count.sh
index 3fec94e8..0ac83140 100755
--- a/test/T060-count.sh
+++ b/test/T060-count.sh
@@ -95,7 +95,7 @@ test_expect_equal_file EXPECTED OUTPUT
backup_database
test_begin_subtest "error message for database open"
-dd if=/dev/zero of="${MAIL_DIR}/.notmuch/xapian/postlist.DB" count=3
+dd if=/dev/zero of="${MAIL_DIR}/.notmuch/xapian/postlist.${db_ending}" count=3
notmuch count '*' 2>OUTPUT 1>/dev/null
output=$(sed 's/^\(A Xapian exception [^:]*\):.*$/\1/' OUTPUT)
test_expect_equal "${output}" "A Xapian exception occurred opening database"
@@ -105,7 +105,7 @@ cat <<EOF > count-files.gdb
set breakpoint pending on
break count_files
commands
-shell cp /dev/null ${MAIL_DIR}/.notmuch/xapian/postlist.DB
+shell cp /dev/null ${MAIL_DIR}/.notmuch/xapian/postlist.${db_ending}
continue
end
run
diff --git a/test/T150-tagging.sh b/test/T150-tagging.sh
index 821d3933..a451ffae 100755
--- a/test/T150-tagging.sh
+++ b/test/T150-tagging.sh
@@ -38,6 +38,17 @@ test_expect_equal "$output" "\
thread:XXX 2001-01-05 [1/1] Notmuch Test Suite; One ()
thread:XXX 2001-01-05 [1/1] Notmuch Test Suite; Two (tag5 tag6 unread)"
+test_begin_subtest "Remove all with batch"
+notmuch tag +tag1 One
+notmuch tag --remove-all --batch <<EOF
+-- One
++tag3 +tag4 +inbox -- Two
+EOF
+output=$(notmuch search \* | notmuch_search_sanitize)
+test_expect_equal "$output" "\
+thread:XXX 2001-01-05 [1/1] Notmuch Test Suite; One ()
+thread:XXX 2001-01-05 [1/1] Notmuch Test Suite; Two (inbox tag3 tag4)"
+
test_begin_subtest "Remove all with a no-op"
notmuch tag +inbox +tag1 +unread One
notmuch tag --remove-all +foo +inbox +tag1 -foo +unread Two
@@ -102,6 +113,20 @@ notmuch search \* | notmuch_search_sanitize > OUTPUT
notmuch restore --format=batch-tag < backup.tags
test_expect_equal_file batch.expected OUTPUT
+test_begin_subtest "--batch --input --remove-all"
+notmuch dump --format=batch-tag > backup.tags
+notmuch tag +foo +bar -- One
+notmuch tag +tag7 -- Two
+notmuch tag --batch --input=batch.in --remove-all
+notmuch search \* | notmuch_search_sanitize > OUTPUT
+notmuch restore --format=batch-tag < backup.tags
+cat > batch_removeall.expected <<EOF
+thread:XXX 2001-01-05 [1/1] Notmuch Test Suite; One (@ tag6)
+thread:XXX 2001-01-05 [1/1] Notmuch Test Suite; Two (tag6)
+EOF
+test_expect_equal_file batch_removeall.expected OUTPUT
+rm batch_removeall.expected
+
test_begin_subtest "--batch, blank lines and comments"
notmuch dump | sort > EXPECTED
notmuch tag --batch <<EOF
@@ -262,9 +287,9 @@ test_expect_code 1 "Empty tag names" 'notmuch tag + One'
test_expect_code 1 "Tag name beginning with -" 'notmuch tag +- One'
test_begin_subtest "Xapian exception: read only files"
-chmod u-w ${MAIL_DIR}/.notmuch/xapian/*.DB
+chmod u-w ${MAIL_DIR}/.notmuch/xapian/*.${db_ending}
output=$(notmuch tag +something '*' 2>&1 | sed 's/: .*$//' )
-chmod u+w ${MAIL_DIR}/.notmuch/xapian/*.DB
+chmod u+w ${MAIL_DIR}/.notmuch/xapian/*.${db_ending}
test_expect_equal "$output" "A Xapian exception occurred opening database"
test_done
diff --git a/test/T190-multipart.sh b/test/T190-multipart.sh
index 7c4c9f71..35678909 100755
--- a/test/T190-multipart.sh
+++ b/test/T190-multipart.sh
@@ -763,4 +763,56 @@ test_begin_subtest "indexes mime-type #3"
output=$(notmuch search from:todd and mimetype:multipart/alternative | notmuch_search_sanitize)
test_expect_equal "$output" "thread:XXX 2014-01-12 [1/1] Todd; odd content types (inbox unread)"
+test_begin_subtest "case of Content-Disposition doesn't matter for indexing"
+cat <<EOF > ${MAIL_DIR}/content-disposition
+Return-path: <david@tethera.net>
+Envelope-to: david@tethera.net
+Delivery-date: Sun, 04 Oct 2015 09:16:03 -0300
+Received: from gitolite.debian.net ([87.98.215.224])
+ by yantan.tethera.net with esmtps (TLS1.2:DHE_RSA_AES_128_CBC_SHA1:128)
+ (Exim 4.80)
+ (envelope-from <david@tethera.net>)
+ id 1ZiiCx-0007iz-RK
+ for david@tethera.net; Sun, 04 Oct 2015 09:16:03 -0300
+Received: from remotemail by gitolite.debian.net with local (Exim 4.80)
+ (envelope-from <david@tethera.net>)
+ id 1ZiiC8-0002Rz-Uf; Sun, 04 Oct 2015 12:15:12 +0000
+Received: (nullmailer pid 28621 invoked by uid 1000); Sun, 04 Oct 2015
+ 12:14:53 -0000
+From: David Bremner <david@tethera.net>
+To: David Bremner <david@tethera.net>
+Subject: test attachment
+User-Agent: Notmuch/0.20.2+93~g33c8777 (http://notmuchmail.org) Emacs/24.5.1
+ (x86_64-pc-linux-gnu)
+Date: Sun, 04 Oct 2015 09:14:53 -0300
+Message-ID: <87io6m96f6.fsf@zancas.localnet>
+MIME-Version: 1.0
+Content-Type: multipart/mixed; boundary="=-=-="
+
+--=-=-=
+Content-Type: text/plain
+Content-Disposition: ATTACHMENT; filename=hello.txt
+Content-Description: this is a very exciting file
+
+hello
+
+--=-=-=
+Content-Type: text/plain
+
+
+world
+
+--=-=-=--
+
+EOF
+NOTMUCH_NEW
+
+cat <<EOF > EXPECTED
+attachment
+inbox
+unread
+EOF
+
+notmuch search --output=tags id:87io6m96f6.fsf@zancas.localnet > OUTPUT
+test_expect_equal_file EXPECTED OUTPUT
test_done
diff --git a/test/T310-emacs.sh b/test/T310-emacs.sh
index 61bc369a..daa02568 100755
--- a/test/T310-emacs.sh
+++ b/test/T310-emacs.sh
@@ -130,7 +130,7 @@ test_expect_equal "$output" "thread:XXX 2009-11-18 [4/4] Jjgod Jiang, Alexande
test_begin_subtest "Add tag (large query)"
# We use a long query to force us into batch mode and use a funny tag
# that requires escaping for batch tagging.
-test_emacs "(notmuch-tag (concat \"$os_x_darwin_thread\" \" or \" (make-string notmuch-tag-argument-limit ?x)) (list \"+tag-from-%-large-query\"))"
+test_emacs "(notmuch-tag (concat \"$os_x_darwin_thread\" \" or \" (mapconcat #'identity (make-list notmuch-tag-argument-limit \"x\") \"-\")) (list \"+tag-from-%-large-query\"))"
output=$(notmuch search $os_x_darwin_thread | notmuch_search_sanitize)
test_expect_equal "$output" "thread:XXX 2009-11-18 [4/4] Jjgod Jiang, Alexander Botero-Lowry; [notmuch] Mac OS X/Darwin compatibility issues (inbox tag-from-%-large-query unread)"
notmuch tag -tag-from-%-large-query $os_x_darwin_thread
@@ -394,6 +394,8 @@ User-Agent: Notmuch/XXX Emacs/XXX
--text follows this line--
Adrian Perez de Castro <aperez@igalia.com> writes:
+> [ Unknown signature status ]
+>
> Hello to all,
>
> I have just heard about Not Much today in some random Linux-related news
@@ -473,6 +475,38 @@ Alex Botero-Lowry <alex.boterolowry@gmail.com> writes:
> and http://mail-index.netbsd.org/pkgsrc-bugs/2006/06/07/msg016808.htmlspecifically
> uses 64 as the
> buffer size.
+> From e3bc4bbd7b9d0d086816ab5f8f2d6ffea1dd3ea4 Mon Sep 17 00:00:00 2001
+> From: Alexander Botero-Lowry <alex.boterolowry@gmail.com>
+> Date: Tue, 17 Nov 2009 11:30:39 -0800
+> Subject: [PATCH] Deal with situation where sysconf(_SC_GETPW_R_SIZE_MAX) returns -1
+>
+> ---
+> notmuch-config.c | 2 ++
+> 1 files changed, 2 insertions(+), 0 deletions(-)
+>
+> diff --git a/notmuch-config.c b/notmuch-config.c
+> index 248149c..e7220d8 100644
+> --- a/notmuch-config.c
+> +++ b/notmuch-config.c
+> @@ -77,6 +77,7 @@ static char *
+> get_name_from_passwd_file (void *ctx)
+> {
+> long pw_buf_size = sysconf(_SC_GETPW_R_SIZE_MAX);
+> + if (pw_buf_size == -1) pw_buf_size = 64;
+> char *pw_buf = talloc_zero_size (ctx, pw_buf_size);
+> struct passwd passwd, *ignored;
+> char *name;
+> @@ -101,6 +102,7 @@ static char *
+> get_username_from_passwd_file (void *ctx)
+> {
+> long pw_buf_size = sysconf(_SC_GETPW_R_SIZE_MAX);
+> + if (pw_buf_size == -1) pw_buf_size = 64;
+> char *pw_buf = talloc_zero_size (ctx, pw_buf_size);
+> struct passwd passwd, *ignored;
+> char *name;
+> --
+> 1.6.5.2
+>
> _______________________________________________
> notmuch mailing list
> notmuch@notmuchmail.org
diff --git a/test/T355-smime.sh b/test/T355-smime.sh
new file mode 100755
index 00000000..d9424125
--- /dev/null
+++ b/test/T355-smime.sh
@@ -0,0 +1,90 @@
+#!/usr/bin/env bash
+
+test_description='S/MIME signature verification and decryption'
+. ./test-lib.sh || exit 1
+
+add_gpgsm_home ()
+{
+ local fpr
+ [ -d ${GNUPGHOME} ] && return
+ mkdir -m 0700 "$GNUPGHOME"
+ gpgsm --no-tty --no-common-certs-import --disable-dirmngr --import < $TEST_DIRECTORY/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"
+}
+
+test_require_external_prereq openssl
+test_require_external_prereq gpgsm
+
+cp $TEST_DIRECTORY/smime/key+cert.pem test_suite.pem
+
+FINGERPRINT=$(openssl x509 -fingerprint -in test_suite.pem -noout | sed -e 's/^.*=//' -e s/://g)
+
+add_gpgsm_home
+
+test_expect_success 'emacs delivery of S/MIME signed message' \
+ 'emacs_fcc_message \
+ "test signed message 001" \
+ "This is a test signed message." \
+ "(mml-secure-message-sign \"smime\")"'
+
+# Hard code the MML to avoid several interactive questions
+test_expect_success 'emacs delivery of S/MIME encrypted + signed message' \
+'emacs_fcc_message \
+ "test encrypted message 001" \
+ "<#secure method=smime mode=signencrypt keyfile=\\\"test_suite.pem\\\" certfile=\\\"test_suite.pem\\\">\nThis is a test encrypted message.\n"'
+
+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
+cat <<EOF > EXPECTED
+Verification successful
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "signature verification (notmuch CLI)"
+output=$(notmuch show --format=json --verify subject:"test signed message 001" \
+ | notmuch_json_show_sanitize \
+ | sed -e 's|"created": [-1234567890]*|"created": 946728000|' \
+ -e 's|"expires": [-1234567890]*|"expires": 424242424|' )
+expected='[[[{"id": "XXXXX",
+ "match": true,
+ "excluded": false,
+ "filename": "YYYYY",
+ "timestamp": 946728000,
+ "date_relative": "2000-01-01",
+ "tags": ["inbox","signed"],
+ "headers": {"Subject": "test signed message 001",
+ "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,
+ "sigstatus": [{"status": "good",
+ "fingerprint": "'$FINGERPRINT'",
+ "expires": 424242424,
+ "created": 946728000}],
+ "content-type": "multipart/signed",
+ "content": [{"id": 2,
+ "content-type": "text/plain",
+ "content": "This is a test signed message.\n"},
+ {"id": 3,
+ "content-length": 1922,
+ "content-transfer-encoding": "base64",
+ "content-type": "application/x-pkcs7-signature",
+ "filename": "smime.p7s"}]}]},
+ []]]]'
+test_expect_equal_json \
+ "$output" \
+ "$expected"
+
+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
+cat <<EOF > EXPECTED
+Verification successful
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_done
diff --git a/test/T360-symbol-hiding.sh b/test/T360-symbol-hiding.sh
index 4ec0ea65..3f18ec1a 100755
--- a/test/T360-symbol-hiding.sh
+++ b/test/T360-symbol-hiding.sh
@@ -15,11 +15,11 @@ test_begin_subtest 'running test' run_test
mkdir -p ${PWD}/fakedb/.notmuch
( LD_LIBRARY_PATH="$TEST_DIRECTORY/../lib${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}" \
$TEST_DIRECTORY/symbol-test ${PWD}/fakedb ${PWD}/nonexistent \
- 2>&1 | sed "s,${PWD},CWD,g") > OUTPUT
+ 2>&1 | notmuch_dir_sanitize | sed -e "s,\`,\',g" -e "s,${NOTMUCH_DEFAULT_XAPIAN_BACKEND},backend,g") > OUTPUT
cat <<EOF > EXPECTED
A Xapian exception occurred opening database: Couldn't stat 'CWD/fakedb/.notmuch/xapian'
-caught No chert database found at path \`CWD/nonexistent'
+caught No backend database found at path 'CWD/nonexistent'
EOF
test_expect_equal_file EXPECTED OUTPUT
diff --git a/test/T530-upgrade.sh b/test/T530-upgrade.sh
index 7faf03d3..a3a7d39c 100755
--- a/test/T530-upgrade.sh
+++ b/test/T530-upgrade.sh
@@ -116,25 +116,4 @@ MAIL_DIR/bar/new/21:2,
MAIL_DIR/bar/new/22:2,
MAIL_DIR/cur/51:2,"
-# Ghost messages are difficult to test since they're nearly invisible.
-# However, if the upgrade works correctly, the ghost message should
-# retain the right thread ID even if all of the original messages in
-# the thread are deleted. That's what we test. This won't detect if
-# the upgrade just plain didn't happen, but it should detect if
-# something went wrong.
-test_begin_subtest "ghost message retains thread ID"
-# Upgrade database
-notmuch new
-# Get thread ID of real message
-thread=$(notmuch search --output=threads id:4EFC743A.3060609@april.org)
-# Delete all real messages in that thread
-rm $(notmuch search --output=files $thread)
-notmuch new
-# "Deliver" ghost message
-add_message '[subject]=Ghost' '[id]=4EFC3931.6030007@april.org'
-# If the ghost upgrade worked, the new message should be attached to
-# the existing thread ID.
-nthread=$(notmuch search --output=threads id:4EFC3931.6030007@april.org)
-test_expect_equal "$thread" "$nthread"
-
test_done
diff --git a/test/T560-lib-error.sh b/test/T560-lib-error.sh
index c280939c..087c6bd7 100755
--- a/test/T560-lib-error.sh
+++ b/test/T560-lib-error.sh
@@ -183,13 +183,13 @@ int main (int argc, char** argv)
EOF
cat <<'EOF' >EXPECTED
== stdout ==
-Path already exists: CWD/mail
+Path already exists: MAIL_DIR
== stderr ==
EOF
test_expect_equal_file EXPECTED OUTPUT
-cat <<'EOF' > c_head
+cat <<EOF > c_head
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
@@ -202,16 +202,20 @@ int main (int argc, char** argv)
notmuch_database_t *db;
notmuch_status_t stat;
char *path;
+ char *msg = NULL;
int fd;
- stat = notmuch_database_open (argv[1], NOTMUCH_DATABASE_MODE_READ_WRITE, &db);
+ stat = notmuch_database_open_verbose (argv[1], NOTMUCH_DATABASE_MODE_READ_WRITE, &db, &msg);
if (stat != NOTMUCH_STATUS_SUCCESS) {
- fprintf (stderr, "error opening database: %d\n", stat);
+ fprintf (stderr, "error opening database: %d %s\n", stat, msg ? msg : "");
+ exit (1);
}
- path = talloc_asprintf (db, "%s/.notmuch/xapian/postlist.DB", argv[1]);
+ path = talloc_asprintf (db, "%s/.notmuch/xapian/postlist.${db_ending}", argv[1]);
fd = open(path,O_WRONLY|O_TRUNC);
- if (fd < 0)
- fprintf (stderr, "error opening %s\n");
+ if (fd < 0) {
+ fprintf (stderr, "error opening %s\n", argv[1]);
+ exit (1);
+ }
EOF
cat <<'EOF' > c_tail
if (stat) {
diff --git a/test/T580-thread-search.sh b/test/T580-thread-search.sh
new file mode 100755
index 00000000..6f7106db
--- /dev/null
+++ b/test/T580-thread-search.sh
@@ -0,0 +1,42 @@
+#!/usr/bin/env bash
+#
+# Copyright (c) 2015 David Bremner
+#
+
+test_description='test of searching by thread-id'
+
+. ./test-lib.sh || exit 1
+
+add_email_corpus
+
+test_begin_subtest "Every message is found in exactly one thread"
+
+count=0
+success=0
+for id in $(notmuch search --output=messages '*'); do
+ count=$((count +1))
+ matches=$(notmuch search --output=threads "$id" | wc -l)
+ if [ "$matches" = 1 ]; then
+ success=$((success + 1))
+ fi
+done
+
+test_expect_equal "$count" "$success"
+
+test_begin_subtest "roundtripping message-ids via thread-ids"
+
+count=0
+success=0
+for id in $(notmuch search --output=messages '*'); do
+ count=$((count +1))
+ thread=$(notmuch search --output=threads "$id")
+ matched=$(notmuch search --output=messages "$thread" | grep "$id")
+ if [ "$matched" = "$id" ]; then
+ success=$((success + 1))
+ fi
+done
+
+test_expect_equal "$count" "$success"
+
+
+test_done
diff --git a/test/T590-thread-breakage.sh b/test/T590-thread-breakage.sh
new file mode 100755
index 00000000..6e4031af
--- /dev/null
+++ b/test/T590-thread-breakage.sh
@@ -0,0 +1,124 @@
+#!/usr/bin/env bash
+#
+# Copyright (c) 2016 Daniel Kahn Gillmor
+#
+
+test_description='thread breakage during reindexing
+
+notmuch uses ghost documents to track messages we have seen references
+to but have never seen. Regardless of the order of delivery, message
+deletion, and reindexing, the list of ghost messages for a given
+stored corpus should not vary, so that threads can be reassmebled
+cleanly.
+
+In practice, we accept a small amount of variation (and therefore
+traffic pattern metadata leakage to be stored in the index) for the
+sake of efficiency.
+
+This test also embeds some subtests to ensure that indexing actually
+works properly and attempted fixes to threading issues do not break
+the expected contents of the index.'
+
+. ./test-lib.sh || exit 1
+
+message_a() {
+ mkdir -p ${MAIL_DIR}/cur
+ cat > ${MAIL_DIR}/cur/a <<EOF
+Subject: First message
+Message-ID: <a@example.net>
+From: Alice <alice@example.net>
+To: Bob <bob@example.net>
+Date: Thu, 31 Mar 2016 20:10:00 -0400
+
+This is the first message in the thread.
+Apple
+EOF
+}
+
+message_b() {
+ mkdir -p ${MAIL_DIR}/cur
+ cat > ${MAIL_DIR}/cur/b <<EOF
+Subject: Second message
+Message-ID: <b@example.net>
+In-Reply-To: <a@example.net>
+References: <a@example.net>
+From: Bob <bob@example.net>
+To: Alice <alice@example.net>
+Date: Thu, 31 Mar 2016 20:15:00 -0400
+
+This is the second message in the thread.
+Banana
+EOF
+}
+
+
+test_content_count() {
+ test_begin_subtest "${3:-looking for $2 instance of '$1'}"
+ count=$(notmuch count --output=threads "$1")
+ test_expect_equal "$count" "$2"
+}
+
+test_thread_count() {
+ test_begin_subtest "${2:-Expecting $1 thread(s)}"
+ count=$(notmuch count --output=threads)
+ test_expect_equal "$count" "$1"
+}
+
+test_ghost_count() {
+ test_begin_subtest "${2:-Expecting $1 ghosts(s)}"
+ ghosts=$(../ghost-report ${MAIL_DIR}/.notmuch/xapian)
+ test_expect_equal "$ghosts" "$1"
+}
+
+notmuch new >/dev/null
+
+test_thread_count 0 'There should be no threads initially'
+test_ghost_count 0 'There should be no ghosts initially'
+
+message_a
+notmuch new >/dev/null
+test_thread_count 1 'One message in: one thread'
+test_content_count apple 1
+test_content_count banana 0
+test_ghost_count 0
+
+message_b
+notmuch new >/dev/null
+test_thread_count 1 'Second message in the same thread: one thread'
+test_content_count apple 1
+test_content_count banana 1
+test_ghost_count 0
+
+rm -f ${MAIL_DIR}/cur/a
+notmuch new >/dev/null
+test_thread_count 1 'First message removed: still only one thread'
+test_content_count apple 0
+test_content_count banana 1
+test_ghost_count 1 'should be one ghost after first message removed'
+
+message_a
+notmuch new >/dev/null
+test_thread_count 1 'First message reappears: should return to the same thread'
+test_content_count apple 1
+test_content_count banana 1
+test_ghost_count 0
+
+rm -f ${MAIL_DIR}/cur/b
+notmuch new >/dev/null
+test_thread_count 1 'Removing second message: still only one thread'
+test_content_count apple 1
+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)
+test_expect_equal "$ghosts" "0"
+
+rm -f ${MAIL_DIR}/cur/a
+notmuch new >/dev/null
+test_thread_count 0 'All messages gone: no threads'
+test_content_count apple 0
+test_content_count banana 0
+test_ghost_count 0 'No ghosts should remain after full thread deletion'
+
+test_done
diff --git a/test/atomicity.py b/test/atomicity.py
index 01a42051..1ca52b9c 100644
--- a/test/atomicity.py
+++ b/test/atomicity.py
@@ -29,16 +29,19 @@ class RenameBreakpoint(gdb.Breakpoint):
self.n = 0
def stop(self):
- # As an optimization, only consider snapshots after a Xapian
- # has really committed. Xapian overwrites record.base? as the
- # last step in the commit, so keep an eye on their inumbers.
- inodes = {}
- for path in glob.glob('%s/.notmuch/xapian/record.base*' % maildir):
- inodes[path] = os.stat(path).st_ino
- if inodes == self.last_inodes:
- # Continue
- return False
- self.last_inodes = inodes
+ xapiandir = '%s/.notmuch/xapian' % maildir
+ if os.path.isfile('%s/iamchert' % xapiandir):
+ # As an optimization, only consider snapshots after a
+ # Xapian has really committed. The chert backend
+ # overwrites record.base? as the last step in the commit,
+ # so keep an eye on their inumbers.
+ inodes = {}
+ for path in glob.glob('%s/record.base*' % xapiandir):
+ inodes[path] = os.stat(path).st_ino
+ if inodes == self.last_inodes:
+ # Continue
+ return False
+ self.last_inodes = inodes
# Save a backtrace in case the test does fail
backtrace = gdb.execute('backtrace', to_string=True)
diff --git a/test/emacs-show.expected-output/notmuch-show-elide-non-matching-messages-off b/test/emacs-show.expected-output/notmuch-show-elide-non-matching-messages-off
index b31fe622..9f1e91f0 100644
--- a/test/emacs-show.expected-output/notmuch-show-elide-non-matching-messages-off
+++ b/test/emacs-show.expected-output/notmuch-show-elide-non-matching-messages-off
@@ -5,6 +5,7 @@ Date: Tue, 17 Nov 2009 14:00:54 -0500
[ multipart/mixed ]
[ multipart/signed ]
+[ Unknown signature status ]
[ text/plain ]
I saw the LWN article and decided to take a look at notmuch. I'm
currently using mutt and mairix to index and read a collection of
@@ -39,6 +40,7 @@ Cheers,
[ multipart/mixed ]
[ multipart/signed ]
+ [ Unknown signature status ]
[ text/plain ]
> See the patch just posted here.
@@ -65,6 +67,7 @@ Cheers,
[ multipart/mixed ]
[ multipart/signed ]
+ [ Unknown signature status ]
[ text/plain ]
> I've also pushed a slightly more complicated (and complete) fix to my
> private notmuch repository
diff --git a/test/emacs-show.expected-output/notmuch-show-elide-non-matching-messages-on b/test/emacs-show.expected-output/notmuch-show-elide-non-matching-messages-on
index bafb479e..118053ba 100644
--- a/test/emacs-show.expected-output/notmuch-show-elide-non-matching-messages-on
+++ b/test/emacs-show.expected-output/notmuch-show-elide-non-matching-messages-on
@@ -5,6 +5,7 @@ Date: Tue, 17 Nov 2009 14:00:54 -0500
[ multipart/mixed ]
[ multipart/signed ]
+[ Unknown signature status ]
[ text/plain ]
I saw the LWN article and decided to take a look at notmuch. I'm
currently using mutt and mairix to index and read a collection of
@@ -38,6 +39,7 @@ Cheers,
[ multipart/mixed ]
[ multipart/signed ]
+ [ Unknown signature status ]
[ text/plain ]
> See the patch just posted here.
@@ -62,6 +64,7 @@ Cheers,
[ multipart/mixed ]
[ multipart/signed ]
+ [ Unknown signature status ]
[ text/plain ]
> I've also pushed a slightly more complicated (and complete) fix to my
> private notmuch repository
diff --git a/test/emacs-show.expected-output/notmuch-show-indent-thread-content-off b/test/emacs-show.expected-output/notmuch-show-indent-thread-content-off
index 37b4f7de..2cb12118 100644
--- a/test/emacs-show.expected-output/notmuch-show-indent-thread-content-off
+++ b/test/emacs-show.expected-output/notmuch-show-indent-thread-content-off
@@ -5,6 +5,7 @@ Date: Tue, 17 Nov 2009 14:00:54 -0500
[ multipart/mixed ]
[ multipart/signed ]
+[ Unknown signature status ]
[ text/plain ]
I saw the LWN article and decided to take a look at notmuch. I'm
currently using mutt and mairix to index and read a collection of
@@ -39,6 +40,7 @@ Date: Tue, 17 Nov 2009 15:33:01 -0500
[ multipart/mixed ]
[ multipart/signed ]
+[ Unknown signature status ]
[ text/plain ]
> See the patch just posted here.
@@ -65,6 +67,7 @@ Date: Tue, 17 Nov 2009 19:50:40 -0500
[ multipart/mixed ]
[ multipart/signed ]
+[ Unknown signature status ]
[ text/plain ]
> I've also pushed a slightly more complicated (and complete) fix to my
> private notmuch repository
diff --git a/test/emacs-show.expected-output/notmuch-show-process-crypto-mime-parts-off b/test/emacs-show.expected-output/notmuch-show-process-crypto-mime-parts-off
index 3282c7b1..ce2892a0 100644
--- a/test/emacs-show.expected-output/notmuch-show-process-crypto-mime-parts-off
+++ b/test/emacs-show.expected-output/notmuch-show-process-crypto-mime-parts-off
@@ -9,6 +9,7 @@ Subject: [notmuch] Working with Maildir storage?
[ multipart/mixed ]
[ multipart/signed ]
+ [ Unknown signature status ]
[ text/plain ]
> See the patch just posted here.
diff --git a/test/emacs.expected-output/notmuch-hello b/test/emacs.expected-output/notmuch-hello
index 9ba4cfc1..89186082 100644
--- a/test/emacs.expected-output/notmuch-hello
+++ b/test/emacs.expected-output/notmuch-hello
@@ -8,8 +8,5 @@ Search: .
All tags: [show]
- Type a search query and hit RET to view matching threads.
- Edit saved searches with the `edit' button.
- Hit RET or click on a saved search or tag name to view matching threads.
- `=' to refresh this screen. `s' to search messages. `q' to quit.
- Customize this page.
+ Hit `?' for context-sensitive help in any Notmuch screen.
+ Customize Notmuch or this page.
diff --git a/test/emacs.expected-output/notmuch-hello-long-names b/test/emacs.expected-output/notmuch-hello-long-names
index 1c8d6eb6..da0f3525 100644
--- a/test/emacs.expected-output/notmuch-hello-long-names
+++ b/test/emacs.expected-output/notmuch-hello-long-names
@@ -11,8 +11,5 @@ All tags: [hide]
52 a-very-long-tag 52 inbox 52 unread
4 attachment 7 signed
- Type a search query and hit RET to view matching threads.
- Edit saved searches with the `edit' button.
- Hit RET or click on a saved search or tag name to view matching threads.
- `=' to refresh this screen. `s' to search messages. `q' to quit.
- Customize this page.
+ Hit `?' for context-sensitive help in any Notmuch screen.
+ Customize Notmuch or this page.
diff --git a/test/emacs.expected-output/notmuch-hello-no-saved-searches b/test/emacs.expected-output/notmuch-hello-no-saved-searches
index 05475b15..939965fe 100644
--- a/test/emacs.expected-output/notmuch-hello-no-saved-searches
+++ b/test/emacs.expected-output/notmuch-hello-no-saved-searches
@@ -4,8 +4,5 @@ Search: .
All tags: [show]
- Type a search query and hit RET to view matching threads.
- Edit saved searches with the `edit' button.
- Hit RET or click on a saved search or tag name to view matching threads.
- `=' to refresh this screen. `s' to search messages. `q' to quit.
- Customize this page.
+ Hit `?' for context-sensitive help in any Notmuch screen.
+ Customize Notmuch or this page.
diff --git a/test/emacs.expected-output/notmuch-hello-with-empty b/test/emacs.expected-output/notmuch-hello-with-empty
index 854e0c2a..97d7db26 100644
--- a/test/emacs.expected-output/notmuch-hello-with-empty
+++ b/test/emacs.expected-output/notmuch-hello-with-empty
@@ -8,8 +8,5 @@ Search: .
All tags: [show]
- Type a search query and hit RET to view matching threads.
- Edit saved searches with the `edit' button.
- Hit RET or click on a saved search or tag name to view matching threads.
- `=' to refresh this screen. `s' to search messages. `q' to quit.
- Customize this page.
+ Hit `?' for context-sensitive help in any Notmuch screen.
+ Customize Notmuch or this page.
diff --git a/test/emacs.expected-output/notmuch-show-thread-maildir-storage b/test/emacs.expected-output/notmuch-show-thread-maildir-storage
index cdbfa1d7..35998922 100644
--- a/test/emacs.expected-output/notmuch-show-thread-maildir-storage
+++ b/test/emacs.expected-output/notmuch-show-thread-maildir-storage
@@ -5,6 +5,7 @@ Date: Tue, 17 Nov 2009 14:00:54 -0500
[ multipart/mixed ]
[ multipart/signed ]
+[ Unknown signature status ]
[ text/plain ]
I saw the LWN article and decided to take a look at notmuch. I'm
currently using mutt and mairix to index and read a collection of
@@ -45,6 +46,7 @@ http://notmuchmail.org/mailman/listinfo/notmuch
[ multipart/mixed ]
[ multipart/signed ]
+ [ Unknown signature status ]
[ text/plain ]
Twas brillig at 14:00:54 17.11.2009 UTC-05 when lars@seas.harvard.edu did
@@ -77,6 +79,7 @@ http://notmuchmail.org/mailman/listinfo/notmuch
[ multipart/mixed ]
[ multipart/signed ]
+ [ Unknown signature status ]
[ text/plain ]
> See the patch just posted here.
@@ -159,6 +162,7 @@ http://notmuchmail.org/mailman/listinfo/notmuch
[ multipart/mixed ]
[ multipart/signed ]
+ [ Unknown signature status ]
[ text/plain ]
> I've also pushed a slightly more complicated (and complete) fix to my
> private notmuch repository
diff --git a/test/emacs.expected-output/notmuch-show-thread-maildir-storage-with-fourfold-indentation b/test/emacs.expected-output/notmuch-show-thread-maildir-storage-with-fourfold-indentation
index b0bf93ed..4721b8b0 100644
--- a/test/emacs.expected-output/notmuch-show-thread-maildir-storage-with-fourfold-indentation
+++ b/test/emacs.expected-output/notmuch-show-thread-maildir-storage-with-fourfold-indentation
@@ -5,6 +5,7 @@ Date: Tue, 17 Nov 2009 14:00:54 -0500
[ multipart/mixed ]
[ multipart/signed ]
+[ Unknown signature status ]
[ text/plain ]
I saw the LWN article and decided to take a look at notmuch. I'm
currently using mutt and mairix to index and read a collection of
@@ -45,6 +46,7 @@ http://notmuchmail.org/mailman/listinfo/notmuch
[ multipart/mixed ]
[ multipart/signed ]
+ [ Unknown signature status ]
[ text/plain ]
Twas brillig at 14:00:54 17.11.2009 UTC-05 when lars@seas.harvard.edu did
@@ -77,6 +79,7 @@ http://notmuchmail.org/mailman/listinfo/notmuch
[ multipart/mixed ]
[ multipart/signed ]
+ [ Unknown signature status ]
[ text/plain ]
> See the patch just posted here.
@@ -159,6 +162,7 @@ http://notmuchmail.org/mailman/listinfo/notmuch
[ multipart/mixed ]
[ multipart/signed ]
+ [ Unknown signature status ]
[ text/plain ]
> I've also pushed a slightly more complicated (and complete) fix to my
> private notmuch repository
diff --git a/test/emacs.expected-output/notmuch-show-thread-maildir-storage-without-indentation b/test/emacs.expected-output/notmuch-show-thread-maildir-storage-without-indentation
index 08de8b5d..62a46353 100644
--- a/test/emacs.expected-output/notmuch-show-thread-maildir-storage-without-indentation
+++ b/test/emacs.expected-output/notmuch-show-thread-maildir-storage-without-indentation
@@ -5,6 +5,7 @@ Date: Tue, 17 Nov 2009 14:00:54 -0500
[ multipart/mixed ]
[ multipart/signed ]
+[ Unknown signature status ]
[ text/plain ]
I saw the LWN article and decided to take a look at notmuch. I'm
currently using mutt and mairix to index and read a collection of
@@ -45,6 +46,7 @@ Date: Wed, 18 Nov 2009 01:02:38 +0600
[ multipart/mixed ]
[ multipart/signed ]
+[ Unknown signature status ]
[ text/plain ]
Twas brillig at 14:00:54 17.11.2009 UTC-05 when lars@seas.harvard.edu did
@@ -77,6 +79,7 @@ Date: Tue, 17 Nov 2009 15:33:01 -0500
[ multipart/mixed ]
[ multipart/signed ]
+[ Unknown signature status ]
[ text/plain ]
> See the patch just posted here.
@@ -159,6 +162,7 @@ Date: Tue, 17 Nov 2009 19:50:40 -0500
[ multipart/mixed ]
[ multipart/signed ]
+[ Unknown signature status ]
[ text/plain ]
> I've also pushed a slightly more complicated (and complete) fix to my
> private notmuch repository
diff --git a/test/ghost-report.cc b/test/ghost-report.cc
new file mode 100644
index 00000000..3e1b07c6
--- /dev/null
+++ b/test/ghost-report.cc
@@ -0,0 +1,14 @@
+#include <iostream>
+#include <cstdlib>
+#include <xapian.h>
+
+int main(int argc, char **argv) {
+
+ if (argc < 2) {
+ std::cerr << "usage: ghost-report xapian-dir" << std::endl;
+ exit(1);
+ }
+
+ Xapian::Database db(argv[1]);
+ std::cout << db.get_termfreq("Tghost") << std::endl;
+}
diff --git a/test/smime/README b/test/smime/README
new file mode 100644
index 00000000..92803c77
--- /dev/null
+++ b/test/smime/README
@@ -0,0 +1,7 @@
+test.crt: self signed certificated
+ % gpgsm --gen-key # needs gpgsm 2.1
+
+key+cert.pem: cert + unencryped private
+ % gpsm --import test.crt
+ % gpgsm --export-private-key-p12 -out foo.p12 (no passphrase)
+ % openssl pkcs12 -in ns.p12 -clcerts -nodes > key+cert.pem
diff --git a/test/smime/key+cert.pem b/test/smime/key+cert.pem
new file mode 100644
index 00000000..6ee30cf8
--- /dev/null
+++ b/test/smime/key+cert.pem
@@ -0,0 +1,56 @@
+Bag Attributes
+ friendlyName: GnuPG exported certificate e0972a47
+ localKeyID: 61 6F 46 CD 73 83 4C 63 84 77 56 AF 0D FB 64 A6 E0 97 2A 47
+subject=/CN=Notmuch Test Suite
+issuer=/CN=Notmuch Test Suite
+-----BEGIN CERTIFICATE-----
+MIIDCzCCAfOgAwIBAgIIb3SMlL0MZ6kwDQYJKoZIhvcNAQELBQAwHTEbMBkGA1UE
+AxMSTm90bXVjaCBUZXN0IFN1aXRlMCAXDTE1MTIxNDAyMDgxMFoYDzIwNjMwNDA1
+MTcwMDAwWjAdMRswGQYDVQQDExJOb3RtdWNoIFRlc3QgU3VpdGUwggEiMA0GCSqG
+SIb3DQEBAQUAA4IBDwAwggEKAoIBAQC7vH1/lkENTAJRbyq2036K7Pw+imSIhB5T
+U0WnAgVGWOemY1Eppi9Dk6rjDxuuUKOCQ5el2wmFZN57Fi/4leBH7x217BnnqWNU
+QV88DxEfV+sk8dSb4a5FOOyfhFJmZso/0lK8x0fBcCNjmRFIjB1afSSXWnCvRpAR
+v+O9trLJuIjbbmXg1gltjuB5yDw8/OLEI7G7YSIop9FxopWJL5rW/o2WEfRPGpYe
+HNRLObCRIvbyDd6XjaCrKBuIrhN7R7mmIa9PUyl8TiY+pCMWs9dHmOsiC73/+P6E
+AhsTOY1bfbGQXBAGZ/FL+SgC5wEcPr2u3+y8y5gw2bpaVhQnu6YLAgMBAAGjTTBL
+MCUGA1UdEQQeMByBGnRlc3Rfc3VpdGVAbm90bXVjaG1haWwub3JnMBEGCisGAQQB
+2kcCAgEEAwEB/zAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQBb
+XP5OnRVplrEdlnivx3CbCLWO13fcMWXfvKxLGsKFwKuxtpvINFUKM+jDr0kVdQ3d
+u3DJe2hNFQMILK/KrGyN5qEz2YBdHNvdkkvWA+3WHr/tiNr6Rly6QuxBzouxzmRu
+MmnUhsOzZaHT3GmLSVJlwie8KqSfKVGwyBmCyHbUQkMrSEV6QDESN6KyWt85gokB
+56Bc/wVq073xS1nFbfF1M3Z5q5BlLZK4IOerKTQx/oSfR4EX6B7rW2pttWsUCyEj
+LljaA8ehxR9B29m08IGGl43pHEpC1WnOHvsEGs99mPpjWbUgVv5KY7OuS/8iVw6v
+/Yy5Z+JBwlMzTBaUXXl3
+-----END CERTIFICATE-----
+Bag Attributes
+ friendlyName: GnuPG exported certificate e0972a47
+ localKeyID: 61 6F 46 CD 73 83 4C 63 84 77 56 AF 0D FB 64 A6 E0 97 2A 47
+Key Attributes: <No Attributes>
+-----BEGIN PRIVATE KEY-----
+MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC7vH1/lkENTAJR
+byq2036K7Pw+imSIhB5TU0WnAgVGWOemY1Eppi9Dk6rjDxuuUKOCQ5el2wmFZN57
+Fi/4leBH7x217BnnqWNUQV88DxEfV+sk8dSb4a5FOOyfhFJmZso/0lK8x0fBcCNj
+mRFIjB1afSSXWnCvRpARv+O9trLJuIjbbmXg1gltjuB5yDw8/OLEI7G7YSIop9Fx
+opWJL5rW/o2WEfRPGpYeHNRLObCRIvbyDd6XjaCrKBuIrhN7R7mmIa9PUyl8TiY+
+pCMWs9dHmOsiC73/+P6EAhsTOY1bfbGQXBAGZ/FL+SgC5wEcPr2u3+y8y5gw2bpa
+VhQnu6YLAgMBAAECggEAVhtHCHz3C01Ahu9RDRgGI1w8+cZqA/9tFVTNTqNrne9r
+GHLXKB4z8W/KYmhsjtAnnri31neXb1prfNMZX5AGlZfD7cwDubCEgYGWV6qldNXT
+YVeV54VkdBV+2k9Lp/Ifc5RZJILWk4+Ge8kaF0dEs1tQrCbsJkhcDfgQUdR5PnGe
+6cKv/8HJo0ep6u5cJloIluit8yF3z4+aHixMQBvQKm/8tug+EsrQZ3IVXbh1hONO
+AZ68z9CrU2pJ/0w/jwwcM5feRfTMC7bZ3vkQb1mQKYFJrvN77TGroUtAZFWqJw7M
+r0f2MShdVjfEdJ1ySnCyKF24cSSPSQsLZUe4UlFyQQKBgQDlqr9ajaUzc6Lyma2e
+Q1IJapbX2OZQtf5tlKVCVtZOlu5r97YMOK96XsQFKtdxhAhrGvvTJwPmwhj+fqfR
+XltNrmUBpHCMsm9nloADvBS83KTP5tw9TMT0VZpt+m5XmvutdyQbSKwy+KMy+GZz
+/XBQCfTEoiDS4grGFftvZuRB4QKBgQDRQvsVFMh2NOnVGqczHJNGjvbDueUJmPUN
+3VxZc/FpBGLRSoN7uxQ4dGNnwyvXHs+pLAAC6xZpFCos9c3R8EPvoMyUehoDSAKW
+CMD4C+K8z7n4ducE5a0NrGIgQvnXtteKr3ZwK8V7cscyTCyjXdrQmQ5XHeue8asR
+758g+dG9awKBgEWuZJho2XKe5xWMIu0dp8pLmLCsklRyo1tD+lACYMs/Z99CLO3Q
+VQ1fq0GWGf/K+3LjoPwTnk9pHIQ6kVgotLMA8oxpA+zsRni7ZOO9MN2MZETf2nqO
+zEMFpfEwRkI2N54Nw9qzVeuxHHLegtc2Udk27BisyCCzjGlFSiAmq6KBAoGAFGfE
+RXjcvT65HX8Gaya+wtugFB8BRx0JX7dI6OLk5ZKLmq0ykH2bQepgnWermmU4we77
+0Dvtfa3u0YjZ/24XXg2YbSpWiWps0Y2/C7AyAAzq12/1OGcX5qk4Tbd0f+QkIset
+qxzmt4XcAKw50J+Vf3DmbYQ1M/BftCZcTm0ShHcCgYEAxp8mjE8iIHxFrm7nHMS0
+2/iWxO8DYaAZ0OLfjaZELHchVvTwa+DynbkwvOc3l4cbNTVaf9O6nmHTkLyBLBNr
+2htPKm1vi9TzNdvGqobFO3ijfvdGvq1rjQl86ns0cf395REmEaVX3zcw2v+GyC5n
+qE6Aa5bvdZ9Yykg6aoFo1mY=
+-----END PRIVATE KEY-----
diff --git a/test/smime/test.crt b/test/smime/test.crt
new file mode 100644
index 00000000..e5d1e822
--- /dev/null
+++ b/test/smime/test.crt
@@ -0,0 +1,19 @@
+-----BEGIN CERTIFICATE-----
+MIIDCzCCAfOgAwIBAgIIb3SMlL0MZ6kwDQYJKoZIhvcNAQELBQAwHTEbMBkGA1UE
+AxMSTm90bXVjaCBUZXN0IFN1aXRlMCAXDTE1MTIxNDAyMDgxMFoYDzIwNjMwNDA1
+MTcwMDAwWjAdMRswGQYDVQQDExJOb3RtdWNoIFRlc3QgU3VpdGUwggEiMA0GCSqG
+SIb3DQEBAQUAA4IBDwAwggEKAoIBAQC7vH1/lkENTAJRbyq2036K7Pw+imSIhB5T
+U0WnAgVGWOemY1Eppi9Dk6rjDxuuUKOCQ5el2wmFZN57Fi/4leBH7x217BnnqWNU
+QV88DxEfV+sk8dSb4a5FOOyfhFJmZso/0lK8x0fBcCNjmRFIjB1afSSXWnCvRpAR
+v+O9trLJuIjbbmXg1gltjuB5yDw8/OLEI7G7YSIop9FxopWJL5rW/o2WEfRPGpYe
+HNRLObCRIvbyDd6XjaCrKBuIrhN7R7mmIa9PUyl8TiY+pCMWs9dHmOsiC73/+P6E
+AhsTOY1bfbGQXBAGZ/FL+SgC5wEcPr2u3+y8y5gw2bpaVhQnu6YLAgMBAAGjTTBL
+MCUGA1UdEQQeMByBGnRlc3Rfc3VpdGVAbm90bXVjaG1haWwub3JnMBEGCisGAQQB
+2kcCAgEEAwEB/zAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQBb
+XP5OnRVplrEdlnivx3CbCLWO13fcMWXfvKxLGsKFwKuxtpvINFUKM+jDr0kVdQ3d
+u3DJe2hNFQMILK/KrGyN5qEz2YBdHNvdkkvWA+3WHr/tiNr6Rly6QuxBzouxzmRu
+MmnUhsOzZaHT3GmLSVJlwie8KqSfKVGwyBmCyHbUQkMrSEV6QDESN6KyWt85gokB
+56Bc/wVq073xS1nFbfF1M3Z5q5BlLZK4IOerKTQx/oSfR4EX6B7rW2pttWsUCyEj
+LljaA8ehxR9B29m08IGGl43pHEpC1WnOHvsEGs99mPpjWbUgVv5KY7OuS/8iVw6v
+/Yy5Z+JBwlMzTBaUXXl3
+-----END CERTIFICATE-----
diff --git a/test/test-lib-common.sh b/test/test-lib-common.sh
index 5eb618c1..4e17b781 100644
--- a/test/test-lib-common.sh
+++ b/test/test-lib-common.sh
@@ -48,7 +48,7 @@ restore_database () {
# Test the binaries we have just built. The tests are kept in
# test/ subdirectory and are run in 'trash directory' subdirectory.
-TEST_DIRECTORY=$(pwd)
+TEST_DIRECTORY=$(pwd -P)
notmuch_path=`find_notmuch_path "$TEST_DIRECTORY"`
# configure output
diff --git a/test/test-lib.el b/test/test-lib.el
index 04c8d634..596a7051 100644
--- a/test/test-lib.el
+++ b/test/test-lib.el
@@ -188,3 +188,13 @@ nothing."
;; environments
(setq mm-text-html-renderer 'html2text)
+
+;; Set some variables for S/MIME tests.
+
+(setq smime-keys '(("" "test_suite.pem" nil)))
+
+(setq mml-smime-use 'openssl)
+
+;; all test keys are without passphrase
+(eval-after-load 'smime
+ '(defun smime-ask-passphrase (cache) nil))
diff --git a/test/test-lib.sh b/test/test-lib.sh
index 126911fb..ac04b15a 100644
--- a/test/test-lib.sh
+++ b/test/test-lib.sh
@@ -82,6 +82,9 @@ unset CDPATH
unset GREP_OPTIONS
+# For emacsclient
+unset ALTERNATE_EDITOR
+
# Convenience
#
# A regexp to match 5 and 40 hexdigits
@@ -675,9 +678,14 @@ notmuch_search_sanitize ()
perl -pe 's/("?thread"?: ?)("?)................("?)/\1\2XXX\3/'
}
-notmuch_search_files_sanitize()
+notmuch_search_files_sanitize ()
{
- sed -e "s,$MAIL_DIR,MAIL_DIR,"
+ notmuch_dir_sanitize
+}
+
+notmuch_dir_sanitize ()
+{
+ sed -e "s,$MAIL_DIR,MAIL_DIR," -e "s,${PWD},CWD,g" "$@"
}
NOTMUCH_SHOW_FILENAME_SQUELCH='s,filename:.*/mail,filename:/XXX/mail,'
@@ -1177,7 +1185,7 @@ test_C () {
echo "== stdout ==" > OUTPUT.stdout
echo "== stderr ==" > OUTPUT.stderr
./${exec_file} "$@" 1>>OUTPUT.stdout 2>>OUTPUT.stderr
- sed "s,${PWD},CWD,g" OUTPUT.stdout OUTPUT.stderr > OUTPUT
+ notmuch_dir_sanitize OUTPUT.stdout OUTPUT.stderr > OUTPUT
}
@@ -1319,10 +1327,23 @@ test -z "$NO_PYTHON" && test_set_prereq PYTHON
ln -s x y 2>/dev/null && test -h y 2>/dev/null && test_set_prereq SYMLINKS
rm -f y
+# convert variable from configure to more convenient form
+case "$NOTMUCH_DEFAULT_XAPIAN_BACKEND" in
+ glass)
+ db_ending=glass
+ ;;
+ chert)
+ db_ending=DB
+ ;;
+ *)
+ error "Unknown Xapian backend $NOTMUCH_DEFAULT_XAPIAN_BACKEND"
+esac
# declare prerequisites for external binaries used in tests
test_declare_external_prereq dtach
test_declare_external_prereq emacs
test_declare_external_prereq ${TEST_EMACSCLIENT}
test_declare_external_prereq gdb
test_declare_external_prereq gpg
+test_declare_external_prereq openssl
+test_declare_external_prereq gpgsm
test_declare_external_prereq ${NOTMUCH_PYTHON}
diff --git a/test/tree.expected-output/notmuch-tree-show-window b/test/tree.expected-output/notmuch-tree-show-window
index e16792b8..ab7205b7 100644
--- a/test/tree.expected-output/notmuch-tree-show-window
+++ b/test/tree.expected-output/notmuch-tree-show-window
@@ -5,6 +5,7 @@ Date: Tue, 17 Nov 2009 14:00:54 -0500
[ multipart/mixed ]
[ multipart/signed ]
+[ Unknown signature status ]
[ text/plain ]
I saw the LWN article and decided to take a look at notmuch. I'm
currently using mutt and mairix to index and read a collection of
diff --git a/version b/version
index 5320adc1..e3462940 100644
--- a/version
+++ b/version
@@ -1 +1 @@
-0.21
+0.22