diff options
| author | David Bremner <david@tethera.net> | 2016-06-28 16:48:30 +0200 |
|---|---|---|
| committer | David Bremner <david@tethera.net> | 2016-06-28 16:48:30 +0200 |
| commit | 7fe91ddc4f47170724d455e84d5d262410d569f2 (patch) | |
| tree | 4ba841df17c181ab6a785d408c4e5f8316bda9c1 | |
| parent | 148ceed198f98524db8482e212804c5a510d696b (diff) | |
| parent | ea5caecec5c50833a6f5a422e217a71eee6324af (diff) | |
Merge tag 'debian/0.22-1' into jessie-backports
notmuch Debian 0.22-1 upload (same as 0.22)
102 files changed, 2521 insertions, 666 deletions
@@ -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' @@ -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)) @@ -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) { @@ -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 @@ -1 +1 @@ -0.21 +0.22 |
