notmuch Debian 0.22-1 upload (same as 0.22)

-----BEGIN PGP SIGNATURE-----
 Version: GnuPG v1
 
 iQGcBAABCAAGBQJXIA7bAAoJEPIClx2kp54sZSYL/3uJRbUP2rSe/S7PfEh+iQJY
 /4/mbkS1hz72dDH8A111zB4b05aJ5LeWcMRWklY+6ScATjXSmT3xBNggZbFPmL77
 wfVBcIldwrU7SgoEIW3SyoLPQETlj1gCOTQ0gpEkSFzDw+s4ZBFu9DHf/OfQvJ7I
 elAMdCDzZrgX8UJU4wjmSifLj43rIfAJ1yfCzyvUKeEsJ0eJmUYP5Ia4siSULJrU
 /RqZotdCMbN4/viR0JeOV6SVowPJF4nIP1jTIux150cubnm1XMEr3rs1Dx/xrywR
 8iYy068hhtUpsFQ0+JcVWfmxUlMnJg9SxFSjJAqYwSCl23ukoBybBbjzEII//eYh
 tIVWs2z/VvoOuTgCTuMJp2imz9rhfX/yG/DdY5orupU5AgWklo42i69CoF1pEyhY
 PwCl+OfIv2RfGd6/QE90J8qGEbmy4lv/1sWpbfUGzIZ+y9ePcu/CrWlvhoRKW0LV
 W1nHUvpP15cFV/g5zjwi4iKocduSxvhhD9GW4AzLvA==
 =8VzP
 -----END PGP SIGNATURE-----

Merge tag 'debian/0.22-1' into jessie-backports

notmuch Debian 0.22-1 upload (same as 0.22)
This commit is contained in:
David Bremner 2016-06-28 16:48:30 +02:00
commit 7fe91ddc4f
102 changed files with 2524 additions and 669 deletions

135
NEWS
View file

@ -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
---------------

View file

@ -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/%, \

View file

@ -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
python setup.py install --prefix=path/to/your/preferred/location

View file

@ -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
------------------------------------------------------

View file

@ -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:

View file

@ -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__

View file

@ -75,6 +75,9 @@ from .errors import (
UnbalancedFreezeThawError,
UnbalancedAtomicError,
NotInitializedError,
UnsupportedOperationError,
UpgradeRequiredError,
PathError,
)
from .version import __VERSION__
__LICENSE__ = "GPL v3+"

View file

@ -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

View file

@ -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() + "'"

View file

@ -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

View file

@ -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

View file

@ -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):

View file

@ -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

View file

@ -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

View file

@ -1,3 +1,3 @@
# this file should be kept in sync with ../../../version
__VERSION__ = '0.21'
__VERSION__ = '0.22'
SOVERSION = '4'

48
configure vendored
View file

@ -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))

View file

@ -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;
}

19
debian/changelog vendored
View file

@ -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.

4
debian/control vendored
View file

@ -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

View file

@ -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.

2
devel/nmbug/doc/.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
*.pyc
_build

38
devel/nmbug/doc/Makefile Normal file
View file

@ -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

67
devel/nmbug/doc/conf.py Normal file
View file

@ -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'),
]

17
devel/nmbug/doc/index.rst Normal file
View file

@ -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`

View file

@ -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)**

View file

@ -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)**

View file

@ -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):])

View file

@ -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

View file

@ -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"
}

View file

@ -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

157
devel/try-emacs-mua Executable file
View file

@ -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:

View file

@ -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__)

View file

@ -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

View file

@ -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

View file

@ -143,3 +143,5 @@ If no break point is found, return nil."
t)))
(provide 'coolj)
;;; coolj.el ends here

View file

@ -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

View file

@ -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>
;;; 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

88
emacs/notmuch-company.el Normal file
View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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)))
(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)))
(defun notmuch-show-build-buffer ()
(let ((inhibit-read-only t))
;; No need to track undo information for this buffer.
(setq buffer-undo-list t)
(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)
;; 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-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))
(add-hook 'post-command-hook #'notmuch-show-command-hook nil 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)
(jit-lock-register #'notmuch-show-buttonise-links)
;; No messages were inserted - presumably none matched the
;; query.
(kill-buffer (current-buffer))
(ding)
(message "No messages matched the query!")
nil))))
(notmuch-show-mapc (lambda () (notmuch-show-set-prop :orig-tags (notmuch-show-get-tags))))
(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.
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"))))
(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))))
(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)))
;; 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

View file

@ -1,4 +1,4 @@
;; notmuch-tag.el --- tag messages within emacs
;;; notmuch-tag.el --- tag messages within emacs
;;
;; Copyright © Damien Cassou
;; Copyright © Carl Worth

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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;

View file

@ -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));

View file

@ -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)
{

View file

@ -279,6 +279,12 @@ _notmuch_message_remove_term (notmuch_message_t *message,
const char *prefix_name,
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,
@ -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

View file

@ -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

View file

@ -186,6 +186,14 @@ notmuch_query_search_messages (notmuch_query_t *query)
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;
@ -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;
@ -553,6 +561,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;
@ -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 |

View file

@ -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;

View file

@ -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=

View file

@ -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);

View file

@ -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";

View file

@ -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";

View file

@ -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) {

View file

@ -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;
}

1
test/.gitignore vendored
View file

@ -7,4 +7,5 @@ smtp-dummy
symbol-test
make-db-version
test-results
ghost-report
tmp.*

View file

@ -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.

View file

@ -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
#

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

90
test/T355-smime.sh Executable file
View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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) {

42
test/T580-thread-search.sh Executable file
View file

@ -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

124
test/T590-thread-breakage.sh Executable file
View file

@ -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

View file

@ -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)

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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.

View file

@ -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.

View file

@ -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.

View file

@ -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.

View file

@ -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.

View file

@ -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

View file

@ -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

View file

@ -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

14
test/ghost-report.cc Normal file
View file

@ -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;
}

7
test/smime/README Normal file
View file

@ -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

56
test/smime/key+cert.pem Normal file
View file

@ -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-----

19
test/smime/test.crt Normal file
View file

@ -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-----

View file

@ -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

View file

@ -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))

View file

@ -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}

Some files were not shown because too many files have changed in this diff Show more