uploaded to unstable

-----BEGIN PGP SIGNATURE-----
 Version: GnuPG v1
 
 iQGcBAABCAAGBQJWTbNFAAoJEPIClx2kp54sJiYL/imjoqg0scajZ4p8D0Rkmjc8
 WVSxfKbbbZKbZJuYzBpMtOAl8GaqjwAHLtc3MhFgSXmK//JDT8yMlpHNodk08w91
 u7nuKpZTtgdv1dAD6Pj0fu9FL8t/gC0+0D4ikI+U4EXNlXYiaXZQJ8hNeTlnMj2a
 ppUA3sK/1OYFsxkpxAVM2+WIjDty6uTu1vqOiQe23EZLRz7uFd1SyR9iq6B/o5tK
 wF54B6uneAiVtLCTaSoBIevinGkCegZKSyZDHoECkU/C0wHn9Pd9I+AJhoogjVCb
 FP2P5ehln1ZpaaST2WVBAHUfG9k3mxlCtQlYqLNRbG153SVHbsKIeJ59K5zGIUKA
 7wgzJgOgO6VPu+ZIJWWwp727mTrjlWHLXrSDjMjwqRFANfm2fbYGGremDFcJIvzq
 i3DCsMvuRla8ZQMy57bl64byCW3IV3osg8TFv+pwwY093QhJwt9fc6ITHjM6swNU
 sF08cxT0KTIjtlL6zBIczmiOpgX1uk/TK1ReErl9iA==
 =TQWy
 -----END PGP SIGNATURE-----

Merge tag 'debian/0.21-3' into jessie-backports

uploaded to unstable
This commit is contained in:
David Bremner 2015-12-13 08:39:11 -04:00
commit e3cd357fdd
147 changed files with 2319 additions and 818 deletions

View file

@ -20,7 +20,7 @@ configure stage.
Dependencies
------------
Notmuch depends on four libraries: Xapian, GMime 2.4 or 2.6,
Notmuch depends on four libraries: Xapian, GMime 2.6,
Talloc, and zlib which are each described below:
Xapian
@ -39,8 +39,8 @@ Talloc, and zlib which are each described below:
reading mail while notmuch would wait for Xapian when removing
the "inbox" and "unread" tags from messages in a thread.
GMime 2.4 or 2.6
----------------
GMime 2.6
----------
GMime provides decoding of MIME email messages for Notmuch.
Without GMime, Notmuch would not be able to extract and index

View file

@ -59,7 +59,7 @@ endif
FINAL_LIBNOTMUCH_LDFLAGS = $(LDFLAGS) $(AS_NEEDED_LDFLAGS) $(CONFIGURE_LDFLAGS)
.PHONY: all
all: notmuch notmuch-shared build-man
all: notmuch notmuch-shared build-man ruby-bindings
ifeq ($(MAKECMDGOALS),)
ifeq ($(shell cat .first-build-message 2>/dev/null),)
@NOTMUCH_FIRST_BUILD=1 $(MAKE) --no-print-directory all
@ -224,7 +224,7 @@ release-checks:
.PHONY: verify-newer
verify-newer:
@echo -n "Checking that no $(VERSION) release already exists..."
@wget -q -O /dev/null $(RELEASE_URL)/$(TAR_FILE) ; \
@wget -q --no-check-certificate -O /dev/null $(RELEASE_URL)/$(TAR_FILE) ; \
case $$? in \
8) echo "Good." ;; \
0) echo "Ouch."; \
@ -271,6 +271,7 @@ dataclean: distclean
notmuch_client_srcs = \
command-line-arguments.c\
debugger.c \
status.c \
gmime-filter-reply.c \
hooks.c \
notmuch.c \

147
NEWS
View file

@ -1,3 +1,146 @@
Notmuch 0.21 (2015-10-29)
=========================
General
-------
Notmuch now requires gmime >= 2.6.7. The gmime 2.4 series is no longer
supported.
Database revision tracking: `lastmod:` queries
Each message now has a metadata revision number that increases with
every tagging operation. See the discussion of `lastmod:` in
`notmuch-search-terms(7)` for more information.
Date queries now support `date:<expr>..!` shorthand for
`date:<expr>..<expr>`
You can use, for example, `date:yesterday..!` to match from the
beginning of yesterday to the end of yesterday. For further details,
please refer to the `notmuch-search-terms` manual page.
Notmuch database upgrade to support `lastmod:` queries
The above mentioned `lastmod:` prefix. This will be done
automatically, without prompting on the next time `notmuch new` is
run after the upgrade. The upgrade is not reversible, and the
upgraded database will not be readable by older versions of
Notmuch. As a safeguard, a database dump will be created in the
`.notmuch` directory before upgrading.
Build System
------------
The ruby bindings are now built as part of the main notmuch build
process. This can be disabled with the `--without-ruby` option to
configure.
Building the documentation can be disabled with the `--without-docs`
option to configure.
Skipped individual tests are no longer considered as failures.
Command Line Interface
----------------------
Database revision tracking
Two new options were added to support revision tracking. A global
option "--uuid" (`notmuch(1)`) was added for to detect counter
rollover and reinitialization, and `notmuch-count(1)` gained a
`--lastmod` option to query database revision tracking data.
The `notmuch address` command supports new deduplication schemes
`notmuch address` has gained a new `--deduplicate` option to specify
how the results should be deduplicated, if at all. The alternatives
are `no` (do not deduplicate, useful for processing the results with
external tools), `mailbox` (deduplicate based on the full, case
sensitive name and email address), and `address` (deduplicate based
on the case insensitive address part). See the `notmuch-address`
manual page for further information.
Emacs Interface
---------------
`notmuch-emacs-version` is used in `User-Agent` header
The value of recently introduced variable `notmuch-emacs-version` is
now used as a part of `User-Agent` header when sending emails.
Removed `notmuch-version` function by renaming it to `notmuch-cli-version`
With existing variable `notmuch-emacs-version` the accompanied
function which retrieves the version of `notmuch-command` is
better named as `notmuch-cli-version`.
Query input now supports completion for "is:<tag>"
New message composition mode: `notmuch-compose-mode`
This is mainly to fix fcc handling, but may be useful for user
customization as well.
Allow filtering of search results in `notmuch-show`
Add function to rerun current tree-view search in search mode
Bug fix for replying to encrypted messages in `notmuch-tree` mode
Allow saved searched to specify tree view rather than search view
Applies to saved searches run from `notmuch-hello`, or by a keyboard
shortcut (`notmuch-jump`). Can be set in the customize interface, or
by adding :search-type tree to the appropriate saved search plist in
`notmuch-saved-searches`.
Increase maximum size of rendered text parts
The variable `notmuch-show-max-text-part-size` controls the maximum
size (in bytes) which is automatically rendered. This may make
rendering large threads slower. To get the previous behaviour set
this variable to 10000.
Library
-------
The use of absolute paths is now enforced when calling
`notmuch_database_{open, create}`
New function `notmuch_directory_delete` to delete directory documents
Previously there was no way to delete directory documents from the
database, leading to confusing results when the "ghost" directory
document of a renamed or deleted filesystem directory was
encountered every time the parent directory was being scanned by
`notmuch new`. The mtime of the old directory document was also used
if a directory by the same name was added again in the filesystem,
potentially bypassing the scan for the directory. The issues are
fixed by providing a library call to delete directory documents, and
deleting the old documents in `notmuch new` on filesystem directory
removal or rename.
Database revision tracking
Revision tracking is supported via a new prefix "lastmod:" in the
query parser and the new function
`notmuch_database_get_revision`. For the latter, see `notmuch(3)`.
New status code returning API for n_query_count_{messages,threads}
Deprecated functions
`notmuch_query_search_threads`, `notmuch_query_search_messages`,
`notmuch_query_count_messages`, and `notmuch_query_count_threads`
are all deprecated as of this release. Clients are encouraged to
transition to the `_st` variants supporting better error reporting.
nmbug-status
------------
`nmbug-status` now supports specifying the sort order for each view.
Notmuch 0.20.2 (2015-06-27)
===========================
@ -12,7 +155,7 @@ Notmuch 0.20.1 (2015-06-01)
Test Suite
----------
Work around apparent gdb bug on arm64
Work around apparent gdb bug on arm64.
Notmuch 0.20 (2015-05-31)
=========================
@ -2767,7 +2910,7 @@ New 'G' key binding to trigger mail refresh (G == "Get new mail")
The 'G' key works wherever '=' works. Before refreshing the screen
it calls an external program that can be used to poll email servers,
run notmuch new and setup specific tags for the new emails. The
run notmuch new and set up specific tags for the new emails. The
script to be called should be configured with the "Notmuch Poll
Script" setting in the customize interface. This script will
typically invoke "notmuch new" and then perhaps several "notmuch

7
bindings/Makefile Normal file
View file

@ -0,0 +1,7 @@
# See Makefile.local for the list of files to be compiled in this
# directory.
all:
$(MAKE) -C .. all
.DEFAULT:
$(MAKE) -C .. $@

21
bindings/Makefile.local Normal file
View file

@ -0,0 +1,21 @@
# -*- makefile -*-
dir := bindings
# force the shared library to be built
ruby-bindings: lib/$(LINKER_NAME)
ifeq ($(HAVE_RUBY_DEV),1)
cd $(dir)/ruby && \
EXTRA_LDFLAGS="$(NO_UNDEFINED_LDFLAGS)" \
LIBNOTMUCH="../../lib/$(LINKER_NAME)" \
ruby extconf.rb --vendor
$(MAKE) -C $(dir)/ruby
else
@echo Missing dependency, skipping ruby bindings
endif
CLEAN += $(patsubst %,$(dir)/ruby/%, \
.RUBYARCHDIR.time \
Makefile database.o directory.o filenames.o\
init.o message.o messages.o mkmf.log notmuch.so query.o \
status.o tags.o thread.o threads.o)

View file

@ -17,7 +17,7 @@ along with notmuch. If not, see <http://www.gnu.org/licenses/>.
Copyright 2010 Sebastian Spaeth <Sebastian@SSpaeth.de>
"""
from ctypes import c_char_p, c_uint
from ctypes import c_char_p, c_uint, POINTER, byref
from .globals import (
nmlib,
Enum,
@ -178,8 +178,8 @@ class Query(object):
raise NullPointerError
return Messages(msgs_p, self)
_count_messages = nmlib.notmuch_query_count_messages
_count_messages.argtypes = [NotmuchQueryP]
_count_messages = nmlib.notmuch_query_count_messages_st
_count_messages.argtypes = [NotmuchQueryP, POINTER(c_uint)]
_count_messages.restype = c_uint
def count_messages(self):
@ -191,10 +191,14 @@ class Query(object):
:rtype: int
'''
self._assert_query_is_initialized()
return Query._count_messages(self._query)
count = c_uint(0)
status = Query._count_messages(self._query, byref(count))
if status != 0:
raise NotmuchError(status)
return count.value
_count_threads = nmlib.notmuch_query_count_threads
_count_threads.argtypes = [NotmuchQueryP]
_count_threads = nmlib.notmuch_query_count_threads_st
_count_threads.argtypes = [NotmuchQueryP, POINTER(c_uint)]
_count_threads.restype = c_uint
def count_threads(self):
@ -210,7 +214,11 @@ class Query(object):
:rtype: int
'''
self._assert_query_is_initialized()
return Query._count_threads(self._query)
count = c_uint(0)
status = Query._count_threads(self._query, byref(count))
if status != 0:
raise NotmuchError(status)
return count.value
_destroy = nmlib.notmuch_query_destroy
_destroy.argtypes = [NotmuchQueryP]

View file

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

7
bindings/ruby/README Normal file
View file

@ -0,0 +1,7 @@
To build the the notmuch ruby extension, run the following commands
from the *top level* notmuch source directory:
% ./configure
% make ruby-bindings
The generic documentation about building notmuch also applies.

View file

@ -10,22 +10,16 @@ dir = File.join('..', '..', 'lib')
# includes
$INCFLAGS = "-I#{dir} #{$INCFLAGS}"
# make sure there are no undefined symbols
$LDFLAGS += ' -Wl,--no-undefined'
def have_local_library(lib, path, func, headers = nil)
checking_for checking_message(func, lib) do
lib = File.join(path, lib)
if try_func(func, lib, headers)
$LOCAL_LIBS += lib
end
end
if ENV['EXTRA_LDFLAGS']
$LDFLAGS += " " + ENV['EXTRA_LDFLAGS']
end
if not have_local_library('libnotmuch.so', dir, 'notmuch_database_create', 'notmuch.h')
if not ENV['LIBNOTMUCH']
exit 1
end
$LOCAL_LIBS += ENV['LIBNOTMUCH']
# Create Makefile
dir_config('notmuch')
create_makefile('notmuch')

View file

@ -134,12 +134,13 @@ notmuch_rb_query_search_threads (VALUE self)
{
notmuch_query_t *query;
notmuch_threads_t *threads;
notmuch_status_t status;
Data_Get_Notmuch_Query (self, query);
threads = notmuch_query_search_threads (query);
if (!threads)
rb_raise (notmuch_rb_eMemoryError, "Out of memory");
status = notmuch_query_search_threads_st (query, &threads);
if (status)
notmuch_rb_status_raise (status);
return Data_Wrap_Struct (notmuch_rb_cThreads, NULL, NULL, threads);
}
@ -154,12 +155,13 @@ notmuch_rb_query_search_messages (VALUE self)
{
notmuch_query_t *query;
notmuch_messages_t *messages;
notmuch_status_t status;
Data_Get_Notmuch_Query (self, query);
messages = notmuch_query_search_messages (query);
if (!messages)
rb_raise (notmuch_rb_eMemoryError, "Out of memory");
status = notmuch_query_search_messages_st (query, &messages);
if (status)
notmuch_rb_status_raise (status);
return Data_Wrap_Struct (notmuch_rb_cMessages, NULL, NULL, messages);
}
@ -173,14 +175,16 @@ VALUE
notmuch_rb_query_count_messages (VALUE self)
{
notmuch_query_t *query;
notmuch_status_t status;
unsigned int count;
Data_Get_Notmuch_Query (self, query);
/* Xapian exceptions are not handled properly.
* (function may return 0 after printing a message)
* Thus there is nothing we can do here...
*/
return UINT2NUM(notmuch_query_count_messages(query));
status = notmuch_query_count_messages_st (query, &count);
if (status)
notmuch_rb_status_raise (status);
return UINT2NUM(count);
}
/*
@ -192,12 +196,14 @@ VALUE
notmuch_rb_query_count_threads (VALUE self)
{
notmuch_query_t *query;
notmuch_status_t status;
unsigned int count;
Data_Get_Notmuch_Query (self, query);
/* Xapian exceptions are not handled properly.
* (function may return 0 after printing a message)
* Thus there is nothing we can do here...
*/
return UINT2NUM(notmuch_query_count_threads(query));
status = notmuch_query_count_threads_st (query, &count);
if (status)
notmuch_rb_status_raise (status);
return UINT2NUM(count);
}

View file

@ -27,6 +27,8 @@
# on completion.
#
_notmuch_shared_options="--help --uuid= --version"
# $1: current input of the form prefix:partialinput, where prefix is
# to or from.
_notmuch_email()
@ -84,7 +86,7 @@ _notmuch_search_terms()
sed "s|^$path/||" | grep -v "\(^\|/\)\(cur\|new\|tmp\)$" ) )
;;
*)
local search_terms="from: to: subject: attachment: mimetype: tag: id: thread: folder: path: date:"
local search_terms="from: to: subject: attachment: mimetype: tag: id: thread: folder: path: date: lastmod:"
compopt -o nospace
COMPREPLY=( $(compgen -W "${search_terms}" -- ${cur}) )
;;
@ -109,7 +111,7 @@ _notmuch_compact()
! $split &&
case "${cur}" in
-*)
local options="--backup= --quiet"
local options="--backup= --quiet ${_notmuch_shared_options}"
compopt -o nospace
COMPREPLY=( $(compgen -W "$options" -- ${cur}) )
;;
@ -162,7 +164,7 @@ _notmuch_count()
! $split &&
case "${cur}" in
-*)
local options="--output= --exclude= --batch --input="
local options="--output= --exclude= --batch --input= --lastmod ${_notmuch_shared_options}"
compopt -o nospace
COMPREPLY=( $(compgen -W "$options" -- ${cur}) )
;;
@ -192,7 +194,7 @@ _notmuch_dump()
! $split &&
case "${cur}" in
-*)
local options="--gzip --format= --output="
local options="--gzip --format= --output= ${_notmuch_shared_options}"
compopt -o nospace
COMPREPLY=( $(compgen -W "$options" -- ${cur}) )
;;
@ -222,7 +224,7 @@ _notmuch_insert()
! $split &&
case "${cur}" in
--*)
local options="--create-folder --folder= --keep --no-hooks"
local options="--create-folder --folder= --keep --no-hooks ${_notmuch_shared_options}"
compopt -o nospace
COMPREPLY=( $(compgen -W "$options" -- ${cur}) )
return
@ -245,7 +247,8 @@ _notmuch_new()
case "${cur}" in
-*)
local options="--no-hooks --quiet"
local options="--no-hooks --quiet ${_notmuch_shared_options}"
compopt -o nospace
COMPREPLY=( $(compgen -W "${options}" -- ${cur}) )
;;
esac
@ -271,7 +274,7 @@ _notmuch_reply()
! $split &&
case "${cur}" in
-*)
local options="--format= --format-version= --reply-to= --decrypt"
local options="--format= --format-version= --reply-to= --decrypt ${_notmuch_shared_options}"
compopt -o nospace
COMPREPLY=( $(compgen -W "$options" -- ${cur}) )
;;
@ -301,7 +304,7 @@ _notmuch_restore()
! $split &&
case "${cur}" in
-*)
local options="--format= --accumulate --input="
local options="--format= --accumulate --input= ${_notmuch_shared_options}"
compopt -o nospace
COMPREPLY=( $(compgen -W "$options" -- ${cur}) )
;;
@ -336,7 +339,7 @@ _notmuch_search()
! $split &&
case "${cur}" in
-*)
local options="--format= --output= --sort= --offset= --limit= --exclude= --duplicate="
local options="--format= --output= --sort= --offset= --limit= --exclude= --duplicate= ${_notmuch_shared_options}"
compopt -o nospace
COMPREPLY=( $(compgen -W "$options" -- ${cur}) )
;;
@ -369,12 +372,16 @@ _notmuch_address()
COMPREPLY=( $( compgen -W "true false flag all" -- "${cur}" ) )
return
;;
--deduplicate)
COMPREPLY=( $( compgen -W "no mailbox address" -- "${cur}" ) )
return
;;
esac
! $split &&
case "${cur}" in
-*)
local options="--format= --output= --sort= --exclude="
local options="--format= --output= --sort= --exclude= --deduplicate= ${_notmuch_shared_options}"
compopt -o nospace
COMPREPLY=( $(compgen -W "$options" -- ${cur}) )
;;
@ -408,7 +415,7 @@ _notmuch_show()
! $split &&
case "${cur}" in
-*)
local options="--entire-thread= --format= --exclude= --body= --format-version= --part= --verify --decrypt --include-html"
local options="--entire-thread= --format= --exclude= --body= --format-version= --part= --verify --decrypt --include-html ${_notmuch_shared_options}"
compopt -o nospace
COMPREPLY=( $(compgen -W "$options" -- ${cur}) )
;;
@ -435,7 +442,7 @@ _notmuch_tag()
! $split &&
case "${cur}" in
--*)
local options="--batch --input= --remove-all"
local options="--batch --input= --remove-all ${_notmuch_shared_options}"
compopt -o nospace
COMPREPLY=( $(compgen -W "$options" -- ${cur}) )
return
@ -477,10 +484,15 @@ _notmuch()
if [ -z "${arg}" ]; then
# top level completion
local top_options="--help --version"
case "${cur}" in
-*) COMPREPLY=( $(compgen -W "${top_options}" -- ${cur}) ) ;;
*) COMPREPLY=( $(compgen -W "${_notmuch_commands}" -- ${cur}) ) ;;
-*)
# XXX: handle ${_notmuch_shared_options} and --config=
local options="--help --version"
COMPREPLY=( $(compgen -W "${options}" -- ${cur}) )
;;
*)
COMPREPLY=( $(compgen -W "${_notmuch_commands}" -- ${cur}) )
;;
esac
elif [ "${arg}" = "help" ]; then
# handle help command specially due to _notmuch_commands usage

244
configure vendored
View file

@ -21,6 +21,7 @@ srcdir=$(dirname "$0")
subdirs="util compat lib parse-time-string completion doc emacs"
subdirs="${subdirs} performance-test test test/test-databases"
subdirs="${subdirs} bindings"
# For a non-srcdir configure invocation (such as ../configure), create
# the directory structure and copy Makefiles.
@ -37,7 +38,7 @@ if [ "$srcdir" != "." ]; then
cp -a "$srcdir"/test/* test
# Emacs only likes to generate compiled files next to the .el files
# by default so copy these as well (which is not ideal0.
# by default so copy these as well (which is not ideal).
cp -a "$srcdir"/emacs/*.el emacs
fi
@ -47,9 +48,11 @@ CC=${CC:-cc}
CXX=${CXX:-c++}
CFLAGS=${CFLAGS:--g -O2}
CPPFLAGS=${CPPFLAGS:-}
CXXFLAGS_for_sh=${CXXFLAGS:-${CFLAGS}}
CXXFLAGS=${CXXFLAGS:-\$(CFLAGS)}
LDFLAGS=${LDFLAGS:-}
XAPIAN_CONFIG=${XAPIAN_CONFIG:-xapian-config}
PYTHON=${PYTHON:-}
# We don't allow the EMACS or GZIP Makefile variables inherit values
# from the environment as we do with CC and CXX above. The reason is
@ -62,20 +65,12 @@ XAPIAN_CONFIG=${XAPIAN_CONFIG:-xapian-config}
# options.
PREFIX=/usr/local
LIBDIR=
WITH_DOCS=1
WITH_EMACS=1
WITH_BASH=1
WITH_RUBY=1
WITH_ZSH=1
# Compatible GMime versions (with constraints).
# If using GMime 2.6, we need to have a version >= 2.6.5 to avoid a
# crypto bug. We need 2.6.7 for permissive "From " header handling.
GMIME_24_VERSION_CTR=''
GMIME_24_VERSION="gmime-2.4 $GMIME_24_VERSION_CTR"
GMIME_26_VERSION_CTR='>= 2.6.7'
GMIME_26_VERSION="gmime-2.6 $GMIME_26_VERSION_CTR"
WITH_GMIME_VERSIONS="$GMIME_26_VERSION;$GMIME_24_VERSION"
usage ()
{
cat <<EOF
@ -95,7 +90,7 @@ First, some common variables can specified via environment variables:
CC The C compiler to use
CFLAGS Flags to pass to the C compiler
CPPFLAGS Flags to pass to the C preprocessor
CPPFLAGS Flags to pass to the C preprocessor
CXX The C++ compiler to use
CXXFLAGS Flags to pass to the C compiler
LDFLAGS Flags to pass when linking
@ -109,6 +104,8 @@ Other environment variables can be used to control configure itself,
XAPIAN_CONFIG The program to use to determine flags for
compiling and linking against the Xapian
library. [$XAPIAN_CONFIG]
PYTHON Name of python command to use in
configure and the test suite.
Additionally, various options can be specified on the configure
command line.
@ -133,15 +130,13 @@ Fine tuning of some installation directories is available:
--bashcompletiondir=DIR Bash completions files [SYSCONFDIR/bash_completion.d]
--zshcompletiondir=DIR Zsh completions files [PREFIX/share/zsh/functions/Completion/Unix]
Some specific library versions can be specified (auto-detected otherwise):
--with-gmime-version=VERS Specify GMIME version (2.4 or 2.6)
Some features can be disabled (--with-feature=no is equivalent to
--without-feature) :
--without-emacs Do not install lisp file
--without-bash-completion Do not install bash completions files
--without-docs Do not install documentation and man pages
--without-emacs Do not install lisp file
--without-ruby Do not install ruby bindings
--without-zsh-completion Do not install zsh completions files
Additional options are accepted for compatibility with other
@ -182,6 +177,14 @@ for option; do
BASHCOMPLETIONDIR="${option#*=}"
elif [ "${option%%=*}" = '--zshcompletiondir' ] ; then
ZSHCOMLETIONDIR="${option#*=}"
elif [ "${option%%=*}" = '--with-docs' ]; then
if [ "${option#*=}" = 'no' ]; then
WITH_DOCS=0
else
WITH_DOCS=1
fi
elif [ "${option}" = '--without-docs' ] ; then
WITH_DOCS=0
elif [ "${option%%=*}" = '--with-emacs' ]; then
if [ "${option#*=}" = 'no' ]; then
WITH_EMACS=0
@ -198,6 +201,14 @@ for option; do
fi
elif [ "${option}" = '--without-bash-completion' ] ; then
WITH_BASH=0
elif [ "${option%%=*}" = '--with-ruby' ]; then
if [ "${option#*=}" = 'no' ]; then
WITH_RUBY=0
else
WITH_RUBY=1
fi
elif [ "${option}" = '--without-ruby' ] ; then
WITH_RUBY=0
elif [ "${option%%=*}" = '--with-zsh-completion' ]; then
if [ "${option#*=}" = 'no' ]; then
WITH_ZSH=0
@ -206,12 +217,6 @@ for option; do
fi
elif [ "${option}" = '--without-zsh-completion' ] ; then
WITH_ZSH=0
elif [ "${option%%=*}" = '--with-gmime-version' ] ; then
if [ "${option#*=}" = '2.4' ]; then
WITH_GMIME_VERSIONS=$GMIME_24_VERSION
elif [ "${option#*=}" = '2.6' ]; then
WITH_GMIME_VERSIONS=$GMIME_26_VERSION
fi
elif [ "${option%%=*}" = '--build' ] ; then
true
elif [ "${option%%=*}" = '--host' ] ; then
@ -269,6 +274,64 @@ dependencies are available:
EOF
errors=0
printf "int main(void){return 0;}\n" > minimal.c
printf "Sanity checking C compilation environment... "
if ${CC} ${CFLAGS} ${CPPFLAGS} minimal.c ${LDFLAGS} -o minimal > /dev/null 2>&1
then
printf "OK.\n"
else
printf "Fail.\n"
errors=$((errors + 1))
fi
printf "Sanity checking C++ compilation environment... "
if ${CXX} ${CXXFLAGS_for_sh} ${CPPFLAGS} minimal.c ${LDFLAGS} -o minimal > /dev/null 2>&1
then
printf "OK.\n"
else
printf "Fail.\n"
errors=$((errors + 1))
fi
if [ $errors -gt 0 ]; then
cat <<EOF
*** Error: Initial sanity checking of environment failed. Please try
running configure in a clean environment, and if the problem persists,
report a bug.
EOF
rm -f minimal minimal.c
exit 1
fi
printf "Reading libnotmuch version from source... "
cat > _libversion.c <<EOF
#include <stdio.h>
#include "lib/notmuch.h"
int main(void) {
printf("libnotmuch_version_major=%d\n",
LIBNOTMUCH_MAJOR_VERSION);
printf("libnotmuch_version_minor=%d\n",
LIBNOTMUCH_MINOR_VERSION);
printf("libnotmuch_version_release=%d\n",
LIBNOTMUCH_MICRO_VERSION);
return 0;
}
EOF
if ${CC} ${CFLAGS} _libversion.c -o _libversion > /dev/null 2>&1 && \
./_libversion > _libversion.sh && . ./_libversion.sh
then
printf "OK.\n"
else
cat <<EOF
*** Error: Reading lib/notmuch.h failed.
Please try running configure again in a clean environment, and if the
problem persists, report a bug.
EOF
rm -f _libversion _libversion.c _libversion.sh
exit 1
fi
if pkg-config --version > /dev/null 2>&1; then
have_pkg_config=1
@ -298,30 +361,29 @@ have_xapian_compact=0
if [ ${have_xapian} = "1" ]; then
printf "Checking for Xapian compaction support... "
case "${xapian_version}" in
0.*|1.[01].*|1.2.[0-5])
printf "No (only available with Xapian > 1.2.6).\n" ;;
[1-9]*.[0-9]*.[0-9]*)
have_xapian_compact=1
printf "Yes.\n" ;;
*)
printf "Unknown version.\n" ;;
0.*|1.[01].*|1.2.[0-5])
printf "No (only available with Xapian > 1.2.6).\n" ;;
[1-9]*.[0-9]*.[0-9]*)
have_xapian_compact=1
printf "Yes.\n" ;;
*)
printf "Unknown version.\n" ;;
esac
fi
# we need to have a version >= 2.6.5 to avoid a crypto bug. We need
# 2.6.7 for permissive "From " header handling.
GMIME_MINVER=2.6.7
printf "Checking for GMime development files... "
have_gmime=0
IFS=';'
for gmimepc in $WITH_GMIME_VERSIONS; do
if pkg-config --exists $gmimepc; then
printf "Yes ($gmimepc).\n"
have_gmime=1
gmime_cflags=$(pkg-config --cflags $gmimepc)
gmime_ldflags=$(pkg-config --libs $gmimepc)
break
fi
done
IFS=$DEFAULT_IFS
if [ "$have_gmime" = "0" ]; then
if pkg-config --exists "gmime-2.6 >= $GMIME_MINVER"; then
printf "Yes.\n"
have_gmime=1
gmime_cflags=$(pkg-config --cflags gmime-2.6)
gmime_ldflags=$(pkg-config --libs gmime-2.6)
else
have_gmime=0
printf "No.\n"
errors=$((errors + 1))
fi
@ -377,7 +439,7 @@ fi
printf "Checking for python... "
have_python=0
for name in python python2 python3; do
for name in ${PYTHON} python python2 python3; do
if command -v $name > /dev/null; then
have_python=1
python=$name
@ -434,22 +496,37 @@ else
have_emacs=0
fi
printf "Checking if doxygen is available... "
if command -v doxygen > /dev/null; then
printf "Yes.\n"
have_doxygen=1
else
printf "No (so will not install api docs)\n"
have_doxygen=0
have_doxygen=0
if [ $WITH_DOCS = "1" ] ; then
printf "Checking if doxygen is available... "
if command -v doxygen > /dev/null; then
printf "Yes.\n"
have_doxygen=1
else
printf "No (so will not install api docs)\n"
fi
fi
printf "Checking if sphinx is available and supports nroff output... "
if command -v sphinx-build > /dev/null && ${python} -m sphinx.writers.manpage > /dev/null 2>&1 ; then
printf "Yes.\n"
have_sphinx=1
else
printf "No (so will not install man pages).\n"
have_sphinx=0
have_ruby_dev=0
if [ $WITH_RUBY = "1" ] ; then
printf "Checking for ruby development files... "
if ruby -e "require 'mkmf'"> /dev/null 2>&1; then
printf "Yes.\n"
have_ruby_dev=1
else
printf "No (skipping ruby bindings)\n"
fi
fi
have_sphinx=0
if [ $WITH_DOCS = "1" ] ; then
printf "Checking if sphinx is available and supports nroff output... "
if command -v sphinx-build > /dev/null && ${python} -m sphinx.writers.manpage > /dev/null 2>&1 ; then
printf "Yes.\n"
have_sphinx=1
else
printf "No (so will not install man pages).\n"
fi
fi
libdir_in_ldconfig=0
@ -542,7 +619,7 @@ EOF
echo
fi
if [ $have_gmime -eq 0 ]; then
echo " Either GMime 2.4 library" $GMIME_24_VERSION_CTR "or GMime 2.6 library" $GMIME_26_VERSION_CTR
echo " GMime 2.6 library >= $GMIME_MINVER"
echo " (including development files such as headers)"
echo " http://spruce.sourceforge.net/gmime/"
echo
@ -690,8 +767,6 @@ else
fi
rm -f compat/check_asctime
printf "int main(void){return 0;}\n" > minimal.c
printf "Checking for rpath support... "
if ${CC} -Wl,--enable-new-dtags -Wl,-rpath,/tmp/ -o minimal minimal.c >/dev/null 2>&1
then
@ -712,6 +787,16 @@ else
as_needed_ldflags=""
fi
printf "Checking for -Wl,--no-undefined... "
if ${CC} -Wl,--no-undefined -o minimal minimal.c >/dev/null 2>&1
then
printf "Yes.\n"
no_undefined_ldflags="-Wl,--no-undefined"
else
printf "No (nothing to worry about).\n"
no_undefined_ldflags=""
fi
WARN_CXXFLAGS=""
printf "Checking for available C++ compiler warning flags... "
for flag in -Wall -Wextra -Wwrite-strings; do
@ -732,7 +817,7 @@ for flag in -Wmissing-declarations; do
done
printf "\n\t${WARN_CFLAGS}\n"
rm -f minimal minimal.c
rm -f minimal minimal.c _libversion.c _libversion _libversion.sh
# construct the Makefile.config
cat > Makefile.config <<EOF
@ -770,6 +855,28 @@ vpath Makefile.% \$(srcdir)
vpath %.py \$(srcdir)
vpath %.rst \$(srcdir)
# Library versions (used to make SONAME)
# The major version of the library interface. This will control the soname.
# As such, this number must be incremented for any incompatible change to
# the library interface, (such as the deletion of an API or a major
# semantic change that breaks formerly functioning code).
#
LIBNOTMUCH_VERSION_MAJOR = ${libnotmuch_version_major}
# The minor version of the library interface. This should be incremented at
# the time of release for any additions to the library interface,
# (and when it is incremented, the release version of the library should
# be reset to 0).
LIBNOTMUCH_VERSION_MINOR = ${libnotmuch_version_minor}
# The release version the library interface. This should be incremented at
# the time of release if there have been no changes to the interface, (but
# simply compatible changes to the implementation).
LIBNOTMUCH_VERSION_RELEASE = ${libnotmuch_version_release}
# These are derived from the VERSION macros in lib/notmuch.h so
# if you have to change them, something is wrong.
# The C compiler to use
CC = ${CC}
@ -858,6 +965,10 @@ HAVE_CANONICALIZE_FILE_NAME = ${have_canonicalize_file_name}
# build its own version)
HAVE_GETLINE = ${have_getline}
# Are the ruby development files (and ruby) available? If not skip
# building/testing ruby bindings.
HAVE_RUBY_DEV = ${have_ruby_dev}
# Whether the strcasestr function is available (if not, then notmuch will
# build its own version)
HAVE_STRCASESTR = ${have_strcasestr}
@ -894,7 +1005,7 @@ LINKER_RESOLVES_LIBRARY_DEPENDENCIES = ${linker_resolves_library_dependencies}
XAPIAN_CXXFLAGS = ${xapian_cxxflags}
XAPIAN_LDFLAGS = ${xapian_ldflags}
# Flags needed to compile and link against GMime-2.4
# Flags needed to compile and link against GMime
GMIME_CFLAGS = ${gmime_cflags}
GMIME_LDFLAGS = ${gmime_ldflags}
@ -912,6 +1023,9 @@ RPATH_LDFLAGS = ${rpath_ldflags}
# Flags needed to have linker link only to necessary libraries
AS_NEEDED_LDFLAGS = ${as_needed_ldflags}
# Flags to have the linker flag undefined symbols in object files
NO_UNDEFINED_LDFLAGS = ${no_undefined_ldflags}
# Whether valgrind header files are available
HAVE_VALGRIND = ${have_valgrind}
@ -970,6 +1084,10 @@ NOTMUCH_HAVE_MAN=$((have_sphinx))
# Name of python interpreter
NOTMUCH_PYTHON=${python}
# Are the ruby development files (and ruby) available? If not skip
# building/testing ruby bindings.
NOTMUCH_HAVE_RUBY_DEV=${have_ruby_dev}
EOF
# Finally, after everything configured, inform the user how to continue.

View file

@ -20,8 +20,6 @@
#include "notmuch-client.h"
#ifdef GMIME_ATLEAST_26
/* Create a GPG context (GMime 2.6) */
static notmuch_crypto_context_t *
create_gpg_context (const char *gpgpath)
@ -39,29 +37,6 @@ create_gpg_context (const char *gpgpath)
return gpgctx;
}
#else /* GMIME_ATLEAST_26 */
/* Create a GPG context (GMime 2.4) */
static notmuch_crypto_context_t *
create_gpg_context (const char* gpgpath)
{
GMimeSession *session;
notmuch_crypto_context_t *gpgctx;
session = g_object_new (g_mime_session_get_type (), NULL);
gpgctx = g_mime_gpg_context_new (session, gpgpath ? gpgpath : "gpg");
g_object_unref (session);
if (! gpgctx)
return NULL;
g_mime_gpg_context_set_always_trust ((GMimeGpgContext *) gpgctx, FALSE);
return gpgctx;
}
#endif /* GMIME_ATLEAST_26 */
/* for the specified protocol return the context pointer (initializing
* if needed) */
notmuch_crypto_context_t *

10
debian/NEWS vendored
View file

@ -1,3 +1,13 @@
notmuch (0.21~rc1-1) experimental; urgency=medium
* This release of notmuch requires a non-reversible database upgrade
to support database revision tracking. This upgrade will happen on
the first run of 'notmuch-new' after updating. Notmuch will backup
your tags for your before doing the upgrade, but it never hurts to
make your own backup with notmuch dump.
-- David Bremner <bremner@debian.org> Thu, 15 Oct 2015 08:13:04 -0300
notmuch (0.19-1) experimental; urgency=medium
* This release of notmuch again requires a non-reversable database

64
debian/changelog vendored
View file

@ -1,3 +1,67 @@
notmuch (0.21-3) unstable; urgency=medium
* Add mips and mips64el to gdb build-dep blacklist
-- David Bremner <bremner@debian.org> Sat, 14 Nov 2015 19:07:06 -0400
notmuch (0.21-2) unstable; urgency=medium
* Build-conflict with gdb on ppc64el and mipsel. Workaround gdb breakage on those
architectures (Closes: #804792).
-- David Bremner <bremner@debian.org> Thu, 12 Nov 2015 08:54:23 -0400
notmuch (0.21-1) unstable; urgency=medium
* New upstream release. Highlights include
- revision tracking for metadata
- new features and bug fixes for emacs interface
See /usr/share/doc/notmuch/NEWS for more details.
-- David Bremner <bremner@debian.org> Thu, 29 Oct 2015 20:04:42 -0300
notmuch (0.21~rc3-3) experimental; urgency=medium
* Build-conflict with gdb-minimal. gdb python scripts are needed for
the test suite
-- David Bremner <bremner@debian.org> Sun, 25 Oct 2015 22:08:56 -0300
notmuch (0.21~rc3-2) experimental; urgency=medium
* Bug fix: "reply-to encrypted messages in tree view fails to quote
and defaults to unencrypted message", thanks to Vagrant Cascadian
(Closes: #795243).
* Bug fix: "install/notmuch-emacs may interact with console, fail
emacs24 upgrade", thanks to Hilko Bengen (Closes: #802952).
-- David Bremner <bremner@debian.org> Sun, 25 Oct 2015 13:42:57 -0300
notmuch (0.21~rc3-1) experimental; urgency=medium
* New upstream release candidate
-- David Bremner <bremner@debian.org> Thu, 22 Oct 2015 09:19:02 -0300
notmuch (0.21~rc2-1) experimental; urgency=medium
* New upstream release candidate
-- David Bremner <bremner@debian.org> Mon, 19 Oct 2015 07:25:10 -0300
notmuch (0.21~rc1-1) experimental; urgency=medium
* New upstream release candidate
-- David Bremner <bremner@debian.org> Thu, 15 Oct 2015 08:08:17 -0300
notmuch (0.20.2-2) unstable; urgency=medium
* Fix linking in emacsen-install script. The previous version can
break an emacs upgrade.
-- David Bremner <bremner@debian.org> Sat, 26 Sep 2015 09:26:41 -0300
notmuch (0.20.2-1~bpo8+1) jessie-backports; urgency=medium
* Rebuild for jessie-backports.

4
debian/control vendored
View file

@ -5,7 +5,7 @@ Maintainer: Carl Worth <cworth@debian.org>
Uploaders:
Jameson Graef Rollins <jrollins@finestructure.net>,
David Bremner <bremner@debian.org>
Build-Conflicts: ruby1.8
Build-Conflicts: ruby1.8, gdb-minimal, gdb [s390x ia64 armel ppc64el mips mipsel mips64el]
Build-Depends:
debhelper (>= 9),
pkg-config,
@ -20,7 +20,7 @@ Build-Depends:
ruby, ruby-dev (>>1:1.9.3~),
emacs24-nox | emacs24 (>=24~) | emacs24-lucid (>=24~) |
emacs23-nox | emacs23 (>=23~) | emacs23-lucid (>=23~),
gdb [!s390x !ia64 !armel],
gdb [!s390x !ia64 !armel !ppc64el !mips !mipsel !mips64el],
dtach (>= 0.8),
bash-completion (>=1.9.0~)
Standards-Version: 3.9.6

View file

@ -12,6 +12,7 @@ libnotmuch.so.4 libnotmuch4 #MINVER#
notmuch_database_get_all_tags@Base 0.3
notmuch_database_get_directory@Base 0.3
notmuch_database_get_path@Base 0.3
notmuch_database_get_revision@Base 0.21~rc1
notmuch_database_get_version@Base 0.3
notmuch_database_needs_upgrade@Base 0.3
notmuch_database_open@Base 0.3
@ -19,6 +20,7 @@ libnotmuch.so.4 libnotmuch4 #MINVER#
notmuch_database_remove_message@Base 0.3
notmuch_database_status_string@Base 0.20~rc1
notmuch_database_upgrade@Base 0.3
notmuch_directory_delete@Base 0.21~rc1
notmuch_directory_destroy@Base 0.3
notmuch_directory_get_child_directories@Base 0.3
notmuch_directory_get_child_files@Base 0.3
@ -53,9 +55,12 @@ libnotmuch.so.4 libnotmuch4 #MINVER#
notmuch_messages_valid@Base 0.3
notmuch_query_add_tag_exclude@Base 0.12~rc1
notmuch_query_count_messages@Base 0.3
notmuch_query_count_messages_st@Base 0.21~rc1
notmuch_query_count_threads@Base 0.10~rc1
notmuch_query_count_threads_st@Base 0.21~rc1
notmuch_query_create@Base 0.3
notmuch_query_destroy@Base 0.3
notmuch_query_get_database@Base 0.21~rc1
notmuch_query_get_query_string@Base 0.4
notmuch_query_get_sort@Base 0.4
notmuch_query_search_messages@Base 0.3

View file

@ -31,7 +31,7 @@ echo install/${PACKAGE}: byte-compiling for ${FLAVOR}
# Create symlinks to the .el files (see section 6E in debian-emacs
# polcy). This makes complation easy, and also allows find-function
# and find-library to work properly.
(cd ${elc_dir} && cp -s ${el_dir}/*.el .)
(cd ${elc_dir} && ln -sf ${el_dir}/*.el .)
# Byte compile them
(cd ${elc_dir}
@ -43,6 +43,6 @@ echo install/${PACKAGE}: byte-compiling for ${FLAVOR}
exit 1
fi
set -e
gzip -9 Install.log)
gzip -9f Install.log)
exit 0;

1
debian/rules vendored
View file

@ -19,7 +19,6 @@ override_dh_auto_build:
dh_auto_build -- V=1
dh_auto_build --sourcedirectory bindings/python
cd bindings/python && $(python3_all) setup.py build
cd bindings/ruby && ruby extconf.rb --vendor && make
$(MAKE) -C contrib/notmuch-mutt
override_dh_auto_clean:

View file

@ -93,3 +93,13 @@ libnotmuch conventions
* Code which needs to be accessed from both the CLI and from
libnotmuch should be factored out into libutil (under util/).
* Deprecated functions should be marked with the NOTMUCH_DEPRECATED
macro which generates run time warnings with gcc and clang. In order
not to confuse doxygen this should go at the beginning of the
declaration like:
NOTMUCH_DEPRECATED(major,minor) notmuch_status_t notmuch_dwim(void *arg);
The @deprecated doxygen command can be used to generate markup in
the API docs.

View file

@ -79,7 +79,7 @@ while getopts v:c:s: opt; do
done
shift `expr $OPTIND - 1`
. ./test-lib.sh
. ./test-lib.sh || exit 1
SHORT_CORPUS=$(basename ${CORPUS:-database})
DBNAME=${SHORT_CORPUS}${SUFFIX}

View file

@ -156,11 +156,20 @@ class Page (object):
stream.write(self.footer)
def _write_view(self, database, view, stream):
# sort order, default to oldest-first
sort_key = view.get('sort', 'oldest-first')
# dynamically accept all values in Query.SORT
sort_attribute = sort_key.upper().replace('-', '_')
try:
sort = getattr(notmuch.Query.SORT, sort_attribute)
except AttributeError:
raise ConfigError('Invalid sort setting for {}: {!r}'.format(
view['title'], sort_key))
if 'query-string' not in view:
query = view['query']
view['query-string'] = ' and '.join(query)
q = notmuch.Query(database, view['query-string'])
q.set_sort(notmuch.Query.SORT.OLDEST_FIRST)
q.set_sort(sort)
threads = self._get_threads(messages=q.search_messages())
self._write_view_header(view=view, stream=stream)
self._write_threads(threads=threads, stream=stream)
@ -309,7 +318,7 @@ args = parser.parse_args()
try:
config = read_config(path=args.config)
except ConfigError as e:
print(e)
print(e, file=sys.stderr)
sys.exit(1)
header_template = config['meta'].get('header', '''<!DOCTYPE html>

View file

@ -59,6 +59,17 @@ readonly VERSION
# In the rest of this file, tests collect list of errors to be fixed
echo -n "Checking that git working directory is clean... "
git_status=`git status --porcelain`
if [ "$git_status" = '' ]
then
echo Yes.
else
echo No.
append_emsg "Git working directory is not clean (git status --porcelain)."
fi
unset git_status
verfail ()
{
echo No.
@ -77,38 +88,6 @@ case $VERSION in
*) verfail "'$VERSION' is a single number" ;;
esac
echo -n "Checking that LIBNOTMUCH version macros & variables match ... "
# lib/notmuch.h
LIBNOTMUCH_MAJOR_VERSION=broken
LIBNOTMUCH_MINOR_VERSION=broken
LIBNOTMUCH_MICRO_VERSION=broken
# lib/Makefile.local
LIBNOTMUCH_VERSION_MAJOR=borken
LIBNOTMUCH_VERSION_MINOR=borken
LIBNOTMUCH_VERSION_RELEASE=borken
eval `awk 'NF == 3 && $1 == "#define" && $2 ~ /^LIBNOTMUCH_[A-Z]+_VERSION$/ \
&& $3 ~ /^[0-9]+$/ { print $2 "=" $3 }' lib/notmuch.h`
eval `awk 'NF == 3 && $1 ~ /^LIBNOTMUCH_VERSION_[A-Z]+$/ && $2 == "=" \
&& $3 ~ /^[0-9]+$/ { print $1 "=" $3 }' lib/Makefile.local`
check_version_component ()
{
eval local v1=\$LIBNOTMUCH_$1_VERSION
eval local v2=\$LIBNOTMUCH_VERSION_$2
if [ $v1 != $v2 ]
then append_emsg "LIBNOTMUCH_$1_VERSION ($v1) does not equal LIBNOTMUCH_VERSION_$2 ($v2)"
fi
}
old_emsg_count=$emsg_count
check_version_component MAJOR MAJOR
check_version_component MINOR MINOR
check_version_component MICRO RELEASE
[ $old_emsg_count = $emsg_count ] && echo Yes. || echo No.
echo -n "Checking that this is Debian package for notmuch... "
read deb_notmuch deb_version rest < debian/changelog
if [ "$deb_notmuch" = 'notmuch' ]
@ -130,7 +109,7 @@ else
fi
echo -n "Checking that python bindings version is $VERSION... "
py_version=`python -c "with open('$PV_FILE') as vf: exec(vf.read()); print __VERSION__"`
py_version=`python -c "with open('$PV_FILE') as vf: exec(vf.read()); print(__VERSION__)"`
if [ "$py_version" = "$VERSION" ]
then
echo Yes.

View file

@ -7,7 +7,7 @@ SPHINXOPTS := -q
SPHINXBUILD = sphinx-build
DOCBUILDDIR := $(dir)/_build
mkdocdeps := python $(srcdir)/$(dir)/mkdocdeps.py
mkdocdeps := $(PYTHON) $(srcdir)/$(dir)/mkdocdeps.py
# Internal variables.
ALLSPHINXOPTS := -d $(DOCBUILDDIR)/doctrees $(SPHINXOPTS) $(srcdir)/$(dir)

View file

@ -12,7 +12,7 @@ master_doc = 'index'
# General information about the project.
project = u'notmuch'
copyright = u'2014, Carl Worth and many others'
copyright = u'2009-2015, Carl Worth and many others'
location = os.path.dirname(__file__)

View file

@ -74,7 +74,7 @@ STRICT_PROTO_MATCHING = NO
GENERATE_TODOLIST = NO
GENERATE_TESTLIST = NO
GENERATE_BUGLIST = NO
GENERATE_DEPRECATEDLIST= NO
GENERATE_DEPRECATEDLIST= YES
ENABLED_SECTIONS =
MAX_INITIALIZER_LINES = 30
SHOW_USED_FILES = NO

View file

@ -55,6 +55,28 @@ Supported options for **address** include
Note: With this option, addresses are printed only after
the whole search is finished. This may take long time.
``--deduplicate=(no|mailbox|address)``
Control the deduplication of results.
**no**
Output all occurences of addresses in the matching
messages. This is not applicable with --output=count.
**mailbox**
Deduplicate addresses based on the full, case sensitive
name and email address, or mailbox. This is effectively
the same as piping the --deduplicate=no output to **sort |
uniq**, except for the order of results. This is the
default.
**address**
Deduplicate addresses based on the case insensitive
address part of the mailbox. Of all the variants (with
different name or case), print the one occurring most
frequently among the matching messages. If --output=count
is specified, include all variants in the count.
``--sort=``\ (**newest-first**\ \|\ **oldest-first**)
This option can be used to present results in either
chronological order (**oldest-first**) or reverse chronological
@ -63,7 +85,9 @@ Supported options for **address** include
By default, results will be displayed in reverse chronological
order, (that is, the newest results will be displayed first).
This option is not supported with --output=count.
However, if either --output=count or --deduplicate=address is
specified, this option is ignored and the order of the results
is unspecified.
``--exclude=(true|false)``
A message is called "excluded" if it matches at least one tag in

View file

@ -47,6 +47,11 @@ Supported options for **count** include
(or threads) in the database will be output. This option is not
compatible with specifying search terms on the command line.
``--lastmod``
Append lastmod (counter for number of database updates) and UUID
to the output. lastmod values are only comparable between databases
with the same UUID.
``--input=``\ <filename>
Read input from given file, instead of from stdin. Implies
``--batch``.

View file

@ -18,10 +18,6 @@ Supported options for **notmuch-emacs-mua** include
``-h, --help``
Display help.
``--client``
Use emacsclient, rather than emacs. This will start
an emacs daemon process if necessary.
``-s, --subject=``\ <subject>
Specify the subject of the message.
@ -38,7 +34,23 @@ Supported options for **notmuch-emacs-mua** include
Specify a file to include into the body of the message.
``--no-window-system``
Even if a window system is available, use the current terminal
Even if a window system is available, use the current terminal.
``--client``
Use **emacsclient**, rather than **emacs**. For
**emacsclient** to work, you need an already running Emacs
with a server, or use ``--auto-daemon``.
``--auto-daemon``
Automatically start Emacs in daemon mode, if the Emacs server
is not running. Applicable with ``--client``. Implies
``--create-frame``.
``--create-frame``
Create a new frame instead of trying to use the current Emacs
frame. Applicable with ``--client``. This will be required
when Emacs is running (or automatically started with
``--auto-daemon``) in daemon mode.
``--print``
Output the resulting elisp to stdout instead of evaluating it.

View file

@ -39,15 +39,28 @@ OPTIONS
Supported global options for ``notmuch`` include
``--help``
Print a synopsis of available commands and exit.
``--help`` [command-name]
Print a synopsis of available commands and exit.
With an optional command name, show the man page
for that subcommand.
``--version``
Print the installed version of notmuch, and exit.
Print the installed version of notmuch, and exit.
``--config=FILE``
Specify the configuration file to use. This overrides any
configuration file specified by ${NOTMUCH\_CONFIG}.
Specify the configuration file to use. This overrides any
configuration file specified by ${NOTMUCH\_CONFIG}.
``--uuid=HEX``
Enforce that the database UUID (a unique identifier which
persists until e.g. the database is compacted)
is HEX; exit with an error if it is not. This is useful to
detect rollover in modification counts on messages. You can
find this UUID using e.g. ``notmuch count --lastmod``
All global options except ``--config`` can also be specified after the
command. For example, ``notmuch subcommand --uuid=HEX`` is
equivalent to ``notmuch --uuid=HEX subcommand``.
COMMANDS
========

View file

@ -54,6 +54,8 @@ indicate user-supplied values):
- date:<since>..<until>
- lastmod:<initial-revision>..<final-revision>
The **from:** prefix is used to match the name or address of the sender
of an email message.
@ -124,6 +126,12 @@ The time range can also be specified using timestamps with a syntax of:
Each timestamp is a number representing the number of seconds since
1970-01-01 00:00:00 UTC.
The **lastmod:** prefix can be used to restrict the result by the
database revision number of when messages were last modified (tags
were added/removed or filenames changed). This is usually used in
conjunction with the **--uuid** argument to **notmuch search**
to find messages that have changed since an earlier query.
Operators
---------
@ -270,6 +278,13 @@ In this case, <since> is taken as the earliest time it could describe
could describe (the end of yesterday). Similarly, date:january..february
matches from the beginning of January to the end of February.
date:<expr>..! can be used as a shorthand for date:<expr>..<expr>. The
expansion takes place before interpretation, and thus, for example,
date:monday..! matches from the beginning of Monday until the end of
Monday. (Note that entering date:<expr> without "..", for example
date:yesterday, won't work, as it's not interpreted as a range
expression at all. Again, use date:yesterday..!)
Currently, we do not support spaces in range expressions. You can
replace the spaces with '\_', or (in most cases) '-', or (in some cases)
leave the spaces out altogether. Examples in this man page use spaces
@ -280,11 +295,6 @@ to specify date:..<until> or date:<since>.. to not limit the start or
end time, respectively. Pre-1.2.1 Xapian does not report an error on
open ended ranges, but it does not work as expected either.
Entering date:expr without ".." (for example date:yesterday) won't work,
as it's not interpreted as a range expression at all. You can achieve
the expected result by duplicating the expr both sides of ".." (for
example date:yesterday..yesterday).
Relative date and time
----------------------

View file

@ -28,6 +28,8 @@
(declare-function notmuch-search "notmuch" (&optional query oldest-first target-thread target-line continuation))
(declare-function notmuch-poll "notmuch" ())
(declare-function notmuch-tree "notmuch-tree"
(&optional query query-context target buffer-name open-target))
(defun notmuch-saved-search-get (saved-search field)
"Get FIELD from SAVED-SEARCH.
@ -91,7 +93,11 @@ searches so they still work in customize."
(choice :tag " Sort Order"
(const :tag "Default" nil)
(const :tag "Oldest-first" oldest-first)
(const :tag "Newest-first" newest-first))))))
(const :tag "Newest-first" newest-first)))
(group :format "%v" :inline t (const :format "" :search-type)
(choice :tag " Search Type"
(const :tag "Search mode" nil)
(const :tag "Tree mode" tree))))))
(defcustom notmuch-saved-searches
`((:name "inbox" :query "tag:inbox" :key ,(kbd "i"))
@ -114,6 +120,10 @@ a plist. Supported properties are
:sort-order Specify the sort order to be used for the search.
Possible values are 'oldest-first 'newest-first or
nil. Nil means use the default sort order.
:search-type Specify whether to run the search in search-mode
or tree mode. Set to 'tree to specify tree
mode, set to nil (or anything except tree) to
specify search mode.
Other accepted forms are a cons cell of the form (NAME . QUERY)
or a list of the form (NAME QUERY COUNT-QUERY)."
@ -425,10 +435,13 @@ diagonal."
append (notmuch-hello-reflect-generate-row ncols nrows row list))))
(defun notmuch-hello-widget-search (widget &rest ignore)
(notmuch-search (widget-get widget
:notmuch-search-terms)
(widget-get widget
:notmuch-search-oldest-first)))
(if (widget-get widget :notmuch-search-type)
(notmuch-tree (widget-get widget
:notmuch-search-terms))
(notmuch-search (widget-get widget
:notmuch-search-terms)
(widget-get widget
:notmuch-search-oldest-first))))
(defun notmuch-saved-search-count (search)
(car (process-lines notmuch-command "count" search)))
@ -564,6 +577,7 @@ with `notmuch-hello-query-counts'."
(newest-first nil)
(oldest-first t)
(otherwise notmuch-search-oldest-first)))
(search-type (eq (plist-get elem :search-type) 'tree))
(msg-count (plist-get elem :count)))
(widget-insert (format "%8s "
(notmuch-hello-nice-number msg-count)))
@ -571,6 +585,7 @@ with `notmuch-hello-query-counts'."
:notify #'notmuch-hello-widget-search
:notmuch-search-terms query
:notmuch-search-oldest-first oldest-first
:notmuch-search-type search-type
name)
(setq column-indent
(1+ (max 0 (- column-width (length name)))))))
@ -628,7 +643,7 @@ with `notmuch-hello-query-counts'."
(defun notmuch-hello-versions ()
"Display the notmuch version(s)"
(interactive)
(let ((notmuch-cli-version (notmuch-version)))
(let ((notmuch-cli-version (notmuch-cli-version)))
(message "notmuch version %s"
(if (string= notmuch-emacs-version notmuch-cli-version)
notmuch-cli-version

View file

@ -54,7 +54,9 @@ fast way to jump to a saved search from anywhere in Notmuch."
(oldest-first t)
(otherwise (default-value 'notmuch-search-oldest-first)))))
(push (list key name
`(lambda () (notmuch-search ',query ',oldest-first)))
(if (eq (plist-get saved-search :search-type) 'tree)
`(lambda () (notmuch-tree ',query))
`(lambda () (notmuch-search ',query ',oldest-first))))
action-map)))))
(setq action-map (nreverse action-map))

View file

@ -25,6 +25,10 @@
(require 'mm-decode)
(require 'cl)
(unless (require 'notmuch-version nil t)
(defconst notmuch-emacs-version "unknown"
"Placeholder variable when notmuch-version.el[c] is not available."))
(autoload 'notmuch-jump-search "notmuch-jump"
"Jump to a saved search by shortcut key." t)
@ -192,8 +196,8 @@ Otherwise the output will be returned"
"Perhaps you haven't run \"notmuch setup\" yet? Try running this
on the command line, and then retry your notmuch command")))
(defun notmuch-version ()
"Return a string with the notmuch version number."
(defun notmuch-cli-version ()
"Return a string with the notmuch cli command version number."
(let ((long-string
;; Trim off the trailing newline.
(substring (notmuch-command-to-string "--version") 0 -1)))

View file

@ -59,23 +59,19 @@ yet when sending a mail."
:require 'notmuch-fcc-initialization
:group 'notmuch-send)
(defun notmuch-fcc-initialization ()
"If notmuch-fcc-directories is set,
hook them into the message-fcc-handler-function"
;; Set up the message-fcc-handler to move mails to the maildir in Fcc
;; The parameter is set to mark messages as "seen"
(setq message-fcc-handler-function
(lambda (destdir)
(notmuch-maildir-fcc-write-buffer-to-maildir destdir t)))
;; add a hook to actually insert the Fcc header when sending
(add-hook 'message-header-setup-hook 'notmuch-fcc-header-setup))
(defun notmuch-fcc-handler (destdir)
"Write buffer to `destdir', marking it as sent
Intended to be dynamically bound to `message-fcc-handler-function'"
(notmuch-maildir-fcc-write-buffer-to-maildir destdir t))
(defun notmuch-fcc-header-setup ()
"Add an Fcc header to the current message buffer.
Can be added to `message-send-hook' and will set the Fcc header
based on the values of `notmuch-fcc-dirs'. An existing Fcc header
will NOT be removed or replaced."
Sets the Fcc header based on the values of `notmuch-fcc-dirs'.
Originally intended to be use a hook function, but now called directly
by notmuch-mua-mail"
(let ((subdir
(cond
@ -213,6 +209,5 @@ return t if successful, and nil otherwise."
(delete-file (concat destdir "/tmp/" msg-id))))
t)))
(notmuch-fcc-initialization)
(provide 'notmuch-maildir-fcc)

View file

@ -50,7 +50,7 @@ window/frame that will be destroyed when the buffer is killed.
You may want to customize `message-kill-buffer-on-exit'
accordingly."
(when (< emacs-major-version 24)
" Due to a known bug in Emacs 23, you should not set
" Due to a known bug in Emacs 23, you should not set
this to `new-window' if `message-kill-buffer-on-exit' is
disabled: this would result in an incorrect behavior."))
:group 'notmuch-send
@ -118,7 +118,10 @@ Note that these functions use `mail-citation-hook' if that is non-nil."
(defun notmuch-mua-user-agent-notmuch ()
"Generate a `User-Agent:' string suitable for notmuch."
(concat "Notmuch/" (notmuch-version) " (http://notmuchmail.org)"))
(let ((notmuch-version (if (string= notmuch-emacs-version "unknown")
(notmuch-cli-version)
notmuch-emacs-version)))
(concat "Notmuch/" notmuch-version " (http://notmuchmail.org)")))
(defun notmuch-mua-user-agent-emacs ()
"Generate a `User-Agent:' string suitable for notmuch."
@ -265,6 +268,12 @@ Note that these functions use `mail-citation-hook' if that is non-nil."
(message-goto-body)
(set-buffer-modified-p nil))
(define-derived-mode notmuch-message-mode message-mode "Message[Notmuch]"
"Notmuch message composition mode. Mostly like `message-mode'")
(define-key notmuch-message-mode-map (kbd "C-c C-c") #'notmuch-mua-send-and-exit)
(define-key notmuch-message-mode-map (kbd "C-c C-s") #'notmuch-mua-send)
(defun notmuch-mua-mail (&optional to subject other-headers &rest other-args)
"Invoke the notmuch mail composition window.
@ -281,6 +290,8 @@ OTHER-ARGS are passed through to `message-mail'."
(notmuch-user-name) " <" (notmuch-user-primary-email) ">")) other-headers))
(apply #'message-mail to subject other-headers other-args)
(notmuch-message-mode)
(notmuch-fcc-header-setup)
(message-sort-headers)
(message-hide-headers)
(set-buffer-modified-p nil)
@ -394,7 +405,13 @@ will be addressed to all recipients of the source message."
(defun notmuch-mua-send-and-exit (&optional arg)
(interactive "P")
(message-send-and-exit arg))
(let ((message-fcc-handler-function #'notmuch-fcc-handler))
(message-send-and-exit arg)))
(defun notmuch-mua-send (&optional arg)
(interactive "P")
(let ((message-fcc-handler-function #'notmuch-fcc-handler))
(message-send arg)))
(defun notmuch-mua-kill-buffer ()
(interactive)

View file

@ -47,6 +47,7 @@
(declare-function notmuch-tree "notmuch-tree"
(&optional query query-context target buffer-name open-target))
(declare-function notmuch-tree-get-message-properties "notmuch-tree" nil)
(declare-function notmuch-read-query "notmuch" (prompt))
(defcustom notmuch-message-headers '("Subject" "To" "Cc" "Date")
"Headers that should be shown in a message, in this order.
@ -99,7 +100,7 @@ visible for any given message."
:group 'notmuch-show
:group 'notmuch-hooks)
(defcustom notmuch-show-max-text-part-size 10000
(defcustom notmuch-show-max-text-part-size 100000
"Maximum size of a text part to be shown by default in characters.
Set to 0 to show the part regardless of size."
@ -1281,6 +1282,16 @@ This includes:
")")
notmuch-show-thread-id))
(defun notmuch-show-goto-message (msg-id)
"Go to message with msg-id."
(goto-char (point-min))
(unless (loop if (string= msg-id (notmuch-show-get-message-id))
return t
until (not (notmuch-show-goto-message-next)))
(goto-char (point-min))
(message "Message-id not found."))
(notmuch-show-message-adjust))
(defun notmuch-show-apply-state (state)
"Apply STATE to the current buffer.
@ -1298,13 +1309,7 @@ This includes:
until (not (notmuch-show-goto-message-next)))
;; Go to the previously open message.
(goto-char (point-min))
(unless (loop if (string= current (notmuch-show-get-message-id))
return t
until (not (notmuch-show-goto-message-next)))
(goto-char (point-min))
(message "Previously current message not found."))
(notmuch-show-message-adjust)))
(notmuch-show-goto-message current)))
(defun notmuch-show-refresh-view (&optional reset-state)
"Refresh the current view.
@ -1368,6 +1373,7 @@ reset based on the original query."
(define-key map (kbd "<backtab>") 'notmuch-show-previous-button)
(define-key map (kbd "TAB") 'notmuch-show-next-button)
(define-key map "f" 'notmuch-show-forward-message)
(define-key map "l" 'notmuch-show-filter-thread)
(define-key map "r" 'notmuch-show-reply-sender)
(define-key map "R" 'notmuch-show-reply)
(define-key map "|" 'notmuch-show-pipe-message)
@ -1656,6 +1662,16 @@ user decision and we should not override it."
(save-excursion
(funcall notmuch-show-mark-read-function (window-start) (window-end)))))
(defun notmuch-show-filter-thread (query)
"Filter or LIMIT the current thread based on a new query string.
Reshows the current thread with matches defined by the new query-string."
(interactive (list (notmuch-read-query "Filter thread: ")))
(let ((msg-id (notmuch-show-get-message-id)))
(setq notmuch-show-query-context (if (string= query "") nil query))
(notmuch-show-refresh-view t)
(notmuch-show-goto-message msg-id)))
;; Functions for getting attributes of several messages in the current
;; thread.

View file

@ -265,7 +265,7 @@ changed (the normal case) are shown using formats from
(defcustom notmuch-before-tag-hook nil
"Hooks that are run before tags of a message are modified.
'tags' will contain the tags that are about to be added or removed as
'tag-changes' will contain the tags that are about to be added or removed as
a list of strings of the form \"+TAG\" or \"-TAG\".
'query' will be a string containing the search query that determines
the messages that are about to be tagged"
@ -277,7 +277,7 @@ the messages that are about to be tagged"
(defcustom notmuch-after-tag-hook nil
"Hooks that are run after tags of a message are modified.
'tags' will contain the tags that were added or removed as
'tag-changes' will contain the tags that were added or removed as
a list of strings of the form \"+TAG\" or \"-TAG\".
'query' will be a string containing the search query that determines
the messages that were tagged"

View file

@ -240,6 +240,8 @@ FUNC."
;; Override because we want to close message pane first.
(define-key map [remap notmuch-mua-new-mail] (notmuch-tree-close-message-pane-and #'notmuch-mua-new-mail))
(define-key map "S" 'notmuch-search-from-tree-current-query)
;; these use notmuch-show functions directly
(define-key map "|" 'notmuch-show-pipe-message)
(define-key map "w" 'notmuch-show-save-attachments)
@ -402,6 +404,12 @@ Does NOT change the database."
(notmuch-tree-close-message-window)
(notmuch-tree query)))
(defun notmuch-search-from-tree-current-query ()
"Call notmuch search with the current query"
(interactive)
(notmuch-tree-close-message-window)
(notmuch-search (notmuch-tree-get-query)))
(defun notmuch-tree-message-window-kill-hook ()
"Close the message pane when exiting the show buffer."
(let ((buffer (current-buffer)))
@ -867,6 +875,11 @@ the same as for the function notmuch-tree."
(setq notmuch-tree-query-context query-context)
(setq notmuch-tree-target-msg target)
(setq notmuch-tree-open-target open-target)
;; Set the default value for `notmuch-show-process-crypto' in this
;; buffer. Although we don't use this some of the functions we call
;; (such as reply) do. It is a buffer local variable so setting it
;; will not affect genuine show buffers.
(setq notmuch-show-process-crypto notmuch-crypto-process-mime)
(erase-buffer)
(goto-char (point-min))

View file

@ -61,10 +61,6 @@
(require 'notmuch-message)
(require 'notmuch-parser)
(unless (require 'notmuch-version nil t)
(defconst notmuch-emacs-version "unknown"
"Placeholder variable when notmuch-version.el[c] is not available."))
(defcustom notmuch-search-result-format
`(("date" . "%12s ")
("count" . "%-7s ")
@ -181,6 +177,7 @@ there will be called at other points of notmuch execution."
(defvar notmuch-search-stash-map
(let ((map (make-sparse-keymap)))
(define-key map "i" 'notmuch-search-stash-thread-id)
(define-key map "q" 'notmuch-stash-query)
(define-key map "?" 'notmuch-subkeymap-help)
map)
"Submap for stash commands")
@ -191,6 +188,11 @@ there will be called at other points of notmuch execution."
(interactive)
(notmuch-common-do-stash (notmuch-search-find-thread-id)))
(defun notmuch-stash-query ()
"Copy current query to kill-ring."
(interactive)
(notmuch-common-do-stash (notmuch-search-get-query)))
(defvar notmuch-search-query-string)
(defvar notmuch-search-target-thread)
(defvar notmuch-search-target-line)
@ -855,13 +857,15 @@ See `notmuch-tag' for information on the format of TAG-CHANGES."
"Read a notmuch-query from the minibuffer with completion.
PROMPT is the string to prompt with."
(lexical-let
((completions
(append (list "folder:" "path:" "thread:" "id:" "date:" "from:" "to:"
"subject:" "attachment:" "mimetype:")
(mapcar (lambda (tag)
(concat "tag:" (notmuch-escape-boolean-term tag)))
(process-lines notmuch-command "search" "--output=tags" "*")))))
(lexical-let*
((all-tags
(mapcar (lambda (tag) (notmuch-escape-boolean-term tag))
(process-lines notmuch-command "search" "--output=tags" "*")))
(completions
(append (list "folder:" "path:" "thread:" "id:" "date:" "from:" "to:"
"subject:" "attachment:" "mimetype:")
(mapcar (lambda (tag) (concat "tag:" tag)) all-tags)
(mapcar (lambda (tag) (concat "is:" tag)) all-tags))))
(let ((keymap (copy-keymap minibuffer-local-map))
(current-query (case major-mode
(notmuch-search-mode (notmuch-search-get-query))
@ -974,18 +978,28 @@ default sort order is defined by `notmuch-search-oldest-first'."
(set 'notmuch-search-oldest-first (not notmuch-search-oldest-first))
(notmuch-search-refresh-view))
(defun notmuch-group-disjunctive-query-string (query-string)
"Group query if it contains a complex expression.
Enclose QUERY-STRING in parentheses if it matches
`notmuch-search-disjunctive-regexp'."
(if (string-match-p notmuch-search-disjunctive-regexp query-string)
(concat "( " query-string " )")
query-string))
(defun notmuch-search-filter (query)
"Filter the current search results based on an additional query string.
Runs a new search matching only messages that match both the
current search results AND the additional query string provided."
(interactive (list (notmuch-read-query "Filter search: ")))
(let ((grouped-query (if (string-match-p notmuch-search-disjunctive-regexp query)
(concat "( " query " )")
query)))
(notmuch-search (if (string= notmuch-search-query-string "*")
(let ((grouped-query (notmuch-group-disjunctive-query-string query))
(grouped-original-query (notmuch-group-disjunctive-query-string
notmuch-search-query-string)))
(notmuch-search (if (string= grouped-original-query "*")
grouped-query
(concat notmuch-search-query-string " and " grouped-query)) notmuch-search-oldest-first)))
(concat grouped-original-query " and " grouped-query))
notmuch-search-oldest-first)))
(defun notmuch-search-filter-by-tag (tag)
"Filter the current search results based on a single tag.

View file

@ -1,26 +1,5 @@
# -*- makefile -*-
# The major version of the library interface. This will control the soname.
# As such, this number must be incremented for any incompatible change to
# the library interface, (such as the deletion of an API or a major
# semantic change that breaks formerly functioning code).
#
LIBNOTMUCH_VERSION_MAJOR = 4
# The minor version of the library interface. This should be incremented at
# the time of release for any additions to the library interface,
# (and when it is incremented, the release version of the library should
# be reset to 0).
LIBNOTMUCH_VERSION_MINOR = 2
# The release version the library interface. This should be incremented at
# the time of release if there have been no changes to the interface, (but
# simply compatible changes to the implementation).
LIBNOTMUCH_VERSION_RELEASE = 0
# Note: Don't forget to change the VERSION macros in notmuch.h when
# any of the above change.
ifeq ($(PLATFORM),MACOSX)
LIBRARY_SUFFIX = dylib
# On OS X, library version numbers go before suffix.
@ -33,7 +12,7 @@ LIBRARY_SUFFIX = so
LINKER_NAME = libnotmuch.$(LIBRARY_SUFFIX)
SONAME = $(LINKER_NAME).$(LIBNOTMUCH_VERSION_MAJOR)
LIBNAME = $(SONAME).$(LIBNOTMUCH_VERSION_MINOR).$(LIBNOTMUCH_VERSION_RELEASE)
LIBRARY_LINK_FLAG = -shared -Wl,--version-script=notmuch.sym,-soname=$(SONAME) -Wl,--no-undefined
LIBRARY_LINK_FLAG = -shared -Wl,--version-script=notmuch.sym,-soname=$(SONAME) $(NO_UNDEFINED_LDFLAGS)
ifeq ($(PLATFORM),OPENBSD)
LIBRARY_LINK_FLAG += -lc
endif

View file

@ -100,6 +100,12 @@ enum _notmuch_features {
*
* Introduced: version 3. */
NOTMUCH_FEATURE_INDEXED_MIMETYPES = 1 << 5,
/* If set, messages store the revision number of the last
* modification in NOTMUCH_VALUE_LAST_MOD.
*
* Introduced: version 3. */
NOTMUCH_FEATURE_LAST_MOD = 1 << 6,
};
/* In C++, a named enum is its own type, so define bitwise operators
@ -145,6 +151,8 @@ struct _notmuch_database {
notmuch_database_mode_t mode;
int atomic_nesting;
/* TRUE if changes have been made in this atomic section */
notmuch_bool_t atomic_dirty;
Xapian::Database *xapian_db;
/* Bit mask of features used by this database. This is a
@ -158,10 +166,17 @@ struct _notmuch_database {
* next library call. May be NULL */
char *status_string;
/* Highest committed revision number. Modifications are recorded
* under a higher revision number, which can be generated with
* notmuch_database_new_revision. */
unsigned long revision;
const char *uuid;
Xapian::QueryParser *query_parser;
Xapian::TermGenerator *term_gen;
Xapian::ValueRangeProcessor *value_range_processor;
Xapian::ValueRangeProcessor *date_range_processor;
Xapian::ValueRangeProcessor *last_mod_range_processor;
};
/* Prior to database version 3, features were implied by the database
@ -179,7 +194,8 @@ struct _notmuch_database {
* will have it). */
#define NOTMUCH_FEATURES_CURRENT \
(NOTMUCH_FEATURE_FILE_TERMS | NOTMUCH_FEATURE_DIRECTORY_DOCS | \
NOTMUCH_FEATURE_BOOL_FOLDER | NOTMUCH_FEATURE_GHOSTS)
NOTMUCH_FEATURE_BOOL_FOLDER | NOTMUCH_FEATURE_GHOSTS | \
NOTMUCH_FEATURE_LAST_MOD)
/* Return the list of terms from the given iterator matching a prefix.
* The prefix will be stripped from the strings in the returned list.

View file

@ -101,6 +101,9 @@ typedef struct {
*
* SUBJECT: The value of the "Subject" header
*
* LAST_MOD: The revision number as of the last tag or
* filename change.
*
* In addition, terms from the content of the message are added with
* "from", "to", "attachment", and "subject" prefixes for use by the
* user in searching. Similarly, terms from the path of the mail
@ -310,6 +313,8 @@ static const struct {
* them. */
{ NOTMUCH_FEATURE_INDEXED_MIMETYPES,
"indexed MIME types", "w"},
{ NOTMUCH_FEATURE_LAST_MOD,
"modification tracking", "w"},
};
const char *
@ -342,6 +347,8 @@ notmuch_status_to_string (notmuch_status_t status)
return "Unsupported operation";
case NOTMUCH_STATUS_UPGRADE_REQUIRED:
return "Operation requires a database upgrade";
case NOTMUCH_STATUS_PATH_ERROR:
return "Path supplied is illegal for this function";
default:
case NOTMUCH_STATUS_LAST_STATUS:
return "Unknown error status value";
@ -657,6 +664,12 @@ notmuch_database_create_verbose (const char *path,
goto DONE;
}
if (path[0] != '/') {
message = strdup ("Error: Database path must be absolute.\n");
status = NOTMUCH_STATUS_PATH_ERROR;
goto DONE;
}
err = stat (path, &st);
if (err) {
IGNORE_RESULT (asprintf (&message, "Error: Cannot create database at %s: %s.\n",
@ -729,6 +742,23 @@ _notmuch_database_ensure_writable (notmuch_database_t *notmuch)
return NOTMUCH_STATUS_SUCCESS;
}
/* Allocate a revision number for the next change. */
unsigned long
_notmuch_database_new_revision (notmuch_database_t *notmuch)
{
unsigned long new_revision = notmuch->revision + 1;
/* If we're in an atomic section, hold off on updating the
* committed revision number until we commit the atomic section.
*/
if (notmuch->atomic_nesting)
notmuch->atomic_dirty = TRUE;
else
notmuch->revision = new_revision;
return new_revision;
}
/* Parse a database features string from the given database version.
* Returns the feature bit set.
*
@ -847,6 +877,12 @@ notmuch_database_open_verbose (const char *path,
goto DONE;
}
if (path[0] != '/') {
message = strdup ("Error: Database path must be absolute.\n");
status = NOTMUCH_STATUS_PATH_ERROR;
goto DONE;
}
if (! (notmuch_path = talloc_asprintf (local, "%s/%s", path, ".notmuch"))) {
message = strdup ("Out of memory\n");
status = NOTMUCH_STATUS_OUT_OF_MEMORY;
@ -890,6 +926,7 @@ notmuch_database_open_verbose (const char *path,
notmuch->atomic_nesting = 0;
try {
string last_thread_id;
string last_mod;
if (mode == NOTMUCH_DATABASE_MODE_READ_WRITE) {
notmuch->xapian_db = new Xapian::WritableDatabase (xapian_path,
@ -948,11 +985,22 @@ notmuch_database_open_verbose (const char *path,
INTERNAL_ERROR ("Malformed database last_thread_id: %s", str);
}
/* Get current highest revision number. */
last_mod = notmuch->xapian_db->get_value_upper_bound (
NOTMUCH_VALUE_LAST_MOD);
if (last_mod.empty ())
notmuch->revision = 0;
else
notmuch->revision = Xapian::sortable_unserialise (last_mod);
notmuch->uuid = talloc_strdup (
notmuch, notmuch->xapian_db->get_uuid ().c_str ());
notmuch->query_parser = new Xapian::QueryParser;
notmuch->term_gen = new Xapian::TermGenerator;
notmuch->term_gen->set_stemmer (Xapian::Stem ("english"));
notmuch->value_range_processor = new Xapian::NumberValueRangeProcessor (NOTMUCH_VALUE_TIMESTAMP);
notmuch->date_range_processor = new ParseTimeValueRangeProcessor (NOTMUCH_VALUE_TIMESTAMP);
notmuch->last_mod_range_processor = new Xapian::NumberValueRangeProcessor (NOTMUCH_VALUE_LAST_MOD, "lastmod:");
notmuch->query_parser->set_default_op (Xapian::Query::OP_AND);
notmuch->query_parser->set_database (*notmuch->xapian_db);
@ -960,6 +1008,7 @@ notmuch_database_open_verbose (const char *path,
notmuch->query_parser->set_stemming_strategy (Xapian::QueryParser::STEM_SOME);
notmuch->query_parser->add_valuerangeprocessor (notmuch->value_range_processor);
notmuch->query_parser->add_valuerangeprocessor (notmuch->date_range_processor);
notmuch->query_parser->add_valuerangeprocessor (notmuch->last_mod_range_processor);
for (i = 0; i < ARRAY_SIZE (BOOLEAN_PREFIX_EXTERNAL); i++) {
prefix_t *prefix = &BOOLEAN_PREFIX_EXTERNAL[i];
@ -1038,6 +1087,8 @@ notmuch_database_close (notmuch_database_t *notmuch)
notmuch->value_range_processor = NULL;
delete notmuch->date_range_processor;
notmuch->date_range_processor = NULL;
delete notmuch->last_mod_range_processor;
notmuch->last_mod_range_processor = NULL;
return status;
}
@ -1321,6 +1372,7 @@ notmuch_database_upgrade (notmuch_database_t *notmuch,
enum _notmuch_features target_features, new_features;
notmuch_status_t status;
notmuch_private_status_t private_status;
notmuch_query_t *query = NULL;
unsigned int count = 0, total = 0;
status = _notmuch_database_ensure_writable (notmuch);
@ -1336,7 +1388,7 @@ notmuch_database_upgrade (notmuch_database_t *notmuch,
return NOTMUCH_STATUS_SUCCESS;
if (progress_notify) {
/* Setup our handler for SIGALRM */
/* Set up our handler for SIGALRM */
memset (&action, 0, sizeof (struct sigaction));
action.sa_handler = handle_sigalrm;
sigemptyset (&action.sa_mask);
@ -1355,10 +1407,18 @@ notmuch_database_upgrade (notmuch_database_t *notmuch,
/* Figure out how much total work we need to do. */
if (new_features &
(NOTMUCH_FEATURE_FILE_TERMS | NOTMUCH_FEATURE_BOOL_FOLDER)) {
notmuch_query_t *query = notmuch_query_create (notmuch, "");
total += notmuch_query_count_messages (query);
(NOTMUCH_FEATURE_FILE_TERMS | NOTMUCH_FEATURE_BOOL_FOLDER |
NOTMUCH_FEATURE_LAST_MOD)) {
query = notmuch_query_create (notmuch, "");
unsigned msg_count;
status = notmuch_query_count_messages_st (query, &msg_count);
if (status)
goto DONE;
total += msg_count;
notmuch_query_destroy (query);
query = NULL;
}
if (new_features & NOTMUCH_FEATURE_DIRECTORY_DOCS) {
t_end = db->allterms_end ("XTIMESTAMP");
@ -1382,13 +1442,18 @@ notmuch_database_upgrade (notmuch_database_t *notmuch,
/* Perform per-message upgrades. */
if (new_features &
(NOTMUCH_FEATURE_FILE_TERMS | NOTMUCH_FEATURE_BOOL_FOLDER)) {
notmuch_query_t *query = notmuch_query_create (notmuch, "");
(NOTMUCH_FEATURE_FILE_TERMS | NOTMUCH_FEATURE_BOOL_FOLDER |
NOTMUCH_FEATURE_LAST_MOD)) {
notmuch_messages_t *messages;
notmuch_message_t *message;
char *filename;
for (messages = notmuch_query_search_messages (query);
query = notmuch_query_create (notmuch, "");
status = notmuch_query_search_messages_st (query, &messages);
if (status)
goto DONE;
for (;
notmuch_messages_valid (messages);
notmuch_messages_move_to_next (messages))
{
@ -1419,6 +1484,14 @@ notmuch_database_upgrade (notmuch_database_t *notmuch,
if (new_features & NOTMUCH_FEATURE_BOOL_FOLDER)
_notmuch_message_upgrade_folder (message);
/* Prior to NOTMUCH_FEATURE_LAST_MOD, messages did not
* track modification revisions. Give all messages the
* next available revision; since we just started tracking
* revisions for this database, that will be 1.
*/
if (new_features & NOTMUCH_FEATURE_LAST_MOD)
_notmuch_message_upgrade_last_mod (message);
_notmuch_message_sync (message);
notmuch_message_destroy (message);
@ -1427,6 +1500,7 @@ notmuch_database_upgrade (notmuch_database_t *notmuch,
}
notmuch_query_destroy (query);
query = NULL;
}
/* Perform per-directory upgrades. */
@ -1547,6 +1621,9 @@ notmuch_database_upgrade (notmuch_database_t *notmuch,
sigaction (SIGALRM, &action, NULL);
}
if (query)
notmuch_query_destroy (query);
talloc_free (local);
return status;
}
@ -1601,11 +1678,25 @@ notmuch_database_end_atomic (notmuch_database_t *notmuch)
return NOTMUCH_STATUS_XAPIAN_EXCEPTION;
}
if (notmuch->atomic_dirty) {
++notmuch->revision;
notmuch->atomic_dirty = FALSE;
}
DONE:
notmuch->atomic_nesting--;
return NOTMUCH_STATUS_SUCCESS;
}
unsigned long
notmuch_database_get_revision (notmuch_database_t *notmuch,
const char **uuid)
{
if (uuid)
*uuid = notmuch->uuid;
return notmuch->revision;
}
/* We allow the user to use arbitrarily long paths for directories. But
* we have a term-length limit. So if we exceed that, we'll use the
* SHA-1 of the path for the database term.
@ -2576,7 +2667,7 @@ notmuch_database_get_all_tags (notmuch_database_t *db)
}
const char *
notmuch_database_status_string (notmuch_database_t *notmuch)
notmuch_database_status_string (const notmuch_database_t *notmuch)
{
return notmuch->status_string;
}

View file

@ -281,6 +281,31 @@ notmuch_directory_get_child_directories (notmuch_directory_t *directory)
return child_directories;
}
notmuch_status_t
notmuch_directory_delete (notmuch_directory_t *directory)
{
notmuch_status_t status;
Xapian::WritableDatabase *db;
status = _notmuch_database_ensure_writable (directory->notmuch);
if (status)
return status;
try {
db = static_cast <Xapian::WritableDatabase *> (directory->notmuch->xapian_db);
db->delete_document (directory->document_id);
} catch (const Xapian::Error &error) {
_notmuch_database_log (directory->notmuch,
"A Xapian exception occurred deleting directory entry: %s.\n",
error.get_msg().c_str());
directory->notmuch->exception_reported = TRUE;
status = NOTMUCH_STATUS_XAPIAN_EXCEPTION;
}
notmuch_directory_destroy (directory);
return NOTMUCH_STATUS_SUCCESS;
}
void
notmuch_directory_destroy (notmuch_directory_t *directory)
{

View file

@ -1,3 +1,4 @@
set -eu
# we go through a bit of work to get the unmangled names of the
# typeinfo symbols because of

View file

@ -37,27 +37,6 @@ struct _notmuch_message_file {
GMimeMessage *message;
};
static int
strcase_equal (const void *a, const void *b)
{
return strcasecmp (a, b) == 0;
}
static unsigned int
strcase_hash (const void *ptr)
{
const char *s = ptr;
/* This is the djb2 hash. */
unsigned int hash = 5381;
while (s && *s) {
hash = ((hash << 5) + hash) + tolower (*s);
s++;
}
return hash;
}
static int
_notmuch_message_file_destructor (notmuch_message_file_t *message)
{

View file

@ -43,6 +43,9 @@ struct visible _notmuch_message {
* if each flag has been initialized. */
unsigned long lazy_flags;
/* Message document modified since last sync */
notmuch_bool_t modified;
Xapian::Document doc;
Xapian::termcount termpos;
};
@ -539,6 +542,7 @@ _notmuch_message_remove_terms (notmuch_message_t *message, const char *prefix)
try {
message->doc.remove_term ((*i));
message->modified = TRUE;
} catch (const Xapian::InvalidArgumentError) {
/* Ignore failure to remove non-existent term. */
}
@ -793,6 +797,7 @@ void
_notmuch_message_clear_data (notmuch_message_t *message)
{
message->doc.set_data ("");
message->modified = TRUE;
}
static void
@ -990,6 +995,17 @@ _notmuch_message_set_header_values (notmuch_message_t *message,
Xapian::sortable_serialise (time_value));
message->doc.add_value (NOTMUCH_VALUE_FROM, from);
message->doc.add_value (NOTMUCH_VALUE_SUBJECT, subject);
message->modified = TRUE;
}
/* Upgrade a message to support NOTMUCH_FEATURE_LAST_MOD. The caller
* must call _notmuch_message_sync. */
void
_notmuch_message_upgrade_last_mod (notmuch_message_t *message)
{
/* _notmuch_message_sync will update the last modification
* revision; we just have to ask it to. */
message->modified = TRUE;
}
/* Synchronize changes made to message->doc out into the database. */
@ -1001,8 +1017,24 @@ _notmuch_message_sync (notmuch_message_t *message)
if (message->notmuch->mode == NOTMUCH_DATABASE_MODE_READ_ONLY)
return;
if (! message->modified)
return;
/* Update the last modification of this message. */
if (message->notmuch->features & NOTMUCH_FEATURE_LAST_MOD)
/* sortable_serialise gives a reasonably compact encoding,
* which directly translates to reduced IO when scanning the
* value stream. Since it's built for doubles, we only get 53
* effective bits, but that's still enough for the database to
* last a few centuries at 1 million revisions per second. */
message->doc.add_value (NOTMUCH_VALUE_LAST_MOD,
Xapian::sortable_serialise (
_notmuch_database_new_revision (
message->notmuch)));
db = static_cast <Xapian::WritableDatabase *> (message->notmuch->xapian_db);
db->replace_document (message->doc_id, message->doc);
message->modified = FALSE;
}
/* Delete a message document from the database. */
@ -1077,6 +1109,7 @@ _notmuch_message_add_term (notmuch_message_t *message,
return NOTMUCH_PRIVATE_STATUS_TERM_TOO_LONG;
message->doc.add_term (term, 0);
message->modified = TRUE;
talloc_free (term);
@ -1145,6 +1178,7 @@ _notmuch_message_remove_term (notmuch_message_t *message,
try {
message->doc.remove_term (term);
message->modified = TRUE;
} catch (const Xapian::InvalidArgumentError) {
/* We'll let the philosopher's try to wrestle with the
* question of whether failing to remove that which was not

View file

@ -50,6 +50,7 @@ NOTMUCH_BEGIN_DECLS
#include "xutil.h"
#include "error_util.h"
#include "string-util.h"
#pragma GCC visibility push(hidden)
@ -107,7 +108,8 @@ typedef enum {
NOTMUCH_VALUE_TIMESTAMP = 0,
NOTMUCH_VALUE_MESSAGE_ID,
NOTMUCH_VALUE_FROM,
NOTMUCH_VALUE_SUBJECT
NOTMUCH_VALUE_SUBJECT,
NOTMUCH_VALUE_LAST_MOD,
} notmuch_value_t;
/* Xapian (with flint backend) complains if we provide a term longer
@ -194,6 +196,9 @@ void
_notmuch_database_log (notmuch_database_t *notmuch,
const char *format, ...);
unsigned long
_notmuch_database_new_revision (notmuch_database_t *notmuch);
const char *
_notmuch_database_relative_path (notmuch_database_t *notmuch,
const char *path);
@ -305,6 +310,10 @@ _notmuch_message_set_header_values (notmuch_message_t *message,
const char *date,
const char *from,
const char *subject);
void
_notmuch_message_upgrade_last_mod (notmuch_message_t *message);
void
_notmuch_message_sync (notmuch_message_t *message);

View file

@ -56,9 +56,11 @@ NOTMUCH_BEGIN_DECLS
* version in Makefile.local.
*/
#define LIBNOTMUCH_MAJOR_VERSION 4
#define LIBNOTMUCH_MINOR_VERSION 2
#define LIBNOTMUCH_MINOR_VERSION 3
#define LIBNOTMUCH_MICRO_VERSION 0
#define NOTMUCH_DEPRECATED(major,minor) \
__attribute__ ((deprecated ("function deprecated as of libnotmuch " #major "." #minor)))
#endif /* __DOXYGEN__ */
/**
@ -163,6 +165,11 @@ typedef enum _notmuch_status {
* The operation requires a database upgrade.
*/
NOTMUCH_STATUS_UPGRADE_REQUIRED,
/**
* There is a problem with the proposed path, e.g. a relative path
* passed to a function expecting an absolute path.
*/
NOTMUCH_STATUS_PATH_ERROR,
/**
* Not an actual status value. Just a way to find out how many
* valid status values there are.
@ -306,7 +313,7 @@ notmuch_database_open_verbose (const char *path,
*
*/
const char *
notmuch_database_status_string (notmuch_database_t *notmuch);
notmuch_database_status_string (const notmuch_database_t *notmuch);
/**
* Commit changes and close the given notmuch database.
@ -460,6 +467,24 @@ notmuch_database_begin_atomic (notmuch_database_t *notmuch);
notmuch_status_t
notmuch_database_end_atomic (notmuch_database_t *notmuch);
/**
* Return the committed database revision and UUID.
*
* The database revision number increases monotonically with each
* commit to the database. Hence, all messages and message changes
* committed to the database (that is, visible to readers) have a last
* modification revision <= the committed database revision. Any
* messages committed in the future will be assigned a modification
* revision > the committed database revision.
*
* The UUID is a NUL-terminated opaque string that uniquely identifies
* this database. Two revision numbers are only comparable if they
* have the same database UUID.
*/
unsigned long
notmuch_database_get_revision (notmuch_database_t *notmuch,
const char **uuid);
/**
* Retrieve a directory object from the database for 'path'.
*
@ -703,7 +728,13 @@ typedef enum {
* Return the query_string of this query. See notmuch_query_create.
*/
const char *
notmuch_query_get_query_string (notmuch_query_t *query);
notmuch_query_get_query_string (const notmuch_query_t *query);
/**
* Return the notmuch database of this query. See notmuch_query_create.
*/
notmuch_database_t *
notmuch_query_get_database (const notmuch_query_t *query);
/**
* Exclude values for notmuch_query_set_omit_excluded. The strange
@ -760,7 +791,7 @@ notmuch_query_set_sort (notmuch_query_t *query, notmuch_sort_t sort);
* notmuch_query_set_sort.
*/
notmuch_sort_t
notmuch_query_get_sort (notmuch_query_t *query);
notmuch_query_get_sort (const notmuch_query_t *query);
/**
* Add a tag that will be excluded from the query results by default.
@ -807,19 +838,25 @@ notmuch_query_add_tag_exclude (notmuch_query_t *query, const char *tag);
* notmuch_threads_destroy function, but there's no good reason
* to call it if the query is about to be destroyed).
*
* If a Xapian exception occurs this function will return NULL.
* For better error reporting, use the _st variant.
*/
notmuch_threads_t *
notmuch_query_search_threads (notmuch_query_t *query);
/**
* Like notmuch_query_search_threads, but with a status return.
* @since libnotmuch 4.2 (notmuch 0.20)
*/
notmuch_status_t
notmuch_query_search_threads_st (notmuch_query_t *query,
notmuch_threads_t **out);
/**
* Like notmuch_query_search_threads_st, but without a status return.
*
* If a Xapian exception occurs this function will return NULL.
*
* @deprecated Deprecated as of libnotmuch 4.3 (notmuch 0.21). Please
* use notmuch_query_search_threads_st instead.
*
*/
NOTMUCH_DEPRECATED(4,3)
notmuch_threads_t *
notmuch_query_search_threads (notmuch_query_t *query);
/**
* Execute a query for messages, returning a notmuch_messages_t object
* which can be used to iterate over the results. The returned
@ -858,17 +895,24 @@ notmuch_query_search_threads_st (notmuch_query_t *query,
* reason to call it if the query is about to be destroyed).
*
* If a Xapian exception occurs this function will return NULL.
* For better error reporting, use the _st variant.
*/
notmuch_messages_t *
notmuch_query_search_messages (notmuch_query_t *query);
/**
* Like notmuch_query_search_messages, but with a status return.
*
* @since libnotmuch 4.2 (notmuch 0.20)
*/
notmuch_status_t
notmuch_query_search_messages_st (notmuch_query_t *query,
notmuch_messages_t **out);
/**
* Like notmuch_query_search_messages, but without a status return.
*
* If a Xapian exception occurs this function will return NULL.
*
* @deprecated Deprecated as of libnotmuch 4.3 (notmuch 0.21). Please use
* notmuch_query_search_messages_st instead.
*
*/
NOTMUCH_DEPRECATED(4,3)
notmuch_messages_t *
notmuch_query_search_messages (notmuch_query_t *query);
/**
* Destroy a notmuch_query_t along with any associated resources.
@ -942,10 +986,28 @@ notmuch_threads_destroy (notmuch_threads_t *threads);
* This function performs a search and returns the number of matching
* messages.
*
* If a Xapian exception occurs, this function may return 0 (after
* printing a message).
* @returns
*
* NOTMUCH_STATUS_SUCCESS: query completed successfully.
*
* NOTMUCH_STATUS_XAPIAN_EXCEPTION: a Xapian exception occured. The
* value of *count is not defined.
*
* @since libnotmuch 4.3 (notmuch 0.21)
*/
unsigned
notmuch_status_t
notmuch_query_count_messages_st (notmuch_query_t *query, unsigned int *count);
/**
* like notmuch_query_count_messages_st, but without a status return.
*
* May return 0 in the case of errors.
*
* @deprecated Deprecated since libnotmuch 4.3 (notmuch 0.21). Please
* use notmuch_query_count_messages_st instead.
*/
NOTMUCH_DEPRECATED(4,3)
unsigned int
notmuch_query_count_messages (notmuch_query_t *query);
/**
@ -956,11 +1018,33 @@ notmuch_query_count_messages (notmuch_query_t *query);
* search.
*
* Note that this is a significantly heavier operation than
* notmuch_query_count_messages().
* notmuch_query_count_messages{_st}().
*
* If an error occurs, this function may return 0.
* @returns
*
* NOTMUCH_STATUS_OUT_OF_MEMORY: Memory allocation failed. The value
* of *count is not defined
* NOTMUCH_STATUS_SUCCESS: query completed successfully.
*
* NOTMUCH_STATUS_XAPIAN_EXCEPTION: a Xapian exception occured. The
* value of *count is not defined.
*
* @since libnotmuch 4.3 (notmuch 0.21)
*/
unsigned
notmuch_status_t
notmuch_query_count_threads_st (notmuch_query_t *query, unsigned *count);
/**
* like notmuch_query_count_threads, but without a status return.
*
* May return 0 in case of errors.
*
* @deprecated Deprecated as of libnotmuch 4.3 (notmuch 0.21). Please
* use notmuch_query_count_threads_st instead.
*/
NOTMUCH_DEPRECATED(4,3)
unsigned int
notmuch_query_count_threads (notmuch_query_t *query);
/**
@ -1677,6 +1761,16 @@ notmuch_directory_get_child_files (notmuch_directory_t *directory);
notmuch_filenames_t *
notmuch_directory_get_child_directories (notmuch_directory_t *directory);
/**
* Delete directory document from the database, and destroy the
* notmuch_directory_t object. Assumes any child directories and files
* have been deleted by the caller.
*
* @since libnotmuch 4.3 (notmuch 0.21)
*/
notmuch_status_t
notmuch_directory_delete (notmuch_directory_t *directory);
/**
* Destroy a notmuch_directory_t object.
*/

View file

@ -31,6 +31,7 @@ Xapian::valueno
ParseTimeValueRangeProcessor::operator() (std::string &begin, std::string &end)
{
time_t t, now;
std::string b;
/* Require date: prefix in start of the range... */
if (STRNCMP_LITERAL (begin.c_str (), PREFIX))
@ -38,6 +39,7 @@ ParseTimeValueRangeProcessor::operator() (std::string &begin, std::string &end)
/* ...and remove it. */
begin.erase (0, sizeof (PREFIX) - 1);
b = begin;
/* Use the same 'now' for begin and end. */
if (time (&now) == (time_t) -1)
@ -51,6 +53,9 @@ ParseTimeValueRangeProcessor::operator() (std::string &begin, std::string &end)
}
if (!end.empty ()) {
if (end == "!" && ! b.empty ())
end = b;
if (parse_time_string (end.c_str (), &t, &now, PARSE_TIME_ROUND_UP_INCLUSIVE))
return Xapian::BAD_VALUENO;

View file

@ -98,7 +98,7 @@ notmuch_query_create (notmuch_database_t *notmuch,
}
const char *
notmuch_query_get_query_string (notmuch_query_t *query)
notmuch_query_get_query_string (const notmuch_query_t *query)
{
return query->query_string;
}
@ -117,7 +117,7 @@ notmuch_query_set_sort (notmuch_query_t *query, notmuch_sort_t sort)
}
notmuch_sort_t
notmuch_query_get_sort (notmuch_query_t *query)
notmuch_query_get_sort (const notmuch_query_t *query)
{
return query->sort;
}
@ -541,8 +541,18 @@ notmuch_threads_destroy (notmuch_threads_t *threads)
talloc_free (threads);
}
unsigned
unsigned int
notmuch_query_count_messages (notmuch_query_t *query)
{
notmuch_status_t status;
unsigned int count;
status = notmuch_query_count_messages_st (query, &count);
return status ? 0 : count;
}
notmuch_status_t
notmuch_query_count_messages_st (notmuch_query_t *query, unsigned *count_out)
{
notmuch_database_t *notmuch = query->notmuch;
const char *query_string = query->query_string;
@ -605,31 +615,44 @@ notmuch_query_count_messages (notmuch_query_t *query)
"Query string was: %s\n",
error.get_msg().c_str(),
query->query_string);
return NOTMUCH_STATUS_XAPIAN_EXCEPTION;
}
return count;
*count_out = count;
return NOTMUCH_STATUS_SUCCESS;
}
unsigned
notmuch_query_count_threads (notmuch_query_t *query)
{
notmuch_status_t status;
unsigned int count;
status = notmuch_query_count_threads_st (query, &count);
return status ? 0 : count;
}
notmuch_status_t
notmuch_query_count_threads_st (notmuch_query_t *query, unsigned *count)
{
notmuch_messages_t *messages;
GHashTable *hash;
unsigned int count;
notmuch_sort_t sort;
notmuch_status_t ret = NOTMUCH_STATUS_SUCCESS;
sort = query->sort;
query->sort = NOTMUCH_SORT_UNSORTED;
messages = notmuch_query_search_messages (query);
ret = notmuch_query_search_messages_st (query, &messages);
if (ret)
return ret;
query->sort = sort;
if (messages == NULL)
return 0;
return NOTMUCH_STATUS_XAPIAN_EXCEPTION;
hash = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, NULL);
if (hash == NULL) {
talloc_free (messages);
return 0;
return NOTMUCH_STATUS_OUT_OF_MEMORY;
}
while (notmuch_messages_valid (messages)) {
@ -638,7 +661,7 @@ notmuch_query_count_threads (notmuch_query_t *query)
char *thread_id_copy = talloc_strdup (messages, thread_id);
if (unlikely (thread_id_copy == NULL)) {
notmuch_message_destroy (message);
count = 0;
ret = NOTMUCH_STATUS_OUT_OF_MEMORY;
goto DONE;
}
g_hash_table_insert (hash, thread_id_copy, NULL);
@ -646,11 +669,17 @@ notmuch_query_count_threads (notmuch_query_t *query)
notmuch_messages_move_to_next (messages);
}
count = g_hash_table_size (hash);
*count = g_hash_table_size (hash);
DONE:
g_hash_table_unref (hash);
talloc_free (messages);
return count;
return ret;
}
notmuch_database_t *
notmuch_query_get_database (const notmuch_query_t *query)
{
return query->notmuch;
}

View file

@ -447,6 +447,7 @@ _notmuch_thread_create (void *ctx,
notmuch_messages_t *messages;
notmuch_message_t *message;
notmuch_status_t status;
seed_message = _notmuch_message_create (local, notmuch, seed_doc_id, NULL);
if (! seed_message)
@ -504,7 +505,11 @@ _notmuch_thread_create (void *ctx,
* oldest or newest subject is desired. */
notmuch_query_set_sort (thread_id_query, NOTMUCH_SORT_OLDEST_FIRST);
for (messages = notmuch_query_search_messages (thread_id_query);
status = notmuch_query_search_messages_st (thread_id_query, &messages);
if (status)
goto DONE;
for (;
notmuch_messages_valid (messages);
notmuch_messages_move_to_next (messages))
{

View file

@ -129,8 +129,6 @@ DONE:
return status;
}
#ifdef GMIME_ATLEAST_26
/* Signature list destructor (GMime 2.6) */
static int
_signature_list_free (GMimeSignatureList **proxy)
@ -205,87 +203,6 @@ node_decrypt_and_verify (mime_node_t *node, GMimeObject *part,
g_error_free (err);
}
#else /* GMIME_ATLEAST_26 */
/* Signature validity destructor (GMime 2.4) */
static int
_signature_validity_free (GMimeSignatureValidity **proxy)
{
g_mime_signature_validity_free (*proxy);
return 0;
}
/* Set up signature validity destructor (GMime 2.4) */
static void
set_signature_validity_destructor (mime_node_t *node,
GMimeSignatureValidity *sig_validity)
{
GMimeSignatureValidity **proxy = talloc (node, GMimeSignatureValidity *);
if (proxy) {
*proxy = sig_validity;
talloc_set_destructor (proxy, _signature_validity_free);
}
}
/* Verify a signed mime node (GMime 2.4) */
static void
node_verify (mime_node_t *node, GMimeObject *part,
notmuch_crypto_context_t *cryptoctx)
{
GError *err = NULL;
GMimeSignatureValidity *sig_validity;
node->verify_attempted = TRUE;
sig_validity = g_mime_multipart_signed_verify
(GMIME_MULTIPART_SIGNED (part), cryptoctx, &err);
node->sig_validity = sig_validity;
if (sig_validity) {
set_signature_validity_destructor (node, sig_validity);
} else {
fprintf (stderr, "Failed to verify signed part: %s\n",
err ? err->message : "no error explanation given");
}
if (err)
g_error_free (err);
}
/* Decrypt and optionally verify an encrypted mime node (GMime 2.4) */
static void
node_decrypt_and_verify (mime_node_t *node, GMimeObject *part,
notmuch_crypto_context_t *cryptoctx)
{
GError *err = NULL;
GMimeMultipartEncrypted *encrypteddata = GMIME_MULTIPART_ENCRYPTED (part);
node->decrypt_attempted = TRUE;
node->decrypted_child = g_mime_multipart_encrypted_decrypt
(encrypteddata, cryptoctx, &err);
if (! node->decrypted_child) {
fprintf (stderr, "Failed to decrypt part: %s\n",
err ? err->message : "no error explanation given");
goto DONE;
}
node->decrypt_success = TRUE;
node->verify_attempted = TRUE;
/* The GMimeSignatureValidity returned here is a const, unlike the
* one returned by g_mime_multipart_signed_verify() in
* node_verify() above, so the destructor is not needed.
*/
node->sig_validity = g_mime_multipart_encrypted_get_signature_validity (encrypteddata);
if (! node->sig_validity)
fprintf (stderr, "Failed to verify encrypted signed part: %s\n",
err ? err->message : "no error explanation given");
DONE:
if (err)
g_error_free (err);
}
#endif /* GMIME_ATLEAST_26 */
static mime_node_t *
_mime_node_create (mime_node_t *parent, GMimeObject *part)
{

View file

@ -30,16 +30,7 @@
#include <gmime/gmime.h>
/* GMIME_CHECK_VERSION in gmime 2.4 is not usable from the
* preprocessor (it calls a runtime function). But since
* GMIME_MAJOR_VERSION and friends were added in gmime 2.6, we can use
* these to check the version number. */
#ifdef GMIME_MAJOR_VERSION
#define GMIME_ATLEAST_26
typedef GMimeCryptoContext notmuch_crypto_context_t;
#else
typedef GMimeCipherContext notmuch_crypto_context_t;
#endif
#include "notmuch.h"
@ -57,6 +48,7 @@ typedef GMimeCipherContext notmuch_crypto_context_t;
#include <dirent.h>
#include <errno.h>
#include <signal.h>
#include <ctype.h>
#include "talloc-extra.h"
@ -394,17 +386,10 @@ struct mime_node {
/* True if signature verification on this part was attempted. */
notmuch_bool_t verify_attempted;
#ifdef GMIME_ATLEAST_26
/* The list of signatures for signed or encrypted containers. If
* there are no signatures, this will be NULL. */
GMimeSignatureList* sig_list;
#else
/* For signed or encrypted containers, the validity of the
* signature. May be NULL if signature verification failed. If
* there are simply no signatures, this will be non-NULL with an
* empty signers list. */
const GMimeSignatureValidity *sig_validity;
#endif
/* Internal: Context inherited from the root iterator. */
struct mime_node_context *ctx;
@ -465,5 +450,22 @@ notmuch_database_dump (notmuch_database_t *notmuch,
dump_format_t output_format,
notmuch_bool_t gzip_output);
/* If status is non-zero (i.e. error) print appropriate
messages to stderr.
*/
notmuch_status_t
print_status_query (const char *loc,
const notmuch_query_t *query,
notmuch_status_t status);
#include "command-line-arguments.h"
extern char *notmuch_requested_db_uuid;
extern const notmuch_opt_desc_t notmuch_shared_options [];
void notmuch_exit_if_unmatched_db_uuid (notmuch_database_t *notmuch);
void notmuch_process_shared_options (const char* subcommand_name);
int notmuch_minimal_options (const char* subcommand_name,
int argc, char **argv);
#endif

View file

@ -38,12 +38,21 @@ notmuch_compact_command (notmuch_config_t *config, int argc, char *argv[])
notmuch_opt_desc_t options[] = {
{ NOTMUCH_OPT_STRING, &backup_path, "backup", 0, 0 },
{ NOTMUCH_OPT_BOOLEAN, &quiet, "quiet", 'q', 0 },
{ NOTMUCH_OPT_INHERIT, (void *) &notmuch_shared_options, NULL, 0, 0 },
{ 0, 0, 0, 0, 0}
};
opt_index = parse_arguments (argc, argv, options, 1);
if (opt_index < 0)
return EXIT_FAILURE;
if (notmuch_requested_db_uuid) {
fprintf (stderr, "Error: --uuid not implemented for compact\n");
return EXIT_FAILURE;
}
notmuch_process_shared_options (argv[0]);
if (! quiet)
printf ("Compacting database...\n");
ret = notmuch_database_compact (path, backup_path,

View file

@ -872,8 +872,19 @@ int
notmuch_config_command (notmuch_config_t *config, int argc, char *argv[])
{
int ret;
int opt_index;
argc--; argv++; /* skip subcommand argument */
opt_index = notmuch_minimal_options ("config", argc, argv);
if (opt_index < 0)
return EXIT_FAILURE;
if (notmuch_requested_db_uuid)
fprintf (stderr, "Warning: ignoring --uuid=%s\n",
notmuch_requested_db_uuid);
/* skip at least subcommand argument */
argc-= opt_index;
argv+= opt_index;
if (argc < 1) {
fprintf (stderr, "Error: notmuch config requires at least one argument.\n");

View file

@ -33,17 +33,19 @@ enum {
EXCLUDE_FALSE,
};
static unsigned int
/* Return the number of files matching the query, or -1 for an error */
static int
count_files (notmuch_query_t *query)
{
notmuch_messages_t *messages;
notmuch_message_t *message;
notmuch_filenames_t *filenames;
unsigned int count = 0;
notmuch_status_t status;
int count = 0;
messages = notmuch_query_search_messages (query);
if (messages == NULL)
return 0;
status = notmuch_query_search_messages_st (query, &messages);
if (print_status_query ("notmuch count", query, status))
return -1;
for (;
notmuch_messages_valid (messages);
@ -65,17 +67,24 @@ count_files (notmuch_query_t *query)
return count;
}
/* return 0 on success, -1 on failure */
static int
print_count (notmuch_database_t *notmuch, const char *query_str,
const char **exclude_tags, size_t exclude_tags_length, int output)
const char **exclude_tags, size_t exclude_tags_length, int output, int print_lastmod)
{
notmuch_query_t *query;
size_t i;
int count;
unsigned int ucount;
unsigned long revision;
const char *uuid;
int ret = 0;
notmuch_status_t status;
query = notmuch_query_create (notmuch, query_str);
if (query == NULL) {
fprintf (stderr, "Out of memory\n");
return 1;
return -1;
}
for (i = 0; i < exclude_tags_length; i++)
@ -83,34 +92,54 @@ print_count (notmuch_database_t *notmuch, const char *query_str,
switch (output) {
case OUTPUT_MESSAGES:
printf ("%u\n", notmuch_query_count_messages (query));
status = notmuch_query_count_messages_st (query, &ucount);
if (print_status_query ("notmuch count", query, status))
return -1;
printf ("%u", ucount);
break;
case OUTPUT_THREADS:
printf ("%u\n", notmuch_query_count_threads (query));
status = notmuch_query_count_threads_st (query, &ucount);
if (print_status_query ("notmuch count", query, status))
return -1;
printf ("%u", ucount);
break;
case OUTPUT_FILES:
printf ("%u\n", count_files (query));
count = count_files (query);
if (count >= 0) {
printf ("%u", count);
} else {
ret = -1;
goto DONE;
}
break;
}
if (print_lastmod) {
revision = notmuch_database_get_revision (notmuch, &uuid);
printf ("\t%s\t%lu\n", uuid, revision);
} else {
fputs ("\n", stdout);
}
DONE:
notmuch_query_destroy (query);
return 0;
return ret;
}
static int
count_file (notmuch_database_t *notmuch, FILE *input, const char **exclude_tags,
size_t exclude_tags_length, int output)
size_t exclude_tags_length, int output, int print_lastmod)
{
char *line = NULL;
ssize_t line_len;
size_t line_size;
int ret = 0;
while (!ret && (line_len = getline (&line, &line_size, input)) != -1) {
while (! ret && (line_len = getline (&line, &line_size, input)) != -1) {
chomp_newline (line);
ret = print_count (notmuch, line, exclude_tags, exclude_tags_length,
output);
output, print_lastmod);
}
if (line)
@ -130,6 +159,7 @@ notmuch_count_command (notmuch_config_t *config, int argc, char *argv[])
const char **search_exclude_tags = NULL;
size_t search_exclude_tags_length = 0;
notmuch_bool_t batch = FALSE;
notmuch_bool_t print_lastmod = FALSE;
FILE *input = stdin;
char *input_file_name = NULL;
int ret;
@ -144,8 +174,10 @@ notmuch_count_command (notmuch_config_t *config, int argc, char *argv[])
(notmuch_keyword_t []){ { "true", EXCLUDE_TRUE },
{ "false", EXCLUDE_FALSE },
{ 0, 0 } } },
{ NOTMUCH_OPT_BOOLEAN, &print_lastmod, "lastmod", 'l', 0 },
{ NOTMUCH_OPT_BOOLEAN, &batch, "batch", 0, 0 },
{ NOTMUCH_OPT_STRING, &input_file_name, "input", 'i', 0 },
{ NOTMUCH_OPT_INHERIT, (void *) &notmuch_shared_options, NULL, 0, 0 },
{ 0, 0, 0, 0, 0 }
};
@ -153,6 +185,8 @@ notmuch_count_command (notmuch_config_t *config, int argc, char *argv[])
if (opt_index < 0)
return EXIT_FAILURE;
notmuch_process_shared_options (argv[0]);
if (input_file_name) {
batch = TRUE;
input = fopen (input_file_name, "r");
@ -172,7 +206,9 @@ notmuch_count_command (notmuch_config_t *config, int argc, char *argv[])
NOTMUCH_DATABASE_MODE_READ_ONLY, &notmuch))
return EXIT_FAILURE;
query_str = query_string_from_args (config, argc-opt_index, argv+opt_index);
notmuch_exit_if_unmatched_db_uuid (notmuch);
query_str = query_string_from_args (config, argc - opt_index, argv + opt_index);
if (query_str == NULL) {
fprintf (stderr, "Out of memory.\n");
return EXIT_FAILURE;
@ -185,10 +221,10 @@ notmuch_count_command (notmuch_config_t *config, int argc, char *argv[])
if (batch)
ret = count_file (notmuch, input, search_exclude_tags,
search_exclude_tags_length, output);
search_exclude_tags_length, output, print_lastmod);
else
ret = print_count (notmuch, query_str, search_exclude_tags,
search_exclude_tags_length, output);
search_exclude_tags_length, output, print_lastmod);
notmuch_database_destroy (notmuch);

View file

@ -48,8 +48,13 @@ database_dump_file (notmuch_database_t *notmuch, gzFile output,
char *buffer = NULL;
size_t buffer_size = 0;
notmuch_status_t status;
for (messages = notmuch_query_search_messages (query);
status = notmuch_query_search_messages_st (query, &messages);
if (print_status_query ("notmuch dump", query, status))
return EXIT_FAILURE;
for (;
notmuch_messages_valid (messages);
notmuch_messages_move_to_next (messages)) {
int first = 1;
@ -215,6 +220,8 @@ notmuch_dump_command (notmuch_config_t *config, int argc, char *argv[])
NOTMUCH_DATABASE_MODE_READ_WRITE, &notmuch))
return EXIT_FAILURE;
notmuch_exit_if_unmatched_db_uuid (notmuch);
char *output_file_name = NULL;
int opt_index;
@ -228,6 +235,7 @@ notmuch_dump_command (notmuch_config_t *config, int argc, char *argv[])
{ 0, 0 } } },
{ NOTMUCH_OPT_STRING, &output_file_name, "output", 'o', 0 },
{ NOTMUCH_OPT_BOOLEAN, &gzip_output, "gzip", 'z', 0 },
{ NOTMUCH_OPT_INHERIT, (void *) &notmuch_shared_options, NULL, 0, 0 },
{ 0, 0, 0, 0, 0 }
};
@ -235,6 +243,8 @@ notmuch_dump_command (notmuch_config_t *config, int argc, char *argv[])
if (opt_index < 0)
return EXIT_FAILURE;
notmuch_process_shared_options (argv[0]);
if (opt_index < argc) {
query_str = query_string_from_args (notmuch, argc - opt_index, argv + opt_index);
if (query_str == NULL) {

View file

@ -34,12 +34,13 @@ EMACS=${EMACS-emacs}
EMACSCLIENT=${EMACSCLIENT-emacsclient}
PRINT_ONLY=
NO_WINDOW=
USE_EMACSCLIENT=
CLIENT_TYPE="-c"
AUTO_DAEMON=
CREATE_FRAME=
# The crux of it all: construct an elisp progn and eval it.
ELISP="(prog1 'done (require 'notmuch) (notmuch-mua-new-mail)"
ELISP="${ELISP} (setq message-exit-actions (list #'save-buffers-kill-terminal))"
# Short options compatible with mutt(1).
while getopts :s:c:b:i:h opt; do
@ -63,7 +64,7 @@ while getopts :s:c:b:i:h opt; do
opt=${opt%%=*}
;;
# Long options without arguments.
--help|--print|--no-window-system|--client)
--help|--print|--no-window-system|--client|--auto-daemon|--create-frame)
;;
*)
echo "$0: unknown long option ${opt}, or argument mismatch." >&2
@ -81,9 +82,6 @@ while getopts :s:c:b:i:h opt; do
--help|h)
exec man notmuch-emacs-mua
;;
--client)
USE_EMACSCLIENT="yes"
;;
--subject|s)
ELISP="${ELISP} (message-goto-subject) (insert \"${OPTARG}\")"
;;
@ -103,7 +101,17 @@ while getopts :s:c:b:i:h opt; do
PRINT_ONLY=1
;;
--no-window-system)
CLIENT_TYPE="-t"
NO_WINDOW="-nw"
;;
--client)
USE_EMACSCLIENT="yes"
;;
--auto-daemon)
AUTO_DAEMON="--alternate-editor="
CREATE_FRAME="-c"
;;
--create-frame)
CREATE_FRAME="-c"
;;
*)
# We should never end up here.
@ -122,6 +130,11 @@ for arg; do
ELISP="${ELISP} (message-goto-to) (insert \"${arg}, \")"
done
# Kill the terminal/frame if we're creating one.
if [ -z "$USE_EMACSCLIENT" -o -n "$CREATE_FRAME" -o -n "$NO_WINDOW" ]; then
ELISP="${ELISP} (setq message-exit-actions (list #'save-buffers-kill-terminal))"
fi
# End progn.
ELISP="${ELISP})"
@ -132,7 +145,7 @@ fi
if [ -n "$USE_EMACSCLIENT" ]; then
# Evaluate the progn.
exec ${EMACSCLIENT} ${CLIENT_TYPE} -a '' --eval "${ELISP}"
exec ${EMACSCLIENT} ${NO_WINDOW} ${CREATE_FRAME} ${AUTO_DAEMON} --eval "${ELISP}"
else
exec ${EMACS} --eval "${ELISP}"
exec ${EMACS} ${NO_WINDOW} --eval "${ELISP}"
fi

View file

@ -466,6 +466,7 @@ notmuch_insert_command (notmuch_config_t *config, int argc, char *argv[])
{ NOTMUCH_OPT_BOOLEAN, &create_folder, "create-folder", 0, 0 },
{ NOTMUCH_OPT_BOOLEAN, &keep, "keep", 0, 0 },
{ NOTMUCH_OPT_BOOLEAN, &no_hooks, "no-hooks", 'n', 0 },
{ NOTMUCH_OPT_INHERIT, (void *) &notmuch_shared_options, NULL, 0, 0 },
{ NOTMUCH_OPT_END, 0, 0, 0, 0 }
};
@ -473,6 +474,8 @@ notmuch_insert_command (notmuch_config_t *config, int argc, char *argv[])
if (opt_index < 0)
return EXIT_FAILURE;
notmuch_process_shared_options (argv[0]);
db_path = notmuch_config_get_database_path (config);
new_tags = notmuch_config_get_new_tags (config, &new_tags_length);
synchronize_flags = notmuch_config_get_maildir_synchronize_flags (config);
@ -521,7 +524,7 @@ notmuch_insert_command (notmuch_config_t *config, int argc, char *argv[])
return EXIT_FAILURE;
}
/* Setup our handler for SIGINT. We do not set SA_RESTART so that copying
/* Set up our handler for SIGINT. We do not set SA_RESTART so that copying
* from standard input may be interrupted. */
memset (&action, 0, sizeof (struct sigaction));
action.sa_handler = handle_sigint;
@ -533,6 +536,8 @@ notmuch_insert_command (notmuch_config_t *config, int argc, char *argv[])
NOTMUCH_DATABASE_MODE_READ_WRITE, &notmuch))
return EXIT_FAILURE;
notmuch_exit_if_unmatched_db_uuid (notmuch);
/* Write the message to the Maildir new directory. */
newpath = maildir_write_new (config, STDIN_FILENO, maildir);
if (! newpath) {

View file

@ -528,6 +528,10 @@ add_files (notmuch_database_t *notmuch,
"%s/%s", path,
notmuch_filenames_get (db_files));
if (state->debug)
printf ("(D) add_files_recursive, pass 2: queuing passed file %s for deletion from database\n",
absolute);
_filename_list_add (state->removed_files, absolute);
notmuch_filenames_move_to_next (db_files);
@ -542,6 +546,9 @@ add_files (notmuch_database_t *notmuch,
{
char *absolute = talloc_asprintf (state->removed_directories,
"%s/%s", path, filename);
if (state->debug)
printf ("(D) add_files_recursive, pass 2: queuing passed directory %s for deletion from database\n",
absolute);
_filename_list_add (state->removed_directories, absolute);
}
@ -610,6 +617,9 @@ add_files (notmuch_database_t *notmuch,
char *absolute = talloc_asprintf (state->removed_files,
"%s/%s", path,
notmuch_filenames_get (db_files));
if (state->debug)
printf ("(D) add_files_recursive, pass 3: queuing leftover file %s for deletion from database\n",
absolute);
_filename_list_add (state->removed_files, absolute);
@ -622,6 +632,10 @@ add_files (notmuch_database_t *notmuch,
"%s/%s", path,
notmuch_filenames_get (db_subdirs));
if (state->debug)
printf ("(D) add_files_recursive, pass 3: queuing leftover directory %s for deletion from database\n",
absolute);
_filename_list_add (state->removed_directories, absolute);
notmuch_filenames_move_to_next (db_subdirs);
@ -662,7 +676,7 @@ setup_progress_printing_timer (void)
struct sigaction action;
struct itimerval timerval;
/* Setup our handler for SIGALRM */
/* Set up our handler for SIGALRM */
memset (&action, 0, sizeof (struct sigaction));
action.sa_handler = handle_sigalrm;
sigemptyset (&action.sa_mask);
@ -864,8 +878,11 @@ _remove_directory (void *ctx,
goto DONE;
}
status = notmuch_directory_delete (directory);
DONE:
notmuch_directory_destroy (directory);
if (status)
notmuch_directory_destroy (directory);
return status;
}
@ -910,7 +927,11 @@ int
notmuch_new_command (notmuch_config_t *config, int argc, char *argv[])
{
notmuch_database_t *notmuch;
add_files_state_t add_files_state;
add_files_state_t add_files_state = {
.verbosity = VERBOSITY_NORMAL,
.debug = FALSE,
.output_is_a_tty = isatty (fileno (stdout)),
};
struct timeval tv_start;
int ret = 0;
struct stat st;
@ -925,15 +946,12 @@ notmuch_new_command (notmuch_config_t *config, int argc, char *argv[])
notmuch_bool_t quiet = FALSE, verbose = FALSE;
notmuch_status_t status;
add_files_state.verbosity = VERBOSITY_NORMAL;
add_files_state.debug = FALSE;
add_files_state.output_is_a_tty = isatty (fileno (stdout));
notmuch_opt_desc_t options[] = {
{ NOTMUCH_OPT_BOOLEAN, &quiet, "quiet", 'q', 0 },
{ NOTMUCH_OPT_BOOLEAN, &verbose, "verbose", 'v', 0 },
{ NOTMUCH_OPT_BOOLEAN, &add_files_state.debug, "debug", 'd', 0 },
{ NOTMUCH_OPT_BOOLEAN, &no_hooks, "no-hooks", 'n', 0 },
{ NOTMUCH_OPT_INHERIT, (void *) &notmuch_shared_options, NULL, 0, 0 },
{ 0, 0, 0, 0, 0 }
};
@ -941,6 +959,8 @@ notmuch_new_command (notmuch_config_t *config, int argc, char *argv[])
if (opt_index < 0)
return EXIT_FAILURE;
notmuch_process_shared_options (argv[0]);
/* quiet trumps verbose */
if (quiet)
add_files_state.verbosity = VERBOSITY_QUIET;
@ -992,10 +1012,11 @@ notmuch_new_command (notmuch_config_t *config, int argc, char *argv[])
fputs (status_string, stderr);
free (status_string);
}
return EXIT_FAILURE;
}
notmuch_exit_if_unmatched_db_uuid (notmuch);
if (notmuch_database_needs_upgrade (notmuch)) {
time_t now = time (NULL);
struct tm *gm_time = gmtime (&now);
@ -1047,7 +1068,7 @@ notmuch_new_command (notmuch_config_t *config, int argc, char *argv[])
if (notmuch == NULL)
return EXIT_FAILURE;
/* Setup our handler for SIGINT. We do this after having
/* Set up our handler for SIGINT. We do this after having
* potentially done a database upgrade we this interrupt handler
* won't support. */
memset (&action, 0, sizeof (struct sigaction));
@ -1059,9 +1080,6 @@ notmuch_new_command (notmuch_config_t *config, int argc, char *argv[])
talloc_free (dot_notmuch_path);
dot_notmuch_path = NULL;
add_files_state.processed_files = 0;
add_files_state.added_messages = 0;
add_files_state.removed_messages = add_files_state.renamed_messages = 0;
gettimeofday (&add_files_state.tv_start, NULL);
add_files_state.removed_files = _filename_list_create (config);

View file

@ -606,8 +606,13 @@ notmuch_reply_format_default(void *ctx,
notmuch_messages_t *messages;
notmuch_message_t *message;
mime_node_t *root;
notmuch_status_t status;
for (messages = notmuch_query_search_messages (query);
status = notmuch_query_search_messages_st (query, &messages);
if (print_status_query ("notmuch reply", query, status))
return 1;
for (;
notmuch_messages_valid (messages);
notmuch_messages_move_to_next (messages))
{
@ -650,13 +655,22 @@ notmuch_reply_format_sprinter(void *ctx,
notmuch_messages_t *messages;
notmuch_message_t *message;
mime_node_t *node;
unsigned count;
notmuch_status_t status;
if (notmuch_query_count_messages (query) != 1) {
status = notmuch_query_count_messages_st (query, &count);
if (print_status_query ("notmuch reply", query, status))
return 1;
if (count != 1) {
fprintf (stderr, "Error: search term did not match precisely one message.\n");
return 1;
}
messages = notmuch_query_search_messages (query);
status = notmuch_query_search_messages_st (query, &messages);
if (print_status_query ("notmuch reply", query, status))
return 1;
message = notmuch_messages_get (messages);
if (mime_node_open (ctx, message, &(params->crypto), &node) != NOTMUCH_STATUS_SUCCESS)
return 1;
@ -698,8 +712,13 @@ notmuch_reply_format_headers_only(void *ctx,
notmuch_message_t *message;
const char *in_reply_to, *orig_references, *references;
char *reply_headers;
notmuch_status_t status;
for (messages = notmuch_query_search_messages (query);
status = notmuch_query_search_messages_st (query, &messages);
if (print_status_query ("notmuch reply", query, status))
return 1;
for (;
notmuch_messages_valid (messages);
notmuch_messages_move_to_next (messages))
{
@ -790,6 +809,7 @@ notmuch_reply_command (notmuch_config_t *config, int argc, char *argv[])
{ "sender", FALSE },
{ 0, 0 } } },
{ NOTMUCH_OPT_BOOLEAN, &params.crypto.decrypt, "decrypt", 'd', 0 },
{ NOTMUCH_OPT_INHERIT, (void *) &notmuch_shared_options, NULL, 0, 0 },
{ 0, 0, 0, 0, 0 }
};
@ -797,6 +817,8 @@ notmuch_reply_command (notmuch_config_t *config, int argc, char *argv[])
if (opt_index < 0)
return EXIT_FAILURE;
notmuch_process_shared_options (argv[0]);
if (format == FORMAT_HEADERS_ONLY) {
reply_format_func = notmuch_reply_format_headers_only;
} else if (format == FORMAT_JSON) {
@ -828,6 +850,8 @@ notmuch_reply_command (notmuch_config_t *config, int argc, char *argv[])
NOTMUCH_DATABASE_MODE_READ_ONLY, &notmuch))
return EXIT_FAILURE;
notmuch_exit_if_unmatched_db_uuid (notmuch);
query = notmuch_query_create (notmuch, query_string);
if (query == NULL) {
fprintf (stderr, "Out of memory\n");

View file

@ -154,6 +154,7 @@ notmuch_restore_command (notmuch_config_t *config, int argc, char *argv[])
{ 0, 0 } } },
{ NOTMUCH_OPT_STRING, &input_file_name, "input", 'i', 0 },
{ NOTMUCH_OPT_BOOLEAN, &accumulate, "accumulate", 'a', 0 },
{ NOTMUCH_OPT_INHERIT, (void *) &notmuch_shared_options, NULL, 0, 0 },
{ 0, 0, 0, 0, 0 }
};
@ -163,6 +164,9 @@ notmuch_restore_command (notmuch_config_t *config, int argc, char *argv[])
goto DONE;
}
notmuch_process_shared_options (argv[0]);
notmuch_exit_if_unmatched_db_uuid (notmuch);
name_for_error = input_file_name ? input_file_name : "stdin";
if (! accumulate)

View file

@ -36,6 +36,12 @@ typedef enum {
OUTPUT_COUNT = 1 << 7,
} output_t;
typedef enum {
DEDUP_NONE,
DEDUP_MAILBOX,
DEDUP_ADDRESS,
} dedup_t;
typedef enum {
NOTMUCH_FORMAT_JSON,
NOTMUCH_FORMAT_TEXT,
@ -55,6 +61,7 @@ typedef struct {
int limit;
int dupe;
GHashTable *addresses;
dedup_t dedup;
} search_context_t;
typedef struct {
@ -111,15 +118,22 @@ do_search_threads (search_context_t *ctx)
sprinter_t *format = ctx->format;
time_t date;
int i;
notmuch_status_t status;
if (ctx->offset < 0) {
ctx->offset += notmuch_query_count_threads (ctx->query);
unsigned count;
notmuch_status_t status;
status = notmuch_query_count_threads_st (ctx->query, &count);
if (print_status_query ("notmuch search", ctx->query, status))
return 1;
ctx->offset += count;
if (ctx->offset < 0)
ctx->offset = 0;
}
threads = notmuch_query_search_threads (ctx->query);
if (threads == NULL)
status = notmuch_query_search_threads_st (ctx->query, &threads);
if (print_status_query("notmuch search", ctx->query, status))
return 1;
format->begin_list (format);
@ -243,33 +257,87 @@ do_search_threads (search_context_t *ctx)
return 0;
}
static mailbox_t *new_mailbox (void *ctx, const char *name, const char *addr)
{
mailbox_t *mailbox;
mailbox = talloc (ctx, mailbox_t);
if (! mailbox)
return NULL;
mailbox->name = talloc_strdup (mailbox, name);
mailbox->addr = talloc_strdup (mailbox, addr);
mailbox->count = 1;
return mailbox;
}
static int mailbox_compare (const void *v1, const void *v2)
{
const mailbox_t *m1 = v1, *m2 = v2;
int ret;
ret = strcmp_null (m1->name, m2->name);
if (! ret)
ret = strcmp (m1->addr, m2->addr);
return ret;
}
/* Returns TRUE iff name and addr is duplicate. If not, stores the
* name/addr pair in order to detect subsequent duplicates. */
static notmuch_bool_t
is_duplicate (const search_context_t *ctx, const char *name, const char *addr)
{
notmuch_bool_t duplicate;
char *key;
GList *list, *l;
mailbox_t *mailbox;
key = talloc_asprintf (ctx->format, "%s <%s>", name, addr);
list = g_hash_table_lookup (ctx->addresses, addr);
if (list) {
mailbox_t find = {
.name = name,
.addr = addr,
};
l = g_list_find_custom (list, &find, mailbox_compare);
if (l) {
mailbox = l->data;
mailbox->count++;
return TRUE;
}
mailbox = new_mailbox (ctx->format, name, addr);
if (! mailbox)
return FALSE;
/*
* XXX: It would be more efficient to prepend to the list, but
* then we'd have to store the changed list head back to the
* hash table. This check is here just to avoid the compiler
* warning for unused result.
*/
if (list != g_list_append (list, mailbox))
INTERNAL_ERROR ("appending to list changed list head\n");
return FALSE;
}
key = talloc_strdup (ctx->format, addr);
if (! key)
return FALSE;
duplicate = g_hash_table_lookup_extended (ctx->addresses, key, NULL, (gpointer)&mailbox);
mailbox = new_mailbox (ctx->format, name, addr);
if (! mailbox)
return FALSE;
if (! duplicate) {
mailbox = talloc (ctx->format, mailbox_t);
mailbox->name = talloc_strdup (mailbox, name);
mailbox->addr = talloc_strdup (mailbox, addr);
mailbox->count = 1;
g_hash_table_insert (ctx->addresses, key, mailbox);
} else {
mailbox->count++;
talloc_free (key);
}
list = g_list_append (NULL, mailbox);
if (! list)
return FALSE;
return duplicate;
g_hash_table_insert (ctx->addresses, key, list);
return FALSE;
}
static void
@ -287,7 +355,7 @@ print_mailbox (const search_context_t *ctx, const mailbox_t *mailbox)
name_addr = internet_address_to_string (ia, FALSE);
if (format->is_text_printer) {
if (count > 0) {
if (ctx->output & OUTPUT_COUNT) {
format->integer (format, count);
format->string (format, "\t");
}
@ -301,7 +369,7 @@ print_mailbox (const search_context_t *ctx, const mailbox_t *mailbox)
format->string (format, addr);
format->map_key (format, "name-addr");
format->string (format, name_addr);
if (count > 0) {
if (ctx->output & OUTPUT_COUNT) {
format->map_key (format, "count");
format->integer (format, count);
}
@ -338,13 +406,15 @@ process_address_list (const search_context_t *ctx,
mailbox_t mbx = {
.name = internet_address_get_name (address),
.addr = internet_address_mailbox_get_addr (mailbox),
.count = 0,
};
if (is_duplicate (ctx, mbx.name, mbx.addr))
/* OUTPUT_COUNT only works with deduplication */
if (ctx->dedup != DEDUP_NONE &&
is_duplicate (ctx, mbx.name, mbx.addr))
continue;
if (ctx->output & OUTPUT_COUNT)
/* OUTPUT_COUNT and DEDUP_ADDRESS require a full pass. */
if (ctx->output & OUTPUT_COUNT || ctx->dedup == DEDUP_ADDRESS)
continue;
print_mailbox (ctx, &mbx);
@ -378,14 +448,56 @@ _talloc_free_for_g_hash (void *ptr)
}
static void
print_hash_value (unused (gpointer key), gpointer value, gpointer user_data)
_list_free_for_g_hash (void *ptr)
{
const mailbox_t *mailbox = value;
search_context_t *ctx = user_data;
g_list_free_full (ptr, _talloc_free_for_g_hash);
}
/* Print the most common variant of a list of unique mailboxes, and
* conflate the counts. */
static void
print_popular (const search_context_t *ctx, GList *list)
{
GList *l;
mailbox_t *mailbox = NULL, *m;
int max = 0;
int total = 0;
for (l = list; l; l = l->next) {
m = l->data;
total += m->count;
if (m->count > max) {
mailbox = m;
max = m->count;
}
}
if (! mailbox)
INTERNAL_ERROR("Empty list in address hash table\n");
/* The original count is no longer needed, so overwrite. */
mailbox->count = total;
print_mailbox (ctx, mailbox);
}
static void
print_list_value (void *mailbox, void *context)
{
print_mailbox (context, mailbox);
}
static void
print_hash_value (unused (void *key), void *list, void *context)
{
const search_context_t *ctx = context;
if (ctx->dedup == DEDUP_ADDRESS)
print_popular (ctx, list);
else
g_list_foreach (list, print_list_value, context);
}
static int
_count_filenames (notmuch_message_t *message)
{
@ -412,15 +524,22 @@ do_search_messages (search_context_t *ctx)
notmuch_filenames_t *filenames;
sprinter_t *format = ctx->format;
int i;
notmuch_status_t status;
if (ctx->offset < 0) {
ctx->offset += notmuch_query_count_messages (ctx->query);
unsigned count;
notmuch_status_t status;
status = notmuch_query_count_messages_st (ctx->query, &count);
if (print_status_query ("notmuch search", ctx->query, status))
return 1;
ctx->offset += count;
if (ctx->offset < 0)
ctx->offset = 0;
}
messages = notmuch_query_search_messages (ctx->query);
if (messages == NULL)
status = notmuch_query_search_messages_st (ctx->query, &messages);
if (print_status_query ("notmuch search", ctx->query, status))
return 1;
format->begin_list (format);
@ -481,7 +600,8 @@ do_search_messages (search_context_t *ctx)
notmuch_message_destroy (message);
}
if (ctx->addresses && ctx->output & OUTPUT_COUNT)
if (ctx->addresses &&
(ctx->output & OUTPUT_COUNT || ctx->dedup == DEDUP_ADDRESS))
g_hash_table_foreach (ctx->addresses, print_hash_value, ctx);
notmuch_messages_destroy (messages);
@ -508,8 +628,9 @@ do_search_tags (const search_context_t *ctx)
if (strcmp (notmuch_query_get_query_string (query), "*") == 0) {
tags = notmuch_database_get_all_tags (notmuch);
} else {
messages = notmuch_query_search_messages (query);
if (messages == NULL)
notmuch_status_t status;
status = notmuch_query_search_messages_st (query, &messages);
if (print_status_query ("notmuch search", query, status))
return 1;
tags = notmuch_messages_collect_tags (messages);
@ -583,6 +704,8 @@ _notmuch_search_prepare (search_context_t *ctx, notmuch_config_t *config, int ar
return EXIT_FAILURE;
}
notmuch_exit_if_unmatched_db_uuid (ctx->notmuch);
query_str = query_string_from_args (ctx->notmuch, argc, argv);
if (query_str == NULL) {
fprintf (stderr, "Out of memory.\n");
@ -640,6 +763,7 @@ static search_context_t search_context = {
.offset = 0,
.limit = -1, /* unlimited */
.dupe = -1,
.dedup = DEDUP_MAILBOX,
};
static const notmuch_opt_desc_t common_options[] = {
@ -681,6 +805,7 @@ notmuch_search_command (notmuch_config_t *config, int argc, char *argv[])
{ NOTMUCH_OPT_INT, &ctx->limit, "limit", 'L', 0 },
{ NOTMUCH_OPT_INT, &ctx->dupe, "duplicate", 'D', 0 },
{ NOTMUCH_OPT_INHERIT, (void *) &common_options, NULL, 0, 0 },
{ NOTMUCH_OPT_INHERIT, (void *) &notmuch_shared_options, NULL, 0, 0 },
{ 0, 0, 0, 0, 0 }
};
@ -689,6 +814,8 @@ notmuch_search_command (notmuch_config_t *config, int argc, char *argv[])
if (opt_index < 0)
return EXIT_FAILURE;
notmuch_process_shared_options (argv[0]);
if (ctx->output != OUTPUT_FILES && ctx->output != OUTPUT_MESSAGES &&
ctx->dupe != -1) {
fprintf (stderr, "Error: --duplicate=N is only supported with --output=files and --output=messages.\n");
@ -736,7 +863,13 @@ notmuch_address_command (notmuch_config_t *config, int argc, char *argv[])
(notmuch_keyword_t []){ { "true", NOTMUCH_EXCLUDE_TRUE },
{ "false", NOTMUCH_EXCLUDE_FALSE },
{ 0, 0 } } },
{ NOTMUCH_OPT_KEYWORD, &ctx->dedup, "deduplicate", 'D',
(notmuch_keyword_t []){ { "no", DEDUP_NONE },
{ "mailbox", DEDUP_MAILBOX },
{ "address", DEDUP_ADDRESS },
{ 0, 0 } } },
{ NOTMUCH_OPT_INHERIT, (void *) &common_options, NULL, 0, 0 },
{ NOTMUCH_OPT_INHERIT, (void *) &notmuch_shared_options, NULL, 0, 0 },
{ 0, 0, 0, 0, 0 }
};
@ -744,15 +877,28 @@ notmuch_address_command (notmuch_config_t *config, int argc, char *argv[])
if (opt_index < 0)
return EXIT_FAILURE;
notmuch_process_shared_options (argv[0]);
if (! (ctx->output & (OUTPUT_SENDER | OUTPUT_RECIPIENTS)))
ctx->output |= OUTPUT_SENDER;
if (ctx->output & OUTPUT_COUNT && ctx->dedup == DEDUP_NONE) {
fprintf (stderr, "--output=count is not applicable with --deduplicate=no\n");
return EXIT_FAILURE;
}
if (_notmuch_search_prepare (ctx, config,
argc - opt_index, argv + opt_index))
return EXIT_FAILURE;
ctx->addresses = g_hash_table_new_full (g_str_hash, g_str_equal,
_talloc_free_for_g_hash, _talloc_free_for_g_hash);
ctx->addresses = g_hash_table_new_full (strcase_hash, strcase_equal,
_talloc_free_for_g_hash,
_list_free_for_g_hash);
/* The order is not guaranteed if a full pass is required, so go
* for fastest. */
if (ctx->output & OUTPUT_COUNT || ctx->dedup == DEDUP_ADDRESS)
notmuch_query_set_sort (ctx->query, NOTMUCH_SORT_UNSORTED);
ret = do_search_messages (ctx);

View file

@ -145,6 +145,13 @@ notmuch_setup_command (notmuch_config_t *config,
chomp_newline (response); \
} while (0)
if (notmuch_minimal_options ("setup", argc, argv) < 0)
return EXIT_FAILURE;
if (notmuch_requested_db_uuid)
fprintf (stderr, "Warning: ignoring --uuid=%s\n",
notmuch_requested_db_uuid);
if (notmuch_config_is_new (config))
welcome_message_pre_setup ();

View file

@ -334,8 +334,6 @@ show_text_part_content (GMimeObject *part, GMimeStream *stream_out,
g_object_unref(stream_filter);
}
#ifdef GMIME_ATLEAST_26
/* Get signature status string (GMime 2.6) */
static const char*
signature_status_to_string (GMimeSignatureStatus x)
@ -427,91 +425,6 @@ format_part_sigstatus_sprinter (sprinter_t *sp, mime_node_t *node)
sp->end (sp);
}
#else /* GMIME_ATLEAST_26 */
/* Get signature status string (GMime 2.4) */
static const char*
signer_status_to_string (GMimeSignerStatus x)
{
switch (x) {
case GMIME_SIGNER_STATUS_NONE:
return "none";
case GMIME_SIGNER_STATUS_GOOD:
return "good";
case GMIME_SIGNER_STATUS_BAD:
return "bad";
case GMIME_SIGNER_STATUS_ERROR:
return "error";
}
return "unknown";
}
/* Signature status sprinter (GMime 2.4) */
static void
format_part_sigstatus_sprinter (sprinter_t *sp, mime_node_t *node)
{
const GMimeSignatureValidity* validity = node->sig_validity;
sp->begin_list (sp);
if (!validity) {
sp->end (sp);
return;
}
const GMimeSigner *signer = g_mime_signature_validity_get_signers (validity);
while (signer) {
sp->begin_map (sp);
/* status */
sp->map_key (sp, "status");
sp->string (sp, signer_status_to_string (signer->status));
if (signer->status == GMIME_SIGNER_STATUS_GOOD)
{
if (signer->fingerprint) {
sp->map_key (sp, "fingerprint");
sp->string (sp, signer->fingerprint);
}
/* these dates are seconds since the epoch; should we
* provide a more human-readable format string? */
if (signer->created) {
sp->map_key (sp, "created");
sp->integer (sp, signer->created);
}
if (signer->expires) {
sp->map_key (sp, "expires");
sp->integer (sp, signer->expires);
}
/* output user id only if validity is FULL or ULTIMATE. */
/* note that gmime is using the term "trust" here, which
* is WRONG. It's actually user id "validity". */
if ((signer->name) && (signer->trust)) {
if ((signer->trust == GMIME_SIGNER_TRUST_FULLY) || (signer->trust == GMIME_SIGNER_TRUST_ULTIMATE)) {
sp->map_key (sp, "userid");
sp->string (sp, signer->name);
}
}
} else {
if (signer->keyid) {
sp->map_key (sp, "keyid");
sp->string (sp, signer->keyid);
}
}
if (signer->errors != GMIME_SIGNER_ERROR_NONE) {
sp->map_key (sp, "errors");
sp->integer (sp, signer->errors);
}
sp->end (sp);
signer = signer->next;
}
sp->end (sp);
}
#endif /* GMIME_ATLEAST_26 */
static notmuch_status_t
format_part_text (const void *ctx, sprinter_t *sp, mime_node_t *node,
int indent, const notmuch_show_params_t *params)
@ -982,13 +895,22 @@ do_show_single (void *ctx,
{
notmuch_messages_t *messages;
notmuch_message_t *message;
notmuch_status_t status;
unsigned int count;
if (notmuch_query_count_messages (query) != 1) {
status = notmuch_query_count_messages_st (query, &count);
if (print_status_query ("notmuch show", query, status))
return 1;
if (count != 1) {
fprintf (stderr, "Error: search term did not match precisely one message.\n");
return 1;
}
messages = notmuch_query_search_messages (query);
status = notmuch_query_search_messages_st (query, &messages);
if (print_status_query ("notmuch show", query, status))
return 1;
message = notmuch_messages_get (messages);
if (message == NULL) {
@ -1015,8 +937,8 @@ do_show (void *ctx,
notmuch_messages_t *messages;
notmuch_status_t status, res = NOTMUCH_STATUS_SUCCESS;
threads = notmuch_query_search_threads (query);
if (! threads)
status= notmuch_query_search_threads_st (query, &threads);
if (print_status_query ("notmuch show", query, status))
return 1;
sp->begin_list (sp);
@ -1114,6 +1036,7 @@ notmuch_show_command (notmuch_config_t *config, int argc, char *argv[])
{ NOTMUCH_OPT_BOOLEAN, &params.crypto.verify, "verify", 'v', 0 },
{ NOTMUCH_OPT_BOOLEAN, &params.output_body, "body", 'b', 0 },
{ NOTMUCH_OPT_BOOLEAN, &params.include_html, "include-html", 0, 0 },
{ NOTMUCH_OPT_INHERIT, (void *) &notmuch_shared_options, NULL, 0, 0 },
{ 0, 0, 0, 0, 0 }
};
@ -1121,6 +1044,8 @@ notmuch_show_command (notmuch_config_t *config, int argc, char *argv[])
if (opt_index < 0)
return EXIT_FAILURE;
notmuch_process_shared_options (argv[0]);
/* decryption implies verification */
if (params.crypto.decrypt)
params.crypto.verify = TRUE;
@ -1210,6 +1135,8 @@ notmuch_show_command (notmuch_config_t *config, int argc, char *argv[])
NOTMUCH_DATABASE_MODE_READ_ONLY, &notmuch))
return EXIT_FAILURE;
notmuch_exit_if_unmatched_db_uuid (notmuch);
query = notmuch_query_create (notmuch, query_string);
if (query == NULL) {
fprintf (stderr, "Out of memory\n");

View file

@ -97,6 +97,8 @@ tag_query (void *ctx, notmuch_database_t *notmuch, const char *query_string,
notmuch_query_t *query;
notmuch_messages_t *messages;
notmuch_message_t *message;
notmuch_status_t status;
int ret = NOTMUCH_STATUS_SUCCESS;
if (! (flags & TAG_FLAG_REMOVE_ALL)) {
@ -119,7 +121,11 @@ tag_query (void *ctx, notmuch_database_t *notmuch, const char *query_string,
/* tagging is not interested in any special sort order */
notmuch_query_set_sort (query, NOTMUCH_SORT_UNSORTED);
for (messages = notmuch_query_search_messages (query);
status = notmuch_query_search_messages_st (query, &messages);
if (print_status_query ("notmuch tag", query, status))
return status;
for (;
notmuch_messages_valid (messages) && ! interrupted;
notmuch_messages_move_to_next (messages)) {
message = notmuch_messages_get (messages);
@ -195,7 +201,7 @@ notmuch_tag_command (notmuch_config_t *config, int argc, char *argv[])
int opt_index;
int ret;
/* Setup our handler for SIGINT */
/* Set up our handler for SIGINT */
memset (&action, 0, sizeof (struct sigaction));
action.sa_handler = handle_sigint;
sigemptyset (&action.sa_mask);
@ -206,6 +212,7 @@ notmuch_tag_command (notmuch_config_t *config, int argc, char *argv[])
{ NOTMUCH_OPT_BOOLEAN, &batch, "batch", 0, 0 },
{ NOTMUCH_OPT_STRING, &input_file_name, "input", 'i', 0 },
{ NOTMUCH_OPT_BOOLEAN, &remove_all, "remove-all", 0, 0 },
{ NOTMUCH_OPT_INHERIT, (void *) &notmuch_shared_options, NULL, 0, 0 },
{ 0, 0, 0, 0, 0 }
};
@ -213,6 +220,8 @@ notmuch_tag_command (notmuch_config_t *config, int argc, char *argv[])
if (opt_index < 0)
return EXIT_FAILURE;
notmuch_process_shared_options (argv[0]);
if (input_file_name) {
batch = TRUE;
input = fopen (input_file_name, "r");
@ -258,6 +267,8 @@ notmuch_tag_command (notmuch_config_t *config, int argc, char *argv[])
NOTMUCH_DATABASE_MODE_READ_WRITE, &notmuch))
return EXIT_FAILURE;
notmuch_exit_if_unmatched_db_uuid (notmuch);
if (notmuch_config_get_maildir_synchronize_flags (config))
tag_flags |= TAG_FLAG_MAILDIR_SYNC;

134
notmuch.c
View file

@ -43,11 +43,64 @@ notmuch_help_command (notmuch_config_t *config, int argc, char *argv[]);
static int
notmuch_command (notmuch_config_t *config, int argc, char *argv[]);
static int
_help_for (const char *topic);
static notmuch_bool_t print_version = FALSE, print_help = FALSE;
char *notmuch_requested_db_uuid = NULL;
const notmuch_opt_desc_t notmuch_shared_options [] = {
{ NOTMUCH_OPT_BOOLEAN, &print_version, "version", 'v', 0 },
{ NOTMUCH_OPT_BOOLEAN, &print_help, "help", 'h', 0 },
{ NOTMUCH_OPT_STRING, &notmuch_requested_db_uuid, "uuid", 'u', 0 },
{0, 0, 0, 0, 0}
};
/* any subcommand wanting to support these options should call
* inherit notmuch_shared_options and call
* notmuch_process_shared_options (subcommand_name);
*/
void
notmuch_process_shared_options (const char *subcommand_name) {
if (print_version) {
printf ("notmuch " STRINGIFY(NOTMUCH_VERSION) "\n");
exit (EXIT_SUCCESS);
}
if (print_help) {
int ret = _help_for (subcommand_name);
exit (ret);
}
}
/* This is suitable for subcommands that do not actually open the
* database.
*/
int notmuch_minimal_options (const char *subcommand_name,
int argc, char **argv)
{
int opt_index;
notmuch_opt_desc_t options[] = {
{ NOTMUCH_OPT_INHERIT, (void *) &notmuch_shared_options, NULL, 0, 0 },
{ 0, 0, 0, 0, 0 }
};
opt_index = parse_arguments (argc, argv, options, 1);
if (opt_index < 0)
return -1;
/* We can't use argv here as it is sometimes NULL */
notmuch_process_shared_options (subcommand_name);
return opt_index;
}
static command_t commands[] = {
{ NULL, notmuch_command, TRUE,
"Notmuch main command." },
{ "setup", notmuch_setup_command, TRUE,
"Interactively setup notmuch for first use." },
"Interactively set up notmuch for first use." },
{ "new", notmuch_new_command, FALSE,
"Find and import new messages to the notmuch database." },
{ "insert", notmuch_insert_command, FALSE,
@ -167,6 +220,22 @@ be supported in the future.\n", notmuch_format_version);
}
}
void
notmuch_exit_if_unmatched_db_uuid (notmuch_database_t *notmuch)
{
const char *uuid = NULL;
if (!notmuch_requested_db_uuid)
return;
IGNORE_RESULT (notmuch_database_get_revision (notmuch, &uuid));
if (strcmp (notmuch_requested_db_uuid, uuid) != 0){
fprintf (stderr, "Error: requested database revision %s does not match %s\n",
notmuch_requested_db_uuid, uuid);
exit (1);
}
}
static void
exec_man (const char *page)
{
@ -177,21 +246,19 @@ exec_man (const char *page)
}
static int
notmuch_help_command (notmuch_config_t *config, int argc, char *argv[])
_help_for (const char *topic_name)
{
command_t *command;
help_topic_t *topic;
unsigned int i;
argc--; argv++; /* Ignore "help" */
if (argc == 0) {
if (!topic_name) {
printf ("The notmuch mail system.\n\n");
usage (stdout);
return EXIT_SUCCESS;
}
if (strcmp (argv[0], "help") == 0) {
if (strcmp (topic_name, "help") == 0) {
printf ("The notmuch help system.\n\n"
"\tNotmuch uses the man command to display help. In case\n"
"\tof difficulties check that MANPATH includes the pages\n"
@ -200,26 +267,46 @@ notmuch_help_command (notmuch_config_t *config, int argc, char *argv[])
return EXIT_SUCCESS;
}
command = find_command (argv[0]);
command = find_command (topic_name);
if (command) {
char *page = talloc_asprintf (config, "notmuch-%s", command->name);
char *page = talloc_asprintf (NULL, "notmuch-%s", command->name);
exec_man (page);
}
for (i = 0; i < ARRAY_SIZE (help_topics); i++) {
topic = &help_topics[i];
if (strcmp (argv[0], topic->name) == 0) {
char *page = talloc_asprintf (config, "notmuch-%s", topic->name);
if (strcmp (topic_name, topic->name) == 0) {
char *page = talloc_asprintf (NULL, "notmuch-%s", topic->name);
exec_man (page);
}
}
fprintf (stderr,
"\nSorry, %s is not a known command. There's not much I can do to help.\n\n",
argv[0]);
topic_name);
return EXIT_FAILURE;
}
static int
notmuch_help_command (unused (notmuch_config_t * config), int argc, char *argv[])
{
int opt_index;
opt_index = notmuch_minimal_options ("help", argc, argv);
if (opt_index < 0)
return EXIT_FAILURE;
/* skip at least subcommand argument */
argc-= opt_index;
argv+= opt_index;
if (argc == 0) {
return _help_for (NULL);
}
return _help_for (argv[0]);
}
/* Handle the case of "notmuch" being invoked with no command
* argument. For now we just call notmuch_setup_command, but we plan
* to be more clever about this in the future.
@ -285,14 +372,12 @@ main (int argc, char *argv[])
command_t *command;
char *config_file_name = NULL;
notmuch_config_t *config = NULL;
notmuch_bool_t print_help=FALSE, print_version=FALSE;
int opt_index;
int ret;
notmuch_opt_desc_t options[] = {
{ NOTMUCH_OPT_BOOLEAN, &print_help, "help", 'h', 0 },
{ NOTMUCH_OPT_BOOLEAN, &print_version, "version", 'v', 0 },
{ NOTMUCH_OPT_STRING, &config_file_name, "config", 'c', 0 },
{ NOTMUCH_OPT_INHERIT, (void *) &notmuch_shared_options, NULL, 0, 0 },
{ 0, 0, 0, 0, 0 }
};
@ -314,28 +399,11 @@ main (int argc, char *argv[])
goto DONE;
}
/* Handle notmuch --help [command] and notmuch command --help. */
if (print_help ||
(opt_index + 1 < argc && strcmp (argv[opt_index + 1], "--help") == 0)) {
/*
* Pass the first positional argument as argv[1] so the help
* command can give help for it. The help command ignores the
* argv[0] passed to it.
*/
ret = notmuch_help_command (NULL, argc - opt_index + 1,
argv + opt_index - 1);
goto DONE;
}
if (print_version) {
printf ("notmuch " STRINGIFY(NOTMUCH_VERSION) "\n");
ret = EXIT_SUCCESS;
goto DONE;
}
if (opt_index < argc)
command_name = argv[opt_index];
notmuch_process_shared_options (command_name);
command = find_command (command_name);
if (!command) {
fprintf (stderr, "Error: Unknown command '%s' (see \"notmuch help\")\n",

View file

@ -2,7 +2,7 @@
test_description='notmuch new'
. ./perf-test-lib.sh
. ./perf-test-lib.sh || exit 1
# ensure initial 'notmuch new' is run by memory_start
uncache_database

View file

@ -2,7 +2,7 @@
test_description='dump and restore'
. ./perf-test-lib.sh
. ./perf-test-lib.sh || exit 1
memory_start

View file

@ -2,7 +2,7 @@
test_description='notmuch new'
. ./perf-test-lib.sh
. ./perf-test-lib.sh || exit 1
uncache_database

View file

@ -2,7 +2,7 @@
test_description='dump and restore'
. ./perf-test-lib.sh
. ./perf-test-lib.sh || exit 1
time_start

View file

@ -2,7 +2,7 @@
test_description='tagging'
. ./perf-test-lib.sh
. ./perf-test-lib.sh || exit 1
time_start

View file

@ -1,4 +1,4 @@
. ./version.sh
. ./version.sh || exit 1
corpus_size=large
@ -25,7 +25,7 @@ do
echo "error: unknown performance test option '$1'" >&2; exit 1 ;;
esac
done
. ../test/test-lib-common.sh
. ../test/test-lib-common.sh || exit 1
set -e
@ -203,7 +203,7 @@ time_done ()
fi
}
cd -P "$test" || error "Cannot setup test environment"
cd -P "$test" || error "Cannot set up test environment"
test_failure=0
test_count=0

21
status.c Normal file
View file

@ -0,0 +1,21 @@
#include "notmuch-client.h"
notmuch_status_t
print_status_query (const char *loc,
const notmuch_query_t *query,
notmuch_status_t status)
{
if (status) {
const char *msg;
notmuch_database_t *notmuch;
fprintf (stderr, "%s: %s\n", loc,
notmuch_status_to_string (status));
notmuch = notmuch_query_get_database (query);
msg = notmuch_database_status_string (notmuch);
if (msg)
fputs (msg, stderr);
}
return status;
}

View file

@ -56,7 +56,17 @@ TEST_BINARIES := $(TEST_BINARIES:.cc=)
test-binaries: $(TEST_BINARIES)
test: all test-binaries
ifeq ($V,)
@echo 'Use "$(MAKE) V=1" to print test headings and PASSIng results.'
@env NOTMUCH_TEST_QUIET=1 ${test_src_dir}/notmuch-test $(OPTIONS)
else
# The user has explicitly enabled quiet execution.
ifeq ($V,0)
@env NOTMUCH_TEST_QUIET=1 ${test_src_dir}/notmuch-test $(OPTIONS)
else
@${test_src_dir}/notmuch-test $(OPTIONS)
endif
endif
check: test

View file

@ -117,6 +117,13 @@ Note that some tests in the existing test suite rely on previous test
items, so you cannot arbitrarily skip any test and expect the
remaining tests to be unaffected.
Currently we do not consider skipped tests as build failures. For
maximum robustness, when setting up automated build processes, you
should explicitely skip tests, rather than relying on notmuch's
detection of missing prerequisites. In the future we may treat tests
unable to run because of missing prerequisites, but not explicitely
skipped by the user, as failures.
Writing Tests
-------------
The test script is written as a shell script. It should start with
@ -138,7 +145,7 @@ Source 'test-lib.sh'
After assigning test_description, the test script should source
test-lib.sh like this:
. ./test-lib.sh
. ./test-lib.sh || exit 1
This test harness library does the following things:

View file

@ -14,7 +14,7 @@ then
exit 1
fi
. ./test-lib.sh
. ./test-lib.sh || exit 1
################################################################
# Test harness

View file

@ -1,7 +1,7 @@
#!/usr/bin/env bash
test_description="online help"
. ./test-lib.sh
. ./test-lib.sh || exit 1
test_expect_success 'notmuch --help' 'notmuch --help'
test_expect_success 'notmuch help' 'notmuch help'
@ -12,9 +12,9 @@ if [ $NOTMUCH_HAVE_MAN -eq 1 ]; then
test_expect_success 'notmuch help tag' 'notmuch help tag'
else
test_expect_success 'notmuch --help tag (man pages not available)' \
'test_must_fail notmuch --help tag'
'test_must_fail notmuch --help tag >/dev/null'
test_expect_success 'notmuch help tag (man pages not available)' \
'test_must_fail notmuch help tag'
'test_must_fail notmuch help tag >/dev/null'
fi
test_done

View file

@ -1,6 +1,6 @@
#!/usr/bin/env bash
test_description='"notmuch compact"'
. ./test-lib.sh
. ./test-lib.sh || exit 1
add_message '[subject]=One'
add_message '[subject]=Two'

View file

@ -1,7 +1,7 @@
#!/usr/bin/env bash
test_description='"notmuch config"'
. ./test-lib.sh
. ./test-lib.sh || exit 1
test_begin_subtest "Get string value"
test_expect_equal "$(notmuch config get user.name)" "Notmuch Test Suite"

View file

@ -1,7 +1,7 @@
#!/usr/bin/env bash
test_description='"notmuch setup"'
. ./test-lib.sh
. ./test-lib.sh || exit 1
test_begin_subtest "Notmuch new without a config suggests notmuch setup"
output=$(notmuch --config=new-notmuch-config new 2>&1)

View file

@ -1,27 +1,27 @@
#!/usr/bin/env bash
test_description='"notmuch new" in several variations'
. ./test-lib.sh
. ./test-lib.sh || exit 1
test_begin_subtest "No new messages"
output=$(NOTMUCH_NEW)
output=$(NOTMUCH_NEW --debug)
test_expect_equal "$output" "No new mail."
test_begin_subtest "Single new message"
generate_message
output=$(NOTMUCH_NEW)
output=$(NOTMUCH_NEW --debug)
test_expect_equal "$output" "Added 1 new message to the database."
test_begin_subtest "Multiple new messages"
generate_message
generate_message
output=$(NOTMUCH_NEW)
output=$(NOTMUCH_NEW --debug)
test_expect_equal "$output" "Added 2 new messages to the database."
test_begin_subtest "No new messages (non-empty DB)"
output=$(NOTMUCH_NEW)
output=$(NOTMUCH_NEW --debug)
test_expect_equal "$output" "No new mail."
@ -31,7 +31,7 @@ mkdir "${MAIL_DIR}"/def
mkdir "${MAIL_DIR}"/ghi
generate_message [dir]=def
output=$(NOTMUCH_NEW)
output=$(NOTMUCH_NEW --debug)
test_expect_equal "$output" "Added 1 new message to the database."
@ -42,7 +42,7 @@ mv "${MAIL_DIR}"/ghi "${MAIL_DIR}"/abc
rm "${MAIL_DIR}"/def/*
generate_message [dir]=abc
output=$(NOTMUCH_NEW)
output=$(NOTMUCH_NEW --debug)
test_expect_equal "$output" "Added 1 new message to the database."
@ -54,7 +54,7 @@ mkdir -p "$(dirname "$tmp_msg_filename")"
mv "$gen_msg_filename" "$tmp_msg_filename"
notmuch new > /dev/null
mv "$tmp_msg_filename" "$gen_msg_filename"
output=$(NOTMUCH_NEW)
output=$(NOTMUCH_NEW --debug)
test_expect_equal "$output" "Added 1 new message to the database."
@ -63,15 +63,18 @@ test_begin_subtest "Renamed message"
generate_message
notmuch new > /dev/null
mv "$gen_msg_filename" "${gen_msg_filename}"-renamed
output=$(NOTMUCH_NEW)
test_expect_equal "$output" "No new mail. Detected 1 file rename."
output=$(NOTMUCH_NEW --debug)
test_expect_equal "$output" "(D) add_files_recursive, pass 2: queuing passed file ${gen_msg_filename} for deletion from database
No new mail. Detected 1 file rename."
test_begin_subtest "Deleted message"
rm "${gen_msg_filename}"-renamed
output=$(NOTMUCH_NEW)
test_expect_equal "$output" "No new mail. Removed 1 message."
output=$(NOTMUCH_NEW --debug)
test_expect_equal "$output" "(D) add_files_recursive, pass 3: queuing leftover file ${gen_msg_filename}-renamed for deletion from database
No new mail. Removed 1 message."
test_begin_subtest "Renamed directory"
@ -84,16 +87,17 @@ notmuch new > /dev/null
mv "${MAIL_DIR}"/dir "${MAIL_DIR}"/dir-renamed
output=$(NOTMUCH_NEW)
test_expect_equal "$output" "No new mail. Detected 3 file renames."
output=$(NOTMUCH_NEW --debug)
test_expect_equal "$output" "(D) add_files_recursive, pass 2: queuing passed directory ${MAIL_DIR}/dir for deletion from database
No new mail. Detected 3 file renames."
test_begin_subtest "Deleted directory"
rm -rf "${MAIL_DIR}"/dir-renamed
output=$(NOTMUCH_NEW)
test_expect_equal "$output" "No new mail. Removed 3 messages."
output=$(NOTMUCH_NEW --debug)
test_expect_equal "$output" "(D) add_files_recursive, pass 2: queuing passed directory ${MAIL_DIR}/dir-renamed for deletion from database
No new mail. Removed 3 messages."
test_begin_subtest "New directory (at end of list)"
@ -102,7 +106,7 @@ generate_message [dir]=zzz
generate_message [dir]=zzz
generate_message [dir]=zzz
output=$(NOTMUCH_NEW)
output=$(NOTMUCH_NEW --debug)
test_expect_equal "$output" "Added 3 new messages to the database."
@ -110,8 +114,9 @@ test_begin_subtest "Deleted directory (end of list)"
rm -rf "${MAIL_DIR}"/zzz
output=$(NOTMUCH_NEW)
test_expect_equal "$output" "No new mail. Removed 3 messages."
output=$(NOTMUCH_NEW --debug)
test_expect_equal "$output" "(D) add_files_recursive, pass 3: queuing leftover directory ${MAIL_DIR}/zzz for deletion from database
No new mail. Removed 3 messages."
test_begin_subtest "New symlink to directory"
@ -122,7 +127,7 @@ mv "${MAIL_DIR}" "${TMP_DIRECTORY}"/actual_maildir
mkdir "${MAIL_DIR}"
ln -s "${TMP_DIRECTORY}"/actual_maildir "${MAIL_DIR}"/symlink
output=$(NOTMUCH_NEW)
output=$(NOTMUCH_NEW --debug)
test_expect_equal "$output" "Added 1 new message to the database."
@ -132,13 +137,13 @@ external_msg_filename="${TMP_DIRECTORY}"/external/"$(basename "$gen_msg_filename
mkdir -p "$(dirname "$external_msg_filename")"
mv "$gen_msg_filename" "$external_msg_filename"
ln -s "$external_msg_filename" "$gen_msg_filename"
output=$(NOTMUCH_NEW)
output=$(NOTMUCH_NEW --debug)
test_expect_equal "$output" "Added 1 new message to the database."
test_begin_subtest "Broken symlink aborts"
ln -s does-not-exist "${MAIL_DIR}/broken"
output=$(NOTMUCH_NEW 2>&1)
output=$(NOTMUCH_NEW --debug 2>&1)
test_expect_equal "$output" \
"Error reading file ${MAIL_DIR}/broken: No such file or directory
Note: A fatal error was encountered: Something went wrong trying to read or write a file
@ -152,7 +157,7 @@ generate_message [dir]=two/levels
generate_message [dir]=two/levels
generate_message [dir]=two/levels
output=$(NOTMUCH_NEW)
output=$(NOTMUCH_NEW --debug)
test_expect_equal "$output" "Added 3 new messages to the database."
@ -160,10 +165,11 @@ test_begin_subtest "Deleted two-level directory"
rm -rf "${MAIL_DIR}"/two
output=$(NOTMUCH_NEW)
test_expect_equal "$output" "No new mail. Removed 3 messages."
output=$(NOTMUCH_NEW --debug)
test_expect_equal "$output" "(D) add_files_recursive, pass 3: queuing leftover directory ${MAIL_DIR}/two for deletion from database
No new mail. Removed 3 messages."
test_begin_subtest "Support single-message mbox (deprecated)"
test_begin_subtest "Support single-message mbox"
cat > "${MAIL_DIR}"/mbox_file1 <<EOF
From test_suite@notmuchmail.org Fri Jan 5 15:43:57 2001
From: Notmuch Test Suite <test_suite@notmuchmail.org>
@ -172,7 +178,7 @@ Subject: Test mbox message 1
Body.
EOF
output=$(NOTMUCH_NEW 2>&1)
output=$(NOTMUCH_NEW --debug 2>&1)
test_expect_equal "$output" "Added 1 new message to the database."
# This test requires that notmuch new has been run at least once.
@ -196,7 +202,7 @@ Subject: Test mbox message 2
Body 2.
EOF
output=$(NOTMUCH_NEW 2>&1)
output=$(NOTMUCH_NEW --debug 2>&1)
test_expect_equal "$output" \
"Note: Ignoring non-mail file: ${MAIL_DIR}/.git/config
Note: Ignoring non-mail file: ${MAIL_DIR}/.ignored_hidden_file
@ -263,23 +269,23 @@ OLDCONFIG=$(notmuch config get new.tags)
test_begin_subtest "Empty tags in new.tags are forbidden"
notmuch config set new.tags "foo;;bar"
output=$(NOTMUCH_NEW 2>&1)
output=$(NOTMUCH_NEW --debug 2>&1)
test_expect_equal "$output" "Error: tag '' in new.tags: empty tag forbidden"
test_begin_subtest "Tags starting with '-' in new.tags are forbidden"
notmuch config set new.tags "-foo;bar"
output=$(NOTMUCH_NEW 2>&1)
output=$(NOTMUCH_NEW --debug 2>&1)
test_expect_equal "$output" "Error: tag '-foo' in new.tags: tag starting with '-' forbidden"
test_expect_code 1 "Invalid tags set exit code" \
"NOTMUCH_NEW 2>&1"
"NOTMUCH_NEW --debug 2>&1"
notmuch config set new.tags $OLDCONFIG
test_begin_subtest "Xapian exception: read only files"
chmod u-w ${MAIL_DIR}/.notmuch/xapian/*.DB
output=$(NOTMUCH_NEW 2>&1 | sed 's/: .*$//' )
output=$(NOTMUCH_NEW --debug 2>&1 | sed 's/: .*$//' )
chmod u+w ${MAIL_DIR}/.notmuch/xapian/*.DB
test_expect_equal "$output" "A Xapian exception occurred opening database"

View file

@ -1,6 +1,6 @@
#!/usr/bin/env bash
test_description='"notmuch count" for messages and threads'
. ./test-lib.sh
. ./test-lib.sh || exit 1
add_email_corpus
@ -93,5 +93,35 @@ notmuch count --output=messages >>EXPECTED
notmuch count --output=messages tag:inbox >>EXPECTED
test_expect_equal_file EXPECTED OUTPUT
backup_database
test_begin_subtest "error message for database open"
dd if=/dev/zero of="${MAIL_DIR}/.notmuch/xapian/postlist.DB" count=3
notmuch count '*' 2>OUTPUT 1>/dev/null
output=$(sed 's/^\(A Xapian exception [^:]*\):.*$/\1/' OUTPUT)
test_expect_equal "${output}" "A Xapian exception occurred opening database"
restore_database
cat <<EOF > count-files.gdb
set breakpoint pending on
break count_files
commands
shell cp /dev/null ${MAIL_DIR}/.notmuch/xapian/postlist.DB
continue
end
run
EOF
backup_database
test_begin_subtest "error message from query_search_messages"
gdb --batch-silent --return-child-result -x count-files.gdb \
--args notmuch count --output=files '*' 2>OUTPUT 1>/dev/null
cat <<EOF > EXPECTED
notmuch count: A Xapian exception occurred
A Xapian exception occurred performing query
Query string was: *
EOF
sed 's/^\(A Xapian exception [^:]*\):.*$/\1/' < OUTPUT > OUTPUT.clean
test_expect_equal_file EXPECTED OUTPUT.clean
restore_database
test_done

View file

@ -1,6 +1,6 @@
#!/usr/bin/env bash
test_description='"notmuch insert"'
. ./test-lib.sh
. ./test-lib.sh || exit 1
test_require_external_prereq gdb
@ -188,7 +188,7 @@ notmuch config set new.tags $OLDCONFIG
# DUPLICATE_MESSAGE_ID is not tested here, because it should actually pass.
for code in OUT_OF_MEMORY XAPIAN_EXCEPTION FILE_NOT_EMAIL \
READ_ONLY_DATABASE UPGRADE_REQUIRED; do
READ_ONLY_DATABASE UPGRADE_REQUIRED PATH_ERROR; do
gen_insert_msg
cat <<EOF > index-file-$code.gdb
set breakpoint pending on

View file

@ -1,6 +1,6 @@
#!/usr/bin/env bash
test_description='"notmuch search" in several variations'
. ./test-lib.sh
. ./test-lib.sh || exit 1
add_email_corpus

View file

@ -1,6 +1,6 @@
#!/usr/bin/env bash
test_description='various settings for "notmuch search --output="'
. ./test-lib.sh
. ./test-lib.sh || exit 1
add_email_corpus

View file

@ -1,6 +1,6 @@
#!/usr/bin/env bash
test_description='"notmuch address" in several variants'
. ./test-lib.sh
. ./test-lib.sh || exit 1
add_email_corpus
@ -145,4 +145,148 @@ cat <<EOF >EXPECTED
EOF
test_expect_equal_file OUTPUT EXPECTED
test_begin_subtest "--deduplicate=no --sort=oldest-first --output=sender"
notmuch address --deduplicate=no --sort=oldest-first --output=sender '*' >OUTPUT
cat <<EOF >EXPECTED
Mikhail Gusarov <dottedmag@dottedmag.net>
Mikhail Gusarov <dottedmag@dottedmag.net>
Carl Worth <cworth@cworth.org>
Lars Kellogg-Stedman <lars@seas.harvard.edu>
Mikhail Gusarov <dottedmag@dottedmag.net>
Alex Botero-Lowry <alex.boterolowry@gmail.com>
Carl Worth <cworth@cworth.org>
Lars Kellogg-Stedman <lars@seas.harvard.edu>
Mikhail Gusarov <dottedmag@dottedmag.net>
Mikhail Gusarov <dottedmag@dottedmag.net>
Keith Packard <keithp@keithp.com>
Keith Packard <keithp@keithp.com>
Keith Packard <keithp@keithp.com>
Jan Janak <jan@ryngle.com>
Jan Janak <jan@ryngle.com>
Jan Janak <jan@ryngle.com>
Israel Herraiz <isra@herraiz.org>
Adrian Perez de Castro <aperez@igalia.com>
Aron Griffis <agriffis@n01se.net>
Ingmar Vanhassel <ingmar@exherbo.org>
Alex Botero-Lowry <alex.boterolowry@gmail.com>
Lars Kellogg-Stedman <lars@seas.harvard.edu>
Lars Kellogg-Stedman <lars@seas.harvard.edu>
Lars Kellogg-Stedman <lars@seas.harvard.edu>
Stewart Smith <stewart@flamingspork.com>
Stewart Smith <stewart@flamingspork.com>
Keith Packard <keithp@keithp.com>
Keith Packard <keithp@keithp.com>
Keith Packard <keithp@keithp.com>
Stewart Smith <stewart@flamingspork.com>
Jjgod Jiang <gzjjgod@gmail.com>
Jan Janak <jan@ryngle.com>
Rolland Santimano <rollandsantimano@yahoo.com>
Alexander Botero-Lowry <alex.boterolowry@gmail.com>
Jjgod Jiang <gzjjgod@gmail.com>
Alexander Botero-Lowry <alex.boterolowry@gmail.com>
Alexander Botero-Lowry <alex.boterolowry@gmail.com>
Keith Packard <keithp@keithp.com>
Alexander Botero-Lowry <alex.boterolowry@gmail.com>
Carl Worth <cworth@cworth.org>
Carl Worth <cworth@cworth.org>
Carl Worth <cworth@cworth.org>
Carl Worth <cworth@cworth.org>
Carl Worth <cworth@cworth.org>
Carl Worth <cworth@cworth.org>
Carl Worth <cworth@cworth.org>
Carl Worth <cworth@cworth.org>
Carl Worth <cworth@cworth.org>
Carl Worth <cworth@cworth.org>
Chris Wilson <chris@chris-wilson.co.uk>
Olivier Berger <olivier.berger@it-sudparis.eu>
François Boulogne <boulogne.f@gmail.com>
EOF
test_expect_equal_file OUTPUT EXPECTED
test_begin_subtest "--deduplicate=no --sort=newest-first --output=sender --output=recipients"
notmuch address --deduplicate=no --sort=newest-first --output=sender --output=recipients path:foo/new >OUTPUT
cat <<EOF >EXPECTED
Mikhail Gusarov <dottedmag@dottedmag.net>
notmuch@notmuchmail.org
Mikhail Gusarov <dottedmag@dottedmag.net>
notmuch@notmuchmail.org
Lars Kellogg-Stedman <lars@seas.harvard.edu>
notmuch@notmuchmail.org
EOF
test_expect_equal_file OUTPUT EXPECTED
test_begin_subtest "--deduplicate=address --output=sender --output=recipients"
notmuch address --deduplicate=address --output=sender --output=recipients '*' | sort >OUTPUT
cat <<EOF >EXPECTED
"Discussion about the Arch User Repository (AUR)" <aur-general@archlinux.org>
Adrian Perez de Castro <aperez@igalia.com>
Alexander Botero-Lowry <alex.boterolowry@gmail.com>
Allan McRae <allan@archlinux.org>
Aron Griffis <agriffis@n01se.net>
Carl Worth <cworth@cworth.org>
Chris Wilson <chris@chris-wilson.co.uk>
François Boulogne <boulogne.f@gmail.com>
Ingmar Vanhassel <ingmar@exherbo.org>
Israel Herraiz <isra@herraiz.org>
Jan Janak <jan@ryngle.com>
Jjgod Jiang <gzjjgod@gmail.com>
Keith Packard <keithp@keithp.com>
Lars Kellogg-Stedman <lars@seas.harvard.edu>
Mikhail Gusarov <dottedmag@dottedmag.net>
Olivier Berger <olivier.berger@it-sudparis.eu>
Rolland Santimano <rollandsantimano@yahoo.com>
Stewart Smith <stewart@flamingspork.com>
notmuch@notmuchmail.org
EOF
test_expect_equal_file OUTPUT EXPECTED
generate_message '[from]="Foo Bar <foo.bar@example.com>"'
generate_message '[from]="Foo Bar <Foo.Bar@Example.Com>"'
generate_message '[from]="Foo Bar <foo.bar@example.com>"'
generate_message '[from]="Bar <Foo.Bar@Example.Com>"'
generate_message '[from]="Foo <foo.bar@example.com>"'
generate_message '[from]="<foo.bar@example.com>"'
generate_message '[from]="foo.bar@example.com"'
generate_message '[from]="Baz <foo.bar+baz@example.com>"'
generate_message '[from]="Foo Bar <foo.bar+baz@example.com>"'
generate_message '[from]="Baz <foo.bar+baz@example.com>"'
notmuch new > /dev/null
test_begin_subtest "--deduplicate=no --output=sender"
notmuch address --deduplicate=no --output=sender from:example.com | sort >OUTPUT
cat <<EOF >EXPECTED
Bar <Foo.Bar@Example.Com>
Baz <foo.bar+baz@example.com>
Baz <foo.bar+baz@example.com>
Foo <foo.bar@example.com>
Foo Bar <Foo.Bar@Example.Com>
Foo Bar <foo.bar+baz@example.com>
Foo Bar <foo.bar@example.com>
Foo Bar <foo.bar@example.com>
foo.bar@example.com
foo.bar@example.com
EOF
test_expect_equal_file OUTPUT EXPECTED
test_begin_subtest "--deduplicate=mailbox --output=sender --output=count"
notmuch address --deduplicate=mailbox --output=sender --output=count from:example.com | sort -n >OUTPUT
cat <<EOF >EXPECTED
1 Bar <Foo.Bar@Example.Com>
1 Foo <foo.bar@example.com>
1 Foo Bar <Foo.Bar@Example.Com>
1 Foo Bar <foo.bar+baz@example.com>
2 Baz <foo.bar+baz@example.com>
2 Foo Bar <foo.bar@example.com>
2 foo.bar@example.com
EOF
test_expect_equal_file OUTPUT EXPECTED
test_begin_subtest "--deduplicate=address --output=sender --output=count"
notmuch address --deduplicate=address --output=sender --output=count from:example.com | sort -n >OUTPUT
cat <<EOF >EXPECTED
3 Baz <foo.bar+baz@example.com>
7 Foo Bar <foo.bar@example.com>
EOF
test_expect_equal_file OUTPUT EXPECTED
test_done

View file

@ -1,6 +1,6 @@
#!/usr/bin/env bash
test_description='"notmuch search" by folder: and path: (with variations)'
. ./test-lib.sh
. ./test-lib.sh || exit 1
add_message '[dir]=bad' '[subject]="To the bone"'
add_message '[dir]=.' '[subject]="Top level"'

View file

@ -18,7 +18,7 @@
# id:3wd4o8wa7fx.fsf@testarossa.amd.com
test_description='that notmuch does not overlap term positions'
. ./test-lib.sh
. ./test-lib.sh || exit 1
add_message '[to]="a@b.c, x@y.z"'

View file

@ -1,6 +1,6 @@
#!/usr/bin/env bash
test_description='messages with unquoted . in name'
. ./test-lib.sh
. ./test-lib.sh || exit 1
add_message \
'[from]="Some.Name for Someone <bugs@quoting.com>"' \

View file

@ -1,6 +1,6 @@
#!/usr/bin/env bash
test_description='"notmuch search" --offset and --limit parameters'
. ./test-lib.sh
. ./test-lib.sh || exit 1
add_email_corpus

View file

@ -1,6 +1,6 @@
#!/usr/bin/env bash
test_description='"notmuch search, count and show" with excludes in several variations'
. ./test-lib.sh
. ./test-lib.sh || exit 1
# Generates a thread consisting of a top level message and 'length'
# replies. The subject of the top message 'subject: top message"

View file

@ -1,6 +1,6 @@
#!/usr/bin/env bash
test_description='"notmuch tag"'
. ./test-lib.sh
. ./test-lib.sh || exit 1
add_message '[subject]=One'
add_message '[subject]=Two'

View file

@ -1,6 +1,6 @@
#!/usr/bin/env bash
test_description="--format=json output"
. ./test-lib.sh
. ./test-lib.sh || exit 1
test_begin_subtest "Show message: json"
add_message "[subject]=\"json-show-subject\"" "[date]=\"Sat, 01 Jan 2000 12:00:00 -0000\"" "[bcc]=\"test_suite+bcc@notmuchmail.org\"" "[reply-to]=\"test_suite+replyto@notmuchmail.org\"" "[body]=\"json-show-message\""

View file

@ -1,6 +1,6 @@
#!/usr/bin/env bash
test_description="--format=sexp output"
. ./test-lib.sh
. ./test-lib.sh || exit 1
test_begin_subtest "Show message: sexp"
add_message "[subject]=\"sexp-show-subject\"" "[date]=\"Sat, 01 Jan 2000 12:00:00 -0000\"" "[bcc]=\"test_suite+bcc@notmuchmail.org\"" "[reply-to]=\"test_suite+replyto@notmuchmail.org\"" "[body]=\"sexp-show-message\""

View file

@ -1,6 +1,6 @@
#!/usr/bin/env bash
test_description="--format=text output"
. ./test-lib.sh
. ./test-lib.sh || exit 1
test_begin_subtest "Show message: text"
add_message "[subject]=\"text-show-subject\"" "[date]=\"Sat, 01 Jan 2000 12:00:00 -0000\"" "[body]=\"text-show-message\""

View file

@ -1,6 +1,6 @@
#!/usr/bin/env bash
test_description="output of multipart message"
. ./test-lib.sh
. ./test-lib.sh || exit 1
cat <<EOF > embedded_message
From: Carl Worth <cworth@cworth.org>

View file

@ -1,6 +1,6 @@
#!/usr/bin/env bash
test_description="naming of threads with changing subject"
. ./test-lib.sh
. ./test-lib.sh || exit 1
test_begin_subtest "Initial thread name (oldest-first search)"
add_message '[subject]="thread-naming: Initial thread subject"' \

View file

@ -1,6 +1,6 @@
#!/usr/bin/env bash
test_description="naming of authors with unusual addresses"
. ./test-lib.sh
. ./test-lib.sh || exit 1
test_begin_subtest "Add author with empty quoted real name"
add_message '[subject]="author-naming: Initial thread subject"' \

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