]> git.notmuchmail.org Git - notmuch/commitdiff
Merge tag '0.23.6'
authorDavid Bremner <david@tethera.net>
Tue, 28 Feb 2017 12:06:42 +0000 (08:06 -0400)
committerDavid Bremner <david@tethera.net>
Tue, 28 Feb 2017 12:06:42 +0000 (08:06 -0400)
notmuch 0.23.6 release

73 files changed:
Makefile
Makefile.global [new file with mode: 0644]
Makefile.local
completion/notmuch-completion.bash
configure
devel/emacs-keybindings.org [new file with mode: 0644]
devel/nmbug/nmbug
devel/schemata
doc/.gitignore
doc/Makefile.local
doc/conf.py
doc/man1/notmuch-address.rst
doc/man1/notmuch-config.rst
doc/man1/notmuch-count.rst
doc/man1/notmuch-dump.rst
doc/man1/notmuch-emacs-mua.rst
doc/man1/notmuch-restore.rst
doc/mkdocdeps.py [deleted file]
doc/notmuch-emacs.rst
emacs/Makefile.local
emacs/notmuch-address.el
emacs/notmuch-company.el
emacs/notmuch-crypto.el
emacs/notmuch-draft.el [new file with mode: 0644]
emacs/notmuch-emacs-mua [new file with mode: 0755]
emacs/notmuch-emacs-mua.desktop [new file with mode: 0644]
emacs/notmuch-hello.el
emacs/notmuch-jump.el
emacs/notmuch-lib.el
emacs/notmuch-maildir-fcc.el
emacs/notmuch-mua.el
emacs/notmuch-show.el
emacs/notmuch-tag.el
emacs/notmuch-tree.el
emacs/notmuch.el
lib/Makefile.local
lib/database-private.h
lib/database.cc
lib/libsha1.c [deleted file]
lib/libsha1.h [deleted file]
lib/message.cc
lib/notmuch-private.h
lib/notmuch.h
lib/query.cc
lib/sha1.c
notmuch-client.h
notmuch-config.c
notmuch-emacs-mua [deleted file]
notmuch-new.c
notmuch-show.c
notmuch.c
notmuch.desktop [deleted file]
test/Makefile.local
test/T000-basic.sh
test/T050-new.sh
test/T060-count.sh
test/T070-insert.sh
test/T160-json.sh
test/T170-sexp.sh
test/T190-multipart.sh
test/T220-reply.sh
test/T260-thread-order.sh
test/T340-maildir-sync.sh
test/T350-crypto.sh
test/T355-smime.sh
test/T380-atomicity.sh
test/T470-missing-headers.sh
test/T510-thread-replies.sh
test/T580-thread-search.sh
test/T630-emacs-draft.sh [new file with mode: 0755]
test/T640-database-modified.sh [new file with mode: 0755]
test/test-lib.sh
util/endian-util.h [deleted file]

index 4c0e8c62011393277cc1041a69859cde5296c2ea..0ef57fa9fabed81019822e033b29802bb0bbb151 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -33,6 +33,8 @@ ifeq ($(configure_options),)
 endif
        $(srcdir)/configure $(configure_options)
 
+# runtime variable definitions available in all subdirs
+include $(srcdir)/Makefile.global
 # Finally, include all of the Makefile.local fragments where all the
 # real work is done.
 
diff --git a/Makefile.global b/Makefile.global
new file mode 100644 (file)
index 0000000..d8f335a
--- /dev/null
@@ -0,0 +1,65 @@
+# Here's the (hopefully simple) versioning scheme.
+#
+# Releases of notmuch have a two-digit version (0.1, 0.2, etc.). We
+# increment the second digit for each release and increment the first
+# digit when we reach particularly major milestones of usability.
+#
+# Between releases, (such as when compiling notmuch from the git
+# repository), we let git append identification of the actual commit.
+PACKAGE=notmuch
+
+IS_GIT:=$(if $(wildcard ${srcdir}/.git),yes,no)
+
+ifeq ($(IS_GIT),yes)
+DATE:=$(shell git --git-dir=${srcdir}/.git log --date=short -1 --pretty=format:%cd)
+else
+DATE:=$(shell date +%F)
+endif
+
+VERSION:=$(shell cat ${srcdir}/version)
+ELPA_VERSION:=$(subst ~,_,$(VERSION))
+ifeq ($(filter release release-message pre-release update-versions,$(MAKECMDGOALS)),)
+ifeq ($(IS_GIT),yes)
+VERSION:=$(shell git --git-dir=${srcdir}/.git describe --abbrev=7 --match '[0-9.]*'|sed -e s/_/~/ -e s/-/+/ -e s/-/~/)
+# drop the ~g$sha1 part
+ELPA_VERSION:=$(word 1,$(subst ~, ,$(VERSION)))
+# convert git version to package.el friendly form
+ELPA_VERSION:=$(subst +,snapshot,$(ELPA_VERSION))
+
+# Write the file 'version.stamp' in case its contents differ from $(VERSION)
+FILE_VERSION:=$(shell test -f version.stamp && read vs < version.stamp || vs=; echo $$vs)
+ifneq ($(FILE_VERSION),$(VERSION))
+       $(shell echo "$(VERSION)" > version.stamp)
+endif
+endif
+endif
+
+UPSTREAM_TAG=$(subst ~,_,$(VERSION))
+DEB_TAG=debian/$(UPSTREAM_TAG)-1
+
+RELEASE_HOST=notmuchmail.org
+RELEASE_DIR=/srv/notmuchmail.org/www/releases
+RELEASE_URL=https://notmuchmail.org/releases
+TAR_FILE=$(PACKAGE)-$(VERSION).tar.gz
+ELPA_FILE:=$(PACKAGE)-emacs-$(ELPA_VERSION).tar
+DEB_TAR_FILE=$(PACKAGE)_$(VERSION).orig.tar.gz
+SHA1_FILE=$(TAR_FILE).sha1
+GPG_FILE=$(SHA1_FILE).asc
+
+PV_FILE=bindings/python/notmuch/version.py
+
+# Smash together user's values with our extra values
+STD_CFLAGS := -std=gnu99
+FINAL_CFLAGS = -DNOTMUCH_VERSION=$(VERSION) $(CPPFLAGS) $(STD_CFLAGS) $(CFLAGS) $(WARN_CFLAGS) $(extra_cflags) $(CONFIGURE_CFLAGS)
+FINAL_CXXFLAGS = $(CPPFLAGS) $(CXXFLAGS) $(WARN_CXXFLAGS) $(extra_cflags) $(extra_cxxflags) $(CONFIGURE_CXXFLAGS)
+FINAL_NOTMUCH_LDFLAGS = $(LDFLAGS) -Lutil -lutil -Llib -lnotmuch
+ifeq ($(LIBDIR_IN_LDCONFIG),0)
+FINAL_NOTMUCH_LDFLAGS += $(RPATH_LDFLAGS)
+endif
+FINAL_NOTMUCH_LDFLAGS += $(AS_NEEDED_LDFLAGS) $(GMIME_LDFLAGS) $(TALLOC_LDFLAGS) $(ZLIB_LDFLAGS)
+FINAL_NOTMUCH_LINKER = CC
+ifneq ($(LINKER_RESOLVES_LIBRARY_DEPENDENCIES),1)
+FINAL_NOTMUCH_LDFLAGS += $(CONFIGURE_LDFLAGS)
+FINAL_NOTMUCH_LINKER = CXX
+endif
+FINAL_LIBNOTMUCH_LDFLAGS = $(LDFLAGS) $(AS_NEEDED_LDFLAGS) $(CONFIGURE_LDFLAGS)
index 0a122ab0e208c29c501bc3aee9c37243e06b13c9..3548ed9665c974f236b7a01469da46bb15026641 100644 (file)
@@ -1,67 +1,5 @@
 # -*- makefile -*-
 
-# Here's the (hopefully simple) versioning scheme.
-#
-# Releases of notmuch have a two-digit version (0.1, 0.2, etc.). We
-# increment the second digit for each release and increment the first
-# digit when we reach particularly major milestones of usability.
-#
-# Between releases, (such as when compiling notmuch from the git
-# repository), we let git append identification of the actual commit.
-PACKAGE=notmuch
-
-IS_GIT:=$(if $(wildcard ${srcdir}/.git),yes,no)
-
-ifeq ($(IS_GIT),yes)
-DATE:=$(shell git --git-dir=${srcdir}/.git log --date=short -1 --pretty=format:%cd)
-else
-DATE:=$(shell date +%F)
-endif
-
-VERSION:=$(shell cat ${srcdir}/version)
-ELPA_VERSION:=$(subst ~,_,$(VERSION))
-ifeq ($(filter release release-message pre-release update-versions,$(MAKECMDGOALS)),)
-ifeq ($(IS_GIT),yes)
-VERSION:=$(shell git --git-dir=${srcdir}/.git describe --abbrev=7 --match '[0-9.]*'|sed -e s/_/~/ -e s/-/+/ -e s/-/~/)
-# drop the ~g$sha1 part
-ELPA_VERSION:=$(word 1,$(subst ~, ,$(VERSION)))
-# Write the file 'version.stamp' in case its contents differ from $(VERSION)
-FILE_VERSION:=$(shell test -f version.stamp && read vs < version.stamp || vs=; echo $$vs)
-ifneq ($(FILE_VERSION),$(VERSION))
-       $(shell echo "$(VERSION)" > version.stamp)
-endif
-endif
-endif
-
-UPSTREAM_TAG=$(subst ~,_,$(VERSION))
-DEB_TAG=debian/$(UPSTREAM_TAG)-1
-
-RELEASE_HOST=notmuchmail.org
-RELEASE_DIR=/srv/notmuchmail.org/www/releases
-RELEASE_URL=https://notmuchmail.org/releases
-TAR_FILE=$(PACKAGE)-$(VERSION).tar.gz
-DEB_TAR_FILE=$(PACKAGE)_$(VERSION).orig.tar.gz
-SHA1_FILE=$(TAR_FILE).sha1
-GPG_FILE=$(SHA1_FILE).asc
-
-PV_FILE=bindings/python/notmuch/version.py
-
-# Smash together user's values with our extra values
-STD_CFLAGS := -std=gnu99
-FINAL_CFLAGS = -DNOTMUCH_VERSION=$(VERSION) $(CPPFLAGS) $(STD_CFLAGS) $(CFLAGS) $(WARN_CFLAGS) $(extra_cflags) $(CONFIGURE_CFLAGS)
-FINAL_CXXFLAGS = $(CPPFLAGS) $(CXXFLAGS) $(WARN_CXXFLAGS) $(extra_cflags) $(extra_cxxflags) $(CONFIGURE_CXXFLAGS)
-FINAL_NOTMUCH_LDFLAGS = $(LDFLAGS) -Lutil -lutil -Llib -lnotmuch
-ifeq ($(LIBDIR_IN_LDCONFIG),0)
-FINAL_NOTMUCH_LDFLAGS += $(RPATH_LDFLAGS)
-endif
-FINAL_NOTMUCH_LDFLAGS += $(AS_NEEDED_LDFLAGS) $(GMIME_LDFLAGS) $(TALLOC_LDFLAGS) $(ZLIB_LDFLAGS)
-FINAL_NOTMUCH_LINKER = CC
-ifneq ($(LINKER_RESOLVES_LIBRARY_DEPENDENCIES),1)
-FINAL_NOTMUCH_LDFLAGS += $(CONFIGURE_LDFLAGS)
-FINAL_NOTMUCH_LINKER = CXX
-endif
-FINAL_LIBNOTMUCH_LDFLAGS = $(LDFLAGS) $(AS_NEEDED_LDFLAGS) $(CONFIGURE_LDFLAGS)
-
 .PHONY: all
 all: notmuch notmuch-shared build-man ruby-bindings
 ifeq ($(MAKECMDGOALS),)
@@ -336,11 +274,6 @@ ifeq ($(WITH_EMACS), 1)
 endif
 endif
 
-.PHONY: install-desktop
-install-desktop:
-       mkdir -p "$(DESTDIR)$(desktop_dir)"
-       desktop-file-install --mode 0644 --dir "$(DESTDIR)$(desktop_dir)" notmuch.desktop
-
 SRCS  := $(SRCS) $(notmuch_client_srcs)
 CLEAN := $(CLEAN) notmuch notmuch-shared $(notmuch_client_modules)
 CLEAN := $(CLEAN) version.stamp notmuch-*.tar.gz.tmp
index 78047b5f424de5bf9b6d55548d6c960e1bbceb8f..7cad047f8499b083640c8a968551c09e9246c094 100644 (file)
@@ -58,6 +58,34 @@ _notmuch_email()
        sed 's/[^<]*<\([^>]*\)>/\1/' | tr "[:upper:]" "[:lower:]" | sort -u
 }
 
+_notmuch_mimetype()
+{
+    # use mime types from mime-support package if available, and fall
+    # back to a handful of common ones otherwise
+    if [ -r "/etc/mime.types" ]; then
+       sed -n '/^[[:alpha:]]/{s/[[:space:]].*//;p;}' /etc/mime.types
+    else
+       cat <<EOF
+application/gzip
+application/msword
+application/pdf
+application/zip
+audio/mpeg
+audio/ogg
+image/gif
+image/jpeg
+image/png
+message/rfc822
+text/calendar
+text/html
+text/plain
+text/vcard
+text/x-diff
+text/x-vcalendar
+EOF
+    fi
+}
+
 _notmuch_search_terms()
 {
     local cur prev words cword split
@@ -85,8 +113,16 @@ _notmuch_search_terms()
            COMPREPLY=( $(compgen -d "$path/${cur##folder:}" | \
                sed "s|^$path/||" | grep -v "\(^\|/\)\(cur\|new\|tmp\)$" ) )
            ;;
+       mimetype:*)
+           compopt -o nospace
+           COMPREPLY=( $(compgen -P "mimetype:" -W "`_notmuch_mimetype ${cur}`" -- ${cur##mimetype:}) )
+           ;;
+       query:*)
+           compopt -o nospace
+           COMPREPLY=( $(compgen -P "query:" -W "`notmuch config list | sed -n '/^query\./s/^query\.\([^=]*\)=.*/\1/p'`" -- ${cur##query:}) )
+           ;;
        *)
-           local search_terms="from: to: subject: attachment: mimetype: tag: id: thread: folder: path: date: lastmod:"
+           local search_terms="from: to: subject: attachment: mimetype: tag: id: thread: folder: path: date: lastmod: query: property:"
            compopt -o nospace
            COMPREPLY=( $(compgen -W "${search_terms}" -- ${cur}) )
            ;;
index 46063b5d2eeffabe893c70639ed74f754e5e0ed8..d0d8f18acef8a3d2f5cfbbf2c70361b0410e0af0 100755 (executable)
--- a/configure
+++ b/configure
@@ -70,6 +70,7 @@ LIBDIR=
 WITH_DOCS=1
 WITH_API_DOCS=1
 WITH_EMACS=1
+WITH_DESKTOP=1
 WITH_BASH=1
 WITH_RUBY=1
 WITH_ZSH=1
@@ -141,6 +142,7 @@ Some features can be disabled (--with-feature=no is equivalent to
        --without-docs                  Do not install documentation
        --without-api-docs              Do not install API man page
        --without-emacs                 Do not install lisp file
+       --without-desktop               Do not install desktop file
        --without-ruby                  Do not install ruby bindings
        --without-zsh-completion        Do not install zsh completions files
        --without-retry-lock            Do not use blocking xapian opens, even if available
@@ -209,6 +211,14 @@ for option; do
        fi
     elif [ "${option}" = '--without-emacs' ] ; then
        WITH_EMACS=0
+    elif [ "${option%%=*}" = '--with-desktop' ]; then
+       if [ "${option#*=}" = 'no' ]; then
+           WITH_DESKTOP=0
+       else
+           WITH_DESKTOP=1
+       fi
+    elif [ "${option}" = '--without-desktop' ] ; then
+       WITH_DESKTOP=0
     elif [ "${option%%=*}" = '--with-bash-completion' ]; then
        if [ "${option#*=}" = 'no' ]; then
            WITH_BASH=0
@@ -615,6 +625,16 @@ else
     printf "No. (missing or broken gpgconf?)\n"
 fi
 
+if [ $WITH_DESKTOP = "1" ]; then
+    printf "Checking if desktop-file-install is available... "
+    if command -v desktop-file-install > /dev/null; then
+       printf "Yes.\n"
+    else
+       printf "No (so will not install .desktop file).\n"
+       WITH_DESKTOP=0
+    fi
+fi
+
 libdir_in_ldconfig=0
 
 printf "Checking which platform we are on... "
@@ -673,19 +693,6 @@ else
 EOF
 fi
 
-printf "Checking byte order... "
-cat> _byteorder.c <<EOF
-#include <stdio.h>
-#include <stdint.h>
-uint32_t test = 0x34333231;
-int main() { printf("%.4s\n", (const char*)&test); return 0; }
-EOF
-${CC} ${CFLAGS} _byteorder.c -o _byteorder > /dev/null 2>&1
-util_byte_order=$(./_byteorder)
-echo $util_byte_order
-
-rm -f _byteorder _byteorder.c
-
 if [ $errors -gt 0 ]; then
     cat <<EOF
 
@@ -1005,9 +1012,6 @@ prefix = ${PREFIX}
 # LIBDIR_IN_LDCONFIG value below is still set correctly.
 libdir = ${LIBDIR:=\$(prefix)/lib}
 
-# byte order within a 32 bit word. 1234 = little, 4321 = big, 0 = guess
-UTIL_BYTE_ORDER = ${util_byte_order}
-
 # Whether libdir is in a path configured into ldconfig
 LIBDIR_IN_LDCONFIG = ${libdir_in_ldconfig}
 
@@ -1136,6 +1140,9 @@ VALGRIND_CFLAGS = ${valgrind_cflags}
 # Support for emacs
 WITH_EMACS = ${WITH_EMACS}
 
+# Support for desktop file
+WITH_DESKTOP = ${WITH_DESKTOP}
+
 # Support for bash completion
 WITH_BASH = ${WITH_BASH}
 
@@ -1155,9 +1162,9 @@ COMMON_CONFIGURE_CFLAGS = \\
        -DSTD_GETPWUID=\$(STD_GETPWUID)                         \\
        -DSTD_ASCTIME=\$(STD_ASCTIME)                           \\
        -DHAVE_XAPIAN_COMPACT=\$(HAVE_XAPIAN_COMPACT)           \\
+       -DSILENCE_XAPIAN_DEPRECATION_WARNINGS                   \\
        -DHAVE_XAPIAN_FIELD_PROCESSOR=\$(HAVE_XAPIAN_FIELD_PROCESSOR) \\
-       -DHAVE_XAPIAN_DB_RETRY_LOCK=\$(HAVE_XAPIAN_DB_RETRY_LOCK) \\
-       -DUTIL_BYTE_ORDER=\$(UTIL_BYTE_ORDER)
+       -DHAVE_XAPIAN_DB_RETRY_LOCK=\$(HAVE_XAPIAN_DB_RETRY_LOCK)
 
 CONFIGURE_CFLAGS = \$(COMMON_CONFIGURE_CFLAGS)
 
diff --git a/devel/emacs-keybindings.org b/devel/emacs-keybindings.org
new file mode 100644 (file)
index 0000000..464b946
--- /dev/null
@@ -0,0 +1,58 @@
+|-----------+----------------------------------------+-------------------------------------------------------+-----------------------------------------|
+| Key       | Search Mode                            | Show Mode                                             | Tree Mode                               |
+|-----------+----------------------------------------+-------------------------------------------------------+-----------------------------------------|
+| a         | notmuch-search-archive-thread          | notmuch-show-archive-message-then-next-or-next-thread | notmuch-tree-archive-message-then-next  |
+| b         | notmuch-search-scroll-down             | notmuch-show-resend-message                           | notmuch-show-resend-message             |
+| c         | notmuch-search-stash-map               | notmuch-show-stash-map                                | notmuch-show-stash-map                  |
+| d         |                                        |                                                       |                                         |
+| e         |                                        |                                                       | (notmuch-tree-button-activate)          |
+| f         |                                        | notmuch-show-forward-message                          | notmuch-show-forward-message            |
+| g         |                                        |                                                       |                                         |
+| h         |                                        | notmuch-show-toggle-visibility-headers                |                                         |
+| i         |                                        |                                                       |                                         |
+| j         | notmuch-jump-search                    | notmuch-jump-search                                   | notmuch-jump-search                     |
+| k         | notmuch-tag-jump                       | notmuch-tag-jump                                      | notmuch-tag-jump                        |
+| l         | notmuch-search-filter                  | notmuch-show-filter-thread                            |                                         |
+| m         | notmuch-mua-new-mail                   | notmuch-mua-new-mail                                  | notmuch-mua-new-mail                    |
+| n         | notmuch-search-next-thread             | notmuch-show-next-open-message                        | notmuch-tree-next-matching-message      |
+| o         | notmuch-search-toggle-order            |                                                       |                                         |
+| p         | notmuch-search-previous-thread         | notmuch-show-previous-open-message                    | notmuch-tree-prev-matching-message      |
+| q         | notmuch-bury-or-kill-this-buffer       | notmuch-bury-or-kill-this-buffer                      | notmuch-bury-or-kill-this-buffer        |
+| r         | notmuch-search-reply-to-thread-sender  | notmuch-show-reply-sender                             | notmuch-show-reply-sender               |
+| s         | notmuch-search                         | notmuch-search                                        | notmuch-search                          |
+| t         | notmuch-search-filter-by-tag           | toggle-truncate-lines                                 |                                         |
+| u         |                                        |                                                       |                                         |
+| v         |                                        |                                                       | notmuch-show-view-all-mime-parts        |
+| w         |                                        | notmuch-show-save-attachments                         | notmuch-show-save-attachments           |
+| x         | notmuch-bury-or-kill-this-buffer       | notmuch-show-archive-message-then-next-or-exit        | notmuch-tree-quit                       |
+| y         |                                        |                                                       |                                         |
+| z         | notmuch-tree                           | notmuch-tree                                          | notmuch-tree-to-tree                    |
+| A         |                                        | notmuch-show-archive-thread-then-next                 | notmuch-tree-archive-thread             |
+| F         |                                        | notmuch-show-forward-open-messages                    |                                         |
+| G         | notmuch-poll-and-refresh-this-buffer   | notmuch-poll-and-refresh-this-buffer                  | notmuch-poll-and-refresh-this-buffer    |
+| N         |                                        | notmuch-show-next-message                             | notmuch-tree-next-message               |
+| O         |                                        |                                                       |                                         |
+| P         |                                        | notmuch-show-previous-message                         | notmuch-tree-prev-message               |
+| R         | notmuch-search-reply-to-thread         | notmuch-show-reply                                    | notmuch-show-reply                      |
+| S         |                                        |                                                       | notmuch-search-from-tree-current-query  |
+| V         |                                        | notmuch-show-view-raw-message                         | notmuch-show-view-raw-message           |
+| X         |                                        | notmuch-show-archive-thread-then-exit                 |                                         |
+| Z         | notmuch-tree-from-search-current-query | notmuch-tree-from-show-current-query                  |                                         |
+| =!=       |                                        | notmuch-show-toggle-elide-non-matching                |                                         |
+| =#=       |                                        | notmuch-show-print-message                            |                                         |
+| =$=       |                                        | notmuch-show-toggle-process-crypto                    |                                         |
+| =*=       | notmuch-search-tag-all                 | notmuch-show-tag-all                                  | notmuch-tree-tag-thread                 |
+| +         | notmuch-search-add-tag                 | notmuch-show-add-tag                                  | notmuch-tree-add-tag                    |
+| -         | notmuch-search-remove-tag              | notmuch-show-remove-tag                               | notmuch-tree-remove-tag                 |
+| .         |                                        | notmuch-show-part-map                                 |                                         |
+| <         | notmuch-search-first-thread            | notmuch-show-toggle-thread-indentation                |                                         |
+| <DEL>     | notmuch-search-scroll-down             | notmuch-show-rewind                                   | notmuch-tree-scroll-message-window-back |
+| <RET>     | notmuch-search-show-thread             | notmuch-show-toggle-message                           | notmuch-tree-show-message               |
+| <SPC>     | notmuch-search-scroll-up               | notmuch-show-advance                                  | notmuch-tree-scroll-or-next             |
+| <TAB>     |                                        | notmuch-show-next-button                              | notmuch-show-next-button                |
+| <backtab> |                                        | notmuch-show-previous-button                          | notmuch-show-previous-button            |
+| =         | notmuch-refresh-this-buffer            | notmuch-refresh-this-buffer                           | notmuch-tree-refresh-view               |
+| >         | notmuch-search-last-thread             |                                                       |                                         |
+| ?         | notmuch-help                           | notmuch-help                                          | notmuch-help                            |
+| \vert     |                                        | notmuch-show-pipe-message                             | notmuch-show-pipe-message               |
+|-----------+----------------------------------------+-------------------------------------------------------+-----------------------------------------|
index 1dd5f14fe7b4dbaf01a4fdfbd1d5332ae15b983a..6febf16fde3f2eabd5eaa8671703911de239bff0 100755 (executable)
@@ -475,7 +475,7 @@ def log(args=()):
     'nmbug log HEAD..@{upstream}'.
     """
     # we don't want output trapping here, because we want the pager.
-    args = ['log', '--name-status'] + list(args)
+    args = ['log', '--name-status', '--no-renames'] + list(args)
     with _git(args=args, expect=(0, 1, -13)) as p:
         p.wait()
 
index 41dc4a60fff36608e25425c7f113c6f2a1b667b0..00ebb7a6e7148f9f7f2930de214e0e85ae915589 100644 (file)
@@ -26,6 +26,10 @@ v1
 v2
 - Added the thread_summary.query field.
 
+v3
+- Replaced message.filename string with a list of filenames.
+- Added part.content-disposition field.
+
 Common non-terminals
 --------------------
 
@@ -59,7 +63,7 @@ message = {
     # (format_message_sprinter)
     id:             messageid,
     match:          bool,
-    filename:      string,
+    filename:      [string*],
     timestamp:      unix_time, # date header as unix time
     date_relative:  string,   # user-friendly timestamp
     tags:           [string*],
@@ -76,6 +80,7 @@ part = {
     sigstatus?:     sigstatus,
 
     content-type:   string,
+    content-disposition?:       string,
     content-id?:    string,
     # if content-type starts with "multipart/":
     content:        [part*],
index d0da78e510f1d283b214d663ff1efc9512ba25df..9fa35d08a95e3c850ceafccdea523f310f775e43 100644 (file)
@@ -1,4 +1,3 @@
 *.pyc
-docdeps.mk
 _build
 config.dox
index 8633cfcd3b51e24b5df686f576271e63dbc69b4f..c6f05ca879c0999f20b5c63bcfcfaa8a54cccc87 100644 (file)
@@ -7,13 +7,23 @@ SPHINXOPTS    := -q
 SPHINXBUILD   = sphinx-build
 DOCBUILDDIR      := $(dir)/_build
 
-mkdocdeps := $(PYTHON) $(srcdir)/$(dir)/mkdocdeps.py
-
 # Internal variables.
 ALLSPHINXOPTS   := -d $(DOCBUILDDIR)/doctrees $(SPHINXOPTS) $(srcdir)/$(dir)
 APIMAN         := $(DOCBUILDDIR)/man/man3/notmuch.3
 DOXYFILE       := $(srcdir)/$(dir)/doxygen.cfg
 
+MAN1_RST := $(wildcard $(srcdir)/doc/man1/*.rst)
+MAN5_RST := $(wildcard $(srcdir)/doc/man5/*.rst)
+MAN7_RST := $(wildcard $(srcdir)/doc/man7/*.rst)
+MAN_RST_FILES := $(MAN1_RST) $(MAN5_RST) $(MAN7_RST)
+
+MAN1_ROFF := $(patsubst $(srcdir)/doc/%,$(DOCBUILDDIR)/man/%,$(MAN1_RST:.rst=.1))
+MAN5_ROFF := $(patsubst $(srcdir)/doc/%,$(DOCBUILDDIR)/man/%,$(MAN5_RST:.rst=.5))
+MAN7_ROFF := $(patsubst $(srcdir)/doc/%,$(DOCBUILDDIR)/man/%,$(MAN7_RST:.rst=.7))
+MAN_ROFF_FILES := $(MAN1_ROFF) $(MAN5_ROFF) $(MAN7_ROFF)
+
+MAN_GZIP_FILES := $(addsuffix .gz,${MAN_ROFF_FILES})
+
 .PHONY: sphinx-html sphinx-texinfo sphinx-info
 
 .PHONY: install-man build-man apidocs install-apidocs
@@ -30,10 +40,6 @@ sphinx-texinfo:
 sphinx-info: sphinx-texinfo
        make -C $(DOCBUILDDIR)/texinfo info
 
--include $(dir)/docdeps.mk
-
-MAN_GZIP_FILES := $(addsuffix .gz,${MAN_ROFF_FILES})
-
 # Use the man page converter that is available. We should never depend
 # on MAN_ROFF_FILES if a converter is not available.
 ${MAN_ROFF_FILES}: $(DOCBUILDDIR)/.roff.stamp
@@ -53,7 +59,7 @@ else
        @echo "Fatal: build dependency fail."
        @false
 endif
-       touch ${MAN_ROFF_FILES} $@
+       touch $@
 
 install-man: install-apidocs
 
@@ -62,7 +68,7 @@ MAN_GZIP_FILES += ${APIMAN}.gz
 apidocs: $(APIMAN)
 install-apidocs: ${APIMAN}.gz
        mkdir -p "$(DESTDIR)$(mandir)/man3"
-       install -m0644  $(DOCBUILDDIR)/man/man3/*.3.gz  $(DESTDIR)/$(mandir)/man3
+       install -m0644 $(filter %.3.gz,$(MAN_GZIP_FILES)) $(DESTDIR)/$(mandir)/man3
 
 $(APIMAN): $(dir)/config.dox $(srcdir)/$(dir)/doxygen.cfg $(srcdir)/lib/notmuch.h
        mkdir -p $(DOCBUILDDIR)/man/man3
@@ -86,9 +92,9 @@ install-man: ${MAN_GZIP_FILES}
        mkdir -p "$(DESTDIR)$(mandir)/man1"
        mkdir -p "$(DESTDIR)$(mandir)/man5"
        mkdir -p "$(DESTDIR)$(mandir)/man7"
-       install -m0644 $(DOCBUILDDIR)/man/man1/*.1.gz $(DESTDIR)/$(mandir)/man1
-       install -m0644 $(DOCBUILDDIR)/man/man5/*.5.gz $(DESTDIR)/$(mandir)/man5
-       install -m0644 $(DOCBUILDDIR)/man/man7/*.7.gz $(DESTDIR)/$(mandir)/man7
+       install -m0644 $(filter %.1.gz,$(MAN_GZIP_FILES)) $(DESTDIR)/$(mandir)/man1
+       install -m0644 $(filter %.5.gz,$(MAN_GZIP_FILES)) $(DESTDIR)/$(mandir)/man5
+       install -m0644 $(filter %.7.gz,$(MAN_GZIP_FILES)) $(DESTDIR)/$(mandir)/man7
        cd $(DESTDIR)/$(mandir)/man1 && ln -sf notmuch.1.gz notmuch-setup.1.gz
 endif
 
@@ -96,8 +102,5 @@ $(dir)/config.dox: version.stamp
        echo "PROJECT_NAME = \"Notmuch $(VERSION)\"" > $@
        echo "INPUT=${srcdir}/lib/notmuch.h" >> $@
 
-$(dir)/docdeps.mk: $(dir)/conf.py $(dir)/mkdocdeps.py
-       $(mkdocdeps) $(srcdir)/doc $(DOCBUILDDIR) $@
-
-CLEAN := $(CLEAN) $(DOCBUILDDIR) $(dir)/docdeps.mk $(DOCBUILDDIR)/.roff.stamp
+CLEAN := $(CLEAN) $(DOCBUILDDIR) $(DOCBUILDDIR)/.roff.stamp
 CLEAN := $(CLEAN) $(MAN_GZIP_FILES) $(MAN_ROFF_FILES) $(dir)/conf.pyc $(dir)/config.dox
index 356a2b2b9e0b32db596c4e8301b42977f565e606..a3d8269696a366dca12b1caa4a8d67e6005d993c 100644 (file)
@@ -52,74 +52,74 @@ htmlhelp_basename = 'notmuchdoc'
 # One entry per manual page. List of tuples
 # (source start file, name, description, authors, manual section).
 
-man_pages = [
-
-('man1/notmuch','notmuch',
-        u'thread-based email index, search, and tagging',
-        [u'Carl Worth and many others'], 1),
-
-('man1/notmuch-address','notmuch-address',
-        u'output addresses from matching messages',
-        [u'Carl Worth and many others'], 1),
+notmuch_authors = u'Carl Worth and many others'
 
-('man1/notmuch-compact','notmuch-compact',
-        u'compact the notmuch database',
-        [u'Carl Worth and many others'], 1),
+man_pages = [
+    ('man1/notmuch', 'notmuch',
+     u'thread-based email index, search, and tagging',
+     [notmuch_authors], 1),
 
-('man1/notmuch-config','notmuch-config',
-        u'access notmuch configuration file',
-        [u'Carl Worth and many others'], 1),
+    ('man1/notmuch-address', 'notmuch-address',
+     u'output addresses from matching messages',
+     [notmuch_authors], 1),
 
-('man1/notmuch-count','notmuch-count',
-        u'count messages matching the given search terms',
-        [u'Carl Worth and many others'], 1),
+    ('man1/notmuch-compact', 'notmuch-compact',
+     u'compact the notmuch database',
+     [notmuch_authors], 1),
 
-('man1/notmuch-dump','notmuch-dump',
-        u'creates a plain-text dump of the tags of each message',
-        [u'Carl Worth and many others'], 1),
+    ('man1/notmuch-config', 'notmuch-config',
+     u'access notmuch configuration file',
+     [notmuch_authors], 1),
 
-('man1/notmuch-emacs-mua','notmuch-emacs-mua',
-        u'send mail with notmuch and emacs',
-        [u'Carl Worth and many others'], 1),
+    ('man1/notmuch-count', 'notmuch-count',
+     u'count messages matching the given search terms',
+     [notmuch_authors], 1),
 
-('man5/notmuch-hooks','notmuch-hooks',
-        u'hooks for notmuch',
-        [u'Carl Worth and many others'], 5),
+    ('man1/notmuch-dump', 'notmuch-dump',
+     u'creates a plain-text dump of the tags of each message',
+     [notmuch_authors], 1),
 
-('man1/notmuch-insert','notmuch-insert',
-        u'add a message to the maildir and notmuch database',
-        [u'Carl Worth and many others'], 1),
+    ('man1/notmuch-emacs-mua', 'notmuch-emacs-mua',
+     u'send mail with notmuch and emacs',
+     [notmuch_authors], 1),
 
-('man1/notmuch-new','notmuch-new',
-        u'incorporate new mail into the notmuch database',
-        [u'Carl Worth and many others'], 1),
+    ('man5/notmuch-hooks', 'notmuch-hooks',
+     u'hooks for notmuch',
+     [notmuch_authors], 5),
 
-('man1/notmuch-reply','notmuch-reply',
-        u'constructs a reply template for a set of messages',
-        [u'Carl Worth and many others'], 1),
+    ('man1/notmuch-insert', 'notmuch-insert',
+     u'add a message to the maildir and notmuch database',
+     [notmuch_authors], 1),
 
-('man1/notmuch-restore','notmuch-restore',
-        u'restores the tags from the given file (see notmuch dump)',
-        [u'Carl Worth and many others'], 1),
+    ('man1/notmuch-new', 'notmuch-new',
+     u'incorporate new mail into the notmuch database',
+     [notmuch_authors], 1),
 
-('man1/notmuch-search','notmuch-search',
-        u'search for messages matching the given search terms',
-        [u'Carl Worth and many others'], 1),
+    ('man1/notmuch-reply', 'notmuch-reply',
+     u'constructs a reply template for a set of messages',
+     [notmuch_authors], 1),
 
-('man7/notmuch-search-terms','notmuch-search-terms',
-        u'syntax for notmuch queries',
-        [u'Carl Worth and many others'], 7),
+    ('man1/notmuch-restore', 'notmuch-restore',
+     u'restores the tags from the given file (see notmuch dump)',
+     [notmuch_authors], 1),
 
-('man1/notmuch-show','notmuch-show',
-        u'show messages matching the given search terms',
-        [u'Carl Worth and many others'], 1),
+    ('man1/notmuch-search', 'notmuch-search',
+     u'search for messages matching the given search terms',
+     [notmuch_authors], 1),
 
-('man1/notmuch-tag','notmuch-tag',
-        u'add/remove tags for all messages matching the search terms',
-        [u'Carl Worth and many others'], 1),
+    ('man7/notmuch-search-terms', 'notmuch-search-terms',
+     u'syntax for notmuch queries',
+     [notmuch_authors], 7),
 
+    ('man1/notmuch-show', 'notmuch-show',
+     u'show messages matching the given search terms',
+     [notmuch_authors], 1),
 
+    ('man1/notmuch-tag', 'notmuch-tag',
+     u'add/remove tags for all messages matching the search terms',
+     [notmuch_authors], 1),
 ]
+
 # If true, show URL addresses after external links.
 #man_show_urls = False
 
@@ -132,52 +132,19 @@ man_pages = [
 texinfo_no_detailmenu = True
 
 texinfo_documents = [
- ('notmuch-emacs', 'notmuch-emacs', u'notmuch Documentation',
-   u'Carl Worth and many others', 'notmuch-emacs',
-   'emacs based front-end for notmuch', 'Miscellaneous'),
-('man1/notmuch','notmuch',u'notmuch Documentation',
-      u'Carl Worth and many others', 'notmuch',
-      'thread-based email index, search, and tagging','Miscellaneous'),
-('man1/notmuch-address','notmuch-address',u'notmuch Documentation',
-      u'Carl Worth and many others', 'notmuch-address',
-      'output addresses from matching messages','Miscellaneous'),
-('man1/notmuch-compact','notmuch-compact',u'notmuch Documentation',
-      u'Carl Worth and many others', 'notmuch-compact',
-      'compact the notmuch database','Miscellaneous'),
-('man1/notmuch-config','notmuch-config',u'notmuch Documentation',
-      u'Carl Worth and many others', 'notmuch-config',
-      'access notmuch configuration file','Miscellaneous'),
-('man1/notmuch-count','notmuch-count',u'notmuch Documentation',
-      u'Carl Worth and many others', 'notmuch-count',
-      'count messages matching the given search terms','Miscellaneous'),
-('man1/notmuch-dump','notmuch-dump',u'notmuch Documentation',
-      u'Carl Worth and many others', 'notmuch-dump',
-      'creates a plain-text dump of the tags of each message','Miscellaneous'),
-('man5/notmuch-hooks','notmuch-hooks',u'notmuch Documentation',
-      u'Carl Worth and many others', 'notmuch-hooks',
-      'hooks for notmuch','Miscellaneous'),
-('man1/notmuch-insert','notmuch-insert',u'notmuch Documentation',
-      u'Carl Worth and many others', 'notmuch-insert',
-      'add a message to the maildir and notmuch database','Miscellaneous'),
-('man1/notmuch-new','notmuch-new',u'notmuch Documentation',
-      u'Carl Worth and many others', 'notmuch-new',
-      'incorporate new mail into the notmuch database','Miscellaneous'),
-('man1/notmuch-reply','notmuch-reply',u'notmuch Documentation',
-      u'Carl Worth and many others', 'notmuch-reply',
-      'constructs a reply template for a set of messages','Miscellaneous'),
-('man1/notmuch-restore','notmuch-restore',u'notmuch Documentation',
-      u'Carl Worth and many others', 'notmuch-restore',
-      'restores the tags from the given file (see notmuch dump)','Miscellaneous'),
-('man1/notmuch-search','notmuch-search',u'notmuch Documentation',
-      u'Carl Worth and many others', 'notmuch-search',
-      'search for messages matching the given search terms','Miscellaneous'),
-('man7/notmuch-search-terms','notmuch-search-terms',u'notmuch Documentation',
-      u'Carl Worth and many others', 'notmuch-search-terms',
-      'syntax for notmuch queries','Miscellaneous'),
-('man1/notmuch-show','notmuch-show',u'notmuch Documentation',
-      u'Carl Worth and many others', 'notmuch-show',
-      'show messages matching the given search terms','Miscellaneous'),
-('man1/notmuch-tag','notmuch-tag',u'notmuch Documentation',
-      u'Carl Worth and many others', 'notmuch-tag',
-      'add/remove tags for all messages matching the search terms','Miscellaneous'),
+    ('notmuch-emacs', 'notmuch-emacs', u'notmuch-emacs documentation',
+     notmuch_authors, 'notmuch-emacs',
+     'emacs based front-end for notmuch', 'Miscellaneous'),
 ]
+
+# generate texinfo list from man page list
+texinfo_documents += [
+    (
+        x[0],                          # source start file
+        x[1],                          # target name
+        x[1] + u' documentation',      # title
+        x[3][0],                       # author
+        x[1],                          # dir menu entry
+        x[2],                          # description
+        'Miscellaneous'                        # category
+    ) for x in man_pages]
index 7f7214b32fb3e25a91c0bb6bc5464d94b13c6254..446cefbd6758238e0ed27659cfd8b5b2e6a9594b 100644 (file)
@@ -64,11 +64,11 @@ Supported options for **address** include
             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.
+            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
index 5a517ebda95ac290a06f11783f38df73f45a37b2..7483b75f1727e0490b70c001604173815173bb5b 100644 (file)
@@ -129,14 +129,14 @@ The available configuration items are described below.
 
         Name (or full path) of gpg binary to use in verification and
         decryption of PGP/MIME messages.
-    
+
         Default: ``gpg``.
 
     **built_with.<name>**
 
-       Compile time feature <name>. Current possibilities include
-       "compact" (see **notmuch-compact(1)**)
-       and "field_processor" (see **notmuch-search-terms(7)**).
+        Compile time feature <name>. Current possibilities include
+        "compact" (see **notmuch-compact(1)**)
+        and "field_processor" (see **notmuch-search-terms(7)**).
 
     **query.<name>**
 
index 99de13a9871245af98f1ca55e8570179e116883e..90d852ae3adfc0f95971734dd5bf236dc7b2e9c6 100644 (file)
@@ -48,9 +48,9 @@ Supported options for **count** include
         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.
+        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
index 585702726bf155d9e8ecd569775565289ac26f12..ca048aebf01f99ddb0e3815f9c2a0693fdc69cee 100644 (file)
@@ -77,21 +77,21 @@ Supported options for **dump** include
 
       **config**
 
-       Output configuration data stored in the database. Each line
-       starts with "#@ ", followed by a space separated key-value
-       pair.  Both key and value are hex encoded if needed.
+        Output configuration data stored in the database. Each line
+        starts with "#@ ", followed by a space separated key-value
+        pair.  Both key and value are hex encoded if needed.
 
       **properties**
 
-       Output per-message (key,value) metadata.  Each line starts
-       with "#= ", followed by a message id, and a space separated
-       list of key=value pairs.  pair.  Ids, keys and values are hex
-       encoded if needed.
+        Output per-message (key,value) metadata.  Each line starts
+        with "#= ", followed by a message id, and a space separated
+        list of key=value pairs.  pair.  Ids, keys and values are hex
+        encoded if needed.
 
       **tags**
 
-       Output per-message boolean metadata, namely tags. See *format* above
-       for description of the output.
+        Output per-message boolean metadata, namely tags. See *format* above
+        for description of the output.
 
       The default is to include all available types of data.  The
       option can be specified multiple times to select some subset. As
index 7c5729047173815a08bb2ce0c10754120a528db3..87787e20e531157b6ebc18a6d5d852634edc8ae0 100644 (file)
@@ -5,15 +5,15 @@ notmuch-emacs-mua
 SYNOPSIS
 ========
 
-**notmuch-emacs-mua** [options ...] [<to-address> ...]
+**notmuch** **emacs-mua** [options ...] [<to-address> ... | <mailto-url>]
 
 DESCRIPTION
 ===========
 
 Start composing an email in the Notmuch Emacs UI with the specified
-subject, recipients, and message body.
+subject, recipients, and message body, or mailto: URL.
 
-Supported options for **notmuch-emacs-mua** include
+Supported options for **emacs-mua** include
 
     ``-h, --help``
         Display help.
@@ -33,6 +33,10 @@ Supported options for **notmuch-emacs-mua** include
     ``-i, --body=``\ <file>
         Specify a file to include into the body of the message.
 
+    ``--hello``
+        Go to the Notmuch hello screen instead of the message composition
+        window if no message composition parameters are given.
+
     ``--no-window-system``
         Even if a window system is available, use the current terminal.
 
@@ -56,7 +60,9 @@ Supported options for **notmuch-emacs-mua** include
         Output the resulting elisp to stdout instead of evaluating it.
 
 The supported positional parameters and short options are a compatible
-subset of the **mutt** MUA command-line options.
+subset of the **mutt** MUA command-line options. The options and
+positional parameters modifying the message can't be combined with the
+mailto: URL.
 
 Options may be specified multiple times.
 
index c681fa2d18bdf4dcd94a07b83a90005907585b84..cb68bc8a72b2dd4e673934c4c196c75760dd7238 100644 (file)
@@ -54,23 +54,23 @@ Supported options for **restore** include
 
       Control what kind of metadata is restored.
 
-       **config**
+        **config**
 
-         Restore configuration data to the database. Each configuration line starts
-         with "#@ ", followed by a space separated key-value pair.
-         Both key and value are hex encoded if needed.
+          Restore configuration data to the database. Each configuration line starts
+          with "#@ ", followed by a space separated key-value pair.
+          Both key and value are hex encoded if needed.
 
-       **properties**
+        **properties**
 
-         Output per-message (key,value) metadata.  Each line starts
-         with "#= ", followed by a message id, and a space separated
-         list of key=value pairs.  pair.  Ids, keys and values are
-         hex encoded if needed.
+          Output per-message (key,value) metadata.  Each line starts
+          with "#= ", followed by a message id, and a space separated
+          list of key=value pairs.  pair.  Ids, keys and values are
+          hex encoded if needed.
 
-       **tags**
+        **tags**
 
-         Output per-message metadata, namely tags. See *format* above
-         for more details.
+          Output per-message metadata, namely tags. See *format* above
+          for more details.
 
       The default is to restore all available types of data.  The
       option can be specified multiple times to select some subset.
diff --git a/doc/mkdocdeps.py b/doc/mkdocdeps.py
deleted file mode 100644 (file)
index b87fe3e..0000000
+++ /dev/null
@@ -1,18 +0,0 @@
-import sys
-
-srcdir = sys.argv[1]
-builddir = sys.argv[2]
-outfile = sys.argv[3]
-
-sys.path.insert(0, srcdir)
-import conf
-
-roff_files = []
-rst_files = []
-for page in conf.man_pages:
-    rst_files = rst_files + ["{0:s}/{1:s}.rst".format(srcdir,page[0])]
-    roff_files = roff_files + ["{0:s}/man/{1:s}.{2:d}".format(builddir,page[0],page[4])]
-
-with open(outfile, 'w') as out:
-    out.write('MAN_ROFF_FILES := ' + ' \\\n\t'.join(roff_files) + '\n')
-    out.write('MAN_RST_FILES := ' + ' \\\n\t'.join(rst_files) + '\n')
index d68542d349a017b1fd6acb2bfd7400a38385bd41..5e25996f69b4e9c7f03ab340bba941b78d4c45ef 100644 (file)
@@ -189,10 +189,12 @@ following key bindings:
 ``j``
     Jump to saved searches using :ref:`notmuch-jump`.
 
+.. _notmuch-jump:
+
 notmuch-jump
 ------------
 
-Saved searches configured through :ref:`notmuch-saved-searches` can
+Saved searches configured through :ref:`saved-searches` can
 include a "shortcut key" that's accessible through notmuch-jump.
 Pressing ``j`` anywhere in notmuch followed by the configured shortcut
 key of a saved search will immediately jump to that saved search.  For
index dfa7c1f11dcad859a7358fe6e3b049d53d880eb3..040e64d435724b450008260e9351e52b3551fdfe 100644 (file)
@@ -21,7 +21,10 @@ emacs_sources := \
        $(dir)/notmuch-print.el \
        $(dir)/notmuch-version.el \
        $(dir)/notmuch-jump.el \
-       $(dir)/notmuch-company.el
+       $(dir)/notmuch-company.el \
+       $(dir)/notmuch-draft.el
+
+elpa_sources := ${emacs_sources} $(dir)/notmuch-pkg.el
 
 $(dir)/notmuch-version.el: $(dir)/Makefile.local version.stamp
 $(dir)/notmuch-version.el: $(srcdir)/$(dir)/notmuch-version.el.tmpl
@@ -35,6 +38,9 @@ $(dir)/notmuch-pkg.el: $(srcdir)/$(dir)/notmuch-pkg.el.tmpl
 all: $(dir)/notmuch-pkg.el
 install-emacs: $(dir)/notmuch-pkg.el
 
+emacs_mua := $(dir)/notmuch-emacs-mua
+emacs_mua_desktop := $(dir)/notmuch-emacs-mua.desktop
+
 emacs_images := \
        $(srcdir)/$(dir)/notmuch-logo.png
 
@@ -72,6 +78,14 @@ ifeq ($(HAVE_EMACS),1)
        $(call quiet,EMACS) --directory emacs -batch -f batch-byte-compile $<
 endif
 
+elpa: $(ELPA_FILE)
+
+notmuch-emacs-%.tar: ${elpa_sources}
+       mkdir -p .elpa-build/notmuch-${ELPA_VERSION}
+       cp ${elpa_sources} .elpa-build/notmuch-${ELPA_VERSION}
+       tar -C .elpa-build -cf $@ notmuch-${ELPA_VERSION}
+       rm -r .elpa-build
+
 ifeq ($(WITH_EMACS),1)
 ifeq ($(HAVE_EMACS),1)
 all: $(emacs_bytecode)
@@ -90,5 +104,12 @@ ifeq ($(HAVE_EMACS),1)
 endif
        mkdir -p "$(DESTDIR)$(emacsetcdir)"
        install -m0644 $(emacs_images) "$(DESTDIR)$(emacsetcdir)"
+       mkdir -p "$(DESTDIR)$(prefix)/bin/"
+       install $(emacs_mua) "$(DESTDIR)$(prefix)/bin"
+ifeq ($(WITH_DESKTOP),1)
+       mkdir -p "$(DESTDIR)$(desktop_dir)"
+       desktop-file-install --mode 0644 --dir "$(DESTDIR)$(desktop_dir)" $(emacs_mua_desktop)
+       -update-desktop-database "$(DESTDIR)$(desktop_dir)"
+endif
 
 CLEAN := $(CLEAN) $(emacs_bytecode) $(dir)/notmuch-version.el $(dir)/notmuch-pkg.el
index 34793dbec99e235ae863ae63dbc6d79881ce3efb..d504ff2d9fd4e0a952116761104a28fb1a1d2602 100644 (file)
 
 (defvar notmuch-address-full-harvest-finished nil
   "t indicates that full completion address harvesting has been
-finished")
+finished. Use notmuch-address--harvest-ready to access as that
+will load a saved hash if necessary (and available).")
+
+(defun notmuch-address--harvest-ready ()
+  "Return t if there is a full address hash available.
+
+If the hash is not present it attempts to load a saved hash."
+  (or notmuch-address-full-harvest-finished
+      (notmuch-address--load-address-hash)))
 
 (defcustom notmuch-address-command 'internal
   "Determines how address completion candidates are generated.
@@ -58,6 +66,7 @@ disabled."
          (const :tag "Disable address completion" nil)
          (string :tag "Use external completion command"))
   :group 'notmuch-send
+  :group 'notmuch-address
   :group 'notmuch-external)
 
 (defcustom notmuch-address-internal-completion '(sent nil)
@@ -85,6 +94,19 @@ This should be a list of the form '(DIRECTION FILTER), where
         (setq notmuch-address-completions (clrhash notmuch-address-completions))
         (setq notmuch-address-full-harvest-finished nil))
   :group 'notmuch-send
+  :group 'notmuch-address
+  :group 'notmuch-external)
+
+(defcustom notmuch-address-save-filename nil
+  "Filename to save the cached completion addresses.
+
+All the addresses notmuch uses for address completion will be
+cached in this file. This has obvious privacy implications so you
+should make sure it is not somewhere publicly readable."
+  :type '(choice (const :tag "Off" nil)
+                (file :tag "Filename"))
+  :group 'notmuch-send
+  :group 'notmuch-address
   :group 'notmuch-external)
 
 (defcustom notmuch-address-selection-function 'notmuch-address-selection-function
@@ -96,8 +118,20 @@ See documentation of function `notmuch-address-selection-function'
 to know how address selection is made by default."
   :type 'function
   :group 'notmuch-send
+  :group 'notmuch-address
   :group 'notmuch-external)
 
+(defcustom notmuch-address-post-completion-functions nil
+  "Functions called after completing address.
+
+The completed address is passed as an argument to each function.
+Note that this hook will be invoked for completion in headers
+matching `notmuch-address-completion-headers-regexp'.
+"
+  :type 'hook
+  :group 'notmuch-address
+  :group 'notmuch-hooks)
+
 (defun notmuch-address-selection-function (prompt collection initial-input)
   "Call (`completing-read'
       PROMPT COLLECTION nil nil INITIAL-INPUT 'notmuch-address-history)"
@@ -115,7 +149,8 @@ to know how address selection is made by default."
 (defcustom notmuch-address-use-company t
   "If available, use company mode for address completion"
   :type 'boolean
-  :group 'notmuch-send)
+  :group 'notmuch-send
+  :group 'notmuch-address)
 
 (defun notmuch-address-setup ()
   (let* ((setup-company (and notmuch-address-use-company
@@ -159,7 +194,7 @@ elisp-based implementation or older implementation requiring
 external commands."
   (cond
    ((eq notmuch-address-command 'internal)
-    (when (not notmuch-address-full-harvest-finished)
+    (unless (notmuch-address--harvest-ready)
       ;; First, run quick synchronous harvest based on what the user
       ;; entered so far
       (notmuch-address-harvest original t))
@@ -194,12 +229,20 @@ external commands."
                    (t
                     (funcall notmuch-address-selection-function
                              (format "Address (%s matches): " num-options)
-                             (cdr options) (car options))))))
+                             ;; We put the first match as the initial
+                             ;; input; we put all the matches as
+                             ;; possible completions, moving the
+                             ;; first match to the end of the list
+                             ;; makes cursor up/down in the list work
+                             ;; better.
+                             (append (cdr options) (list (car options)))
+                             (car options))))))
       (if chosen
          (progn
            (push chosen notmuch-address-history)
            (delete-region beg end)
-           (insert chosen))
+           (insert chosen)
+           (run-hook-with-args 'notmuch-address-post-completion-functions chosen))
        (message "No matches.")
        (ding))))
    (t nil)))
@@ -304,6 +347,64 @@ execution, CALLBACK is called when harvesting finishes."
   ;; return value
   nil)
 
+(defvar notmuch-address--save-hash-version 1
+  "Version format of the save hash.")
+
+(defun notmuch-address--get-address-hash ()
+  "Returns the saved address hash as a plist.
+
+Returns nil if the save file does not exist, or it does not seem
+to be a saved address hash."
+  (when notmuch-address-save-filename
+    (condition-case nil
+       (with-temp-buffer
+         (insert-file-contents notmuch-address-save-filename)
+         (let ((name (read (current-buffer)))
+               (plist (read (current-buffer))))
+           ;; We do two simple sanity checks on the loaded file. We just
+           ;; check a version is specified, not that it is the current
+           ;; version, as we are allowed to over-write and a save-file with
+           ;; an older version.
+           (when (and (string= name "notmuch-address-hash")
+                      (plist-get plist :version))
+             plist)))
+      ;; The error case catches any of the reads failing.
+      (error nil))))
+
+(defun notmuch-address--load-address-hash ()
+  "Read the saved address hash and set the corresponding variables."
+  (let ((load-plist (notmuch-address--get-address-hash)))
+    (when (and load-plist
+              ;; If the user's setting have changed, or the version
+              ;; has changed, return nil to make sure the new settings
+              ;; take effect.
+              (equal (plist-get load-plist :completion-settings)
+                     notmuch-address-internal-completion)
+              (equal (plist-get load-plist :version)
+                     notmuch-address--save-hash-version))
+      (setq notmuch-address-last-harvest (plist-get load-plist :last-harvest)
+           notmuch-address-completions (plist-get load-plist :completions)
+           notmuch-address-full-harvest-finished t)
+      ;; Return t to say load was successful.
+      t)))
+
+(defun notmuch-address--save-address-hash ()
+  (when notmuch-address-save-filename
+    (if (or (not (file-exists-p notmuch-address-save-filename))
+             ;; The file exists, check it is a file we saved
+           (notmuch-address--get-address-hash))
+       (with-temp-file notmuch-address-save-filename
+         (let ((save-plist (list :version notmuch-address--save-hash-version
+                                 :completion-settings notmuch-address-internal-completion
+                                 :last-harvest notmuch-address-last-harvest
+                                 :completions notmuch-address-completions)))
+           (print "notmuch-address-hash" (current-buffer))
+           (print save-plist (current-buffer))))
+      (message "\
+Warning: notmuch-address-save-filename %s exists but doesn't
+appear to be an address savefile.  Not overwriting."
+              notmuch-address-save-filename))))
+
 (defun notmuch-address-harvest-trigger ()
   (let ((now (float-time)))
     (when (> (- now notmuch-address-last-harvest) 86400)
@@ -314,7 +415,9 @@ execution, CALLBACK is called when harvesting finishes."
                                 ;; again when the trigger is next
                                 ;; called
                                 (if (string= event "finished\n")
-                                    (setq notmuch-address-full-harvest-finished t)
+                                    (progn
+                                      (notmuch-address--save-address-hash)
+                                      (setq notmuch-address-full-harvest-finished t))
                                   (setq notmuch-address-last-harvest 0)))))))
 
 ;;
index 5d75c1455933e0548cb44fe47b26d718c4656a14..3e12e7a9f729984899dc32e5cbf2cb1b8e930710 100644 (file)
 (declare-function company-mode "company")
 (declare-function company-manual-begin "company")
 (defvar company-backends)
+(defvar company-idle-delay)
 
 (declare-function notmuch-address-harvest "notmuch-address")
 (declare-function notmuch-address-harvest-trigger "notmuch-address")
 (declare-function notmuch-address-matching "notmuch-address")
-(defvar notmuch-address-full-harvest-finished)
+(declare-function notmuch-address--harvest-ready "notmuch-address")
 (defvar notmuch-address-completion-headers-regexp)
+(defvar notmuch-address-command)
 
 ;;;###autoload
 (defun notmuch-company-setup ()
@@ -70,7 +72,7 @@
                                 (line-beginning-position))
                   (setq notmuch-company-last-prefix (company-grab "[:,][ \t]*\\(.*\\)" 1 (point-at-bol)))))
       (candidates (cond
-                  (notmuch-address-full-harvest-finished
+                  ((notmuch-address--harvest-ready)
                    ;; Update harvested addressed from time to time
                    (notmuch-address-harvest-trigger)
                    (notmuch-address-matching arg))
@@ -87,6 +89,7 @@
       (match (if (string-match notmuch-company-last-prefix arg)
                 (match-end 0)
               0))
+      (post-completion (run-hook-with-args 'notmuch-address-post-completion-functions arg))
       (no-cache t))))
 
 
index e376aa8022d237caee6781eb29eb28e4475dd2bd..68a7e9f3735a814ce70b55948c3cf6ade687aea9 100644 (file)
@@ -42,7 +42,12 @@ mode."
   :group 'notmuch-crypto)
 
 (defface notmuch-crypto-part-header
-  '((t (:foreground "blue")))
+  '((((class color)
+      (background dark))
+     (:foreground "LightBlue1"))
+    (((class color)
+      (background light))
+     (:foreground "blue")))
   "Face used for crypto parts headers."
   :group 'notmuch-crypto
   :group 'notmuch-faces)
diff --git a/emacs/notmuch-draft.el b/emacs/notmuch-draft.el
new file mode 100644 (file)
index 0000000..fb7f4f5
--- /dev/null
@@ -0,0 +1,267 @@
+;;; notmuch-draft.el --- functions for postponing and editing drafts
+;;
+;; Copyright Â© Mark Walters
+;; Copyright Â© David Bremner
+;;
+;; This file is part of Notmuch.
+;;
+;; Notmuch is free software: you can redistribute it and/or modify it
+;; under the terms of the GNU General Public License as published by
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
+;;
+;; Notmuch is distributed in the hope that it will be useful, but
+;; WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+;; General Public License for more details.
+;;
+;; You should have received a copy of the GNU General Public License
+;; along with Notmuch.  If not, see <https://www.gnu.org/licenses/>.
+;;
+;; Authors: Mark Walters <markwalters1009@gmail.com>
+;;         David Bremner <david@tethera.net>
+
+;;; Code:
+
+(require 'notmuch-maildir-fcc)
+(require 'notmuch-tag)
+
+(declare-function notmuch-show-get-message-id "notmuch-show" (&optional bare))
+(declare-function notmuch-message-mode "notmuch-mua")
+
+(defgroup notmuch-draft nil
+  "Saving and editing drafts in Notmuch."
+  :group 'notmuch)
+
+(defcustom notmuch-draft-tags '("+draft")
+  "List of tags changes to apply to a draft message when it is saved in the database.
+
+Tags starting with \"+\" (or not starting with either \"+\" or
+\"-\") in the list will be added, and tags starting with \"-\"
+will be removed from the message being stored.
+
+For example, if you wanted to give the message a \"draft\" tag
+but not the (normally added by default) \"inbox\" tag, you would
+set:
+    (\"+draft\" \"-inbox\")"
+  :type '(repeat string)
+  :group 'notmuch-draft)
+
+(defcustom notmuch-draft-folder "drafts"
+  "Folder to save draft messages in.
+
+This should be specified relative to the root of the notmuch
+database. It will be created if necessary."
+  :type 'string
+  :group 'notmuch-draft)
+
+(defcustom notmuch-draft-quoted-tags '()
+  "Mml tags to quote.
+
+This should be a list of mml tags to quote before saving. You do
+not need to include \"secure\" as that is handled separately.
+
+If you include \"part\" then attachments will not be saved with
+the draft -- if not then they will be saved with the draft. The
+former means the attachments may not still exist when you resume
+the message, the latter means that the attachments as they were
+when you postponed will be sent with the resumed message.
+
+Note you may get strange results if you change this between
+postponing and resuming a message."
+  :type '(repeat string)
+  :group 'notmuch-send)
+
+(defcustom notmuch-draft-save-plaintext 'ask
+  "Should notmuch save/postpone in plaintext messages that seem
+  like they are intended to be sent encrypted
+(i.e with an mml encryption tag in it)."
+  :type '(radio
+         (const :tag "Never" nil)
+         (const :tag "Ask every time" ask)
+         (const :tag "Always" t))
+  :group 'notmuch-draft
+  :group 'notmuch-crypto)
+
+(defvar notmuch-draft-encryption-tag-regex
+  "<#\\(part encrypt\\|secure.*mode=.*encrypt>\\)"
+  "Regular expression matching mml tags indicating encryption of part or message")
+
+(defvar notmuch-draft-id nil
+  "Message-id of the most recent saved draft of this message")
+(make-variable-buffer-local 'notmuch-draft-id)
+
+(defun notmuch-draft--mark-deleted ()
+  "Tag the last saved draft deleted.
+
+Used when a new version is saved, or the message is sent."
+  (when notmuch-draft-id
+    (notmuch-tag notmuch-draft-id '("+deleted"))))
+
+(defun notmuch-draft-quote-some-mml ()
+  "Quote the mml tags in `notmuch-draft-quoted-tags`."
+  (save-excursion
+    ;; First we deal with any secure tag separately.
+    (message-goto-body)
+    (when (looking-at "<#secure[^\n]*>\n")
+      (let ((secure-tag (match-string 0)))
+       (delete-region (match-beginning 0) (match-end 0))
+       (message-add-header (concat "X-Notmuch-Emacs-Secure: " secure-tag))))
+    ;; This is copied from mml-quote-region but only quotes the
+    ;; specified tags.
+    (when notmuch-draft-quoted-tags
+      (let ((re (concat "<#!*/?\\("
+                       (mapconcat 'regexp-quote notmuch-draft-quoted-tags "\\|")
+                       "\\)")))
+       (message-goto-body)
+       (while (re-search-forward re nil t)
+         ;; Insert ! after the #.
+         (goto-char (+ (match-beginning 0) 2))
+         (insert "!"))))))
+
+(defun notmuch-draft-unquote-some-mml ()
+  "Unquote the mml tags in `notmuch-draft-quoted-tags`."
+  (save-excursion
+    (when notmuch-draft-quoted-tags
+      (let ((re (concat "<#!+/?\\("
+                       (mapconcat 'regexp-quote notmuch-draft-quoted-tags "\\|")
+                       "\\)")))
+       (message-goto-body)
+       (while (re-search-forward re nil t)
+         ;; Remove one ! from after the #.
+         (goto-char (+ (match-beginning 0) 2))
+         (delete-char 1))))
+    (let (secure-tag)
+      (save-restriction
+       (message-narrow-to-headers)
+       (setq secure-tag (message-fetch-field "X-Notmuch-Emacs-Secure" 't))
+       (message-remove-header "X-Notmuch-Emacs-Secure"))
+      (message-goto-body)
+      (when secure-tag
+       (insert secure-tag "\n")))))
+
+(defun notmuch-draft--has-encryption-tag ()
+  "Returns t if there is an mml secure tag."
+  (save-excursion
+    (message-goto-body)
+    (re-search-forward notmuch-draft-encryption-tag-regex nil 't)))
+
+(defun notmuch-draft--query-encryption ()
+  "Checks if we should save a message that should be encrypted.
+
+`notmuch-draft-save-plaintext' controls the behaviour."
+  (case notmuch-draft-save-plaintext
+       ((ask)
+        (unless (yes-or-no-p "(Customize `notmuch-draft-save-plaintext' to avoid this warning)
+This message contains mml tags that suggest it is intended to be encrypted.
+Really save and index an unencrypted copy? ")
+          (error "Save aborted")))
+       ((nil)
+        (error "Refusing to save draft with encryption tags (see `notmuch-draft-save-plaintext')"))
+       ((t)
+        (ignore))))
+
+(defun notmuch-draft--make-message-id ()
+  ;; message-make-message-id gives the id inside a "<" ">" pair,
+  ;; but notmuch doesn't want that form, so remove them.
+  (concat "draft-" (substring (message-make-message-id) 1 -1)))
+
+(defun notmuch-draft-save ()
+  "Save the current draft message in the notmuch database.
+
+This saves the current message in the database with tags
+`notmuch-draft-tags` (in addition to any default tags
+applied to newly inserted messages)."
+  (interactive)
+  (when (notmuch-draft--has-encryption-tag)
+    (notmuch-draft--query-encryption))
+  (let ((id (notmuch-draft--make-message-id)))
+    (with-temporary-notmuch-message-buffer
+     ;; We insert a Date header and a Message-ID header, the former
+     ;; so that it is easier to search for the message, and the
+     ;; latter so we have a way of accessing the saved message (for
+     ;; example to delete it at a later time). We check that the
+     ;; user has these in `message-deletable-headers` (the default)
+     ;; as otherwise they are doing something strange and we
+     ;; shouldn't interfere. Note, since we are doing this in a new
+     ;; buffer we don't change the version in the compose buffer.
+     (cond
+      ((member 'Message-ID message-deletable-headers)
+       (message-remove-header "Message-ID")
+       (message-add-header (concat "Message-ID: <" id ">")))
+      (t
+       (message "You have customized emacs so Message-ID is not a deletable header, so not changing it")
+       (setq id nil)))
+     (cond
+      ((member 'Date message-deletable-headers)
+       (message-remove-header "Date")
+       (message-add-header (concat "Date: " (message-make-date))))
+      (t
+       (message "You have customized emacs so Date is not a deletable header, so not changing it")))
+     (message-add-header "X-Notmuch-Emacs-Draft: True")
+     (notmuch-draft-quote-some-mml)
+     (notmuch-maildir-setup-message-for-saving)
+     (notmuch-maildir-notmuch-insert-current-buffer
+      notmuch-draft-folder 't notmuch-draft-tags))
+    ;; We are now back in the original compose buffer. Note the
+    ;; function notmuch-call-notmuch-process (called by
+    ;; notmuch-maildir-notmuch-insert-current-buffer) signals an error
+    ;; on failure, so to get to this point it must have
+    ;; succeeded. Also, notmuch-draft-id is still the id of the
+    ;; previous draft, so it is safe to mark it deleted.
+    (notmuch-draft--mark-deleted)
+    (setq notmuch-draft-id (concat "id:" id))
+    (set-buffer-modified-p nil)))
+
+(defun notmuch-draft-postpone ()
+  "Save the draft message in the notmuch database and exit buffer."
+  (interactive)
+  (notmuch-draft-save)
+  (kill-buffer))
+
+(defun notmuch-draft-resume (id)
+  "Resume editing of message with id ID."
+  (let* ((tags (process-lines notmuch-command "search" "--output=tags"
+                             "--exclude=false" id))
+        (draft (equal tags (notmuch-update-tags tags notmuch-draft-tags))))
+    (when (or draft
+             (yes-or-no-p "Message does not appear to be a draft: really resume? "))
+      (switch-to-buffer (get-buffer-create (concat "*notmuch-draft-" id "*")))
+      (setq buffer-read-only nil)
+      (erase-buffer)
+      (let ((coding-system-for-read 'no-conversion))
+       (call-process notmuch-command nil t nil "show" "--format=raw" id))
+      (mime-to-mml)
+      (goto-char (point-min))
+      (when (re-search-forward "^$" nil t)
+       (replace-match mail-header-separator t t))
+      ;; Remove the Date and Message-ID headers (unless the user has
+      ;; explicitly customized emacs to tell us not to) as they will
+      ;; be replaced when the message is sent.
+      (save-restriction
+       (message-narrow-to-headers)
+       (when (member 'Message-ID message-deletable-headers)
+         (message-remove-header "Message-ID"))
+       (when (member 'Date message-deletable-headers)
+         (message-remove-header "Date"))
+       ;; The X-Notmuch-Emacs-Draft header is a more reliable
+       ;; indication of whether the message really is a draft.
+       (setq draft (> (message-remove-header "X-Notmuch-Emacs-Draft") 0)))
+      ;; If the message is not a draft we should not unquote any mml.
+      (when draft
+       (notmuch-draft-unquote-some-mml))
+      (notmuch-message-mode)
+      (message-goto-body)
+      (set-buffer-modified-p nil)
+      ;; If the resumed message was a draft then set the draft
+      ;; message-id so that we can delete the current saved draft if the
+      ;; message is resaved or sent.
+      (setq notmuch-draft-id (when draft id)))))
+
+
+(add-hook 'message-send-hook 'notmuch-draft--mark-deleted)
+
+
+(provide 'notmuch-draft)
+
+;;; notmuch-draft.el ends here
diff --git a/emacs/notmuch-emacs-mua b/emacs/notmuch-emacs-mua
new file mode 100755 (executable)
index 0000000..a521497
--- /dev/null
@@ -0,0 +1,179 @@
+#!/usr/bin/env bash
+#
+# notmuch-emacs-mua - start composing a mail on the command line
+#
+# Copyright Â© 2014 Jani Nikula
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see https://www.gnu.org/licenses/ .
+#
+# Authors: Jani Nikula <jani@nikula.org>
+#
+
+set -eu
+
+# escape: "expand" '\' as '\\' and '"' as '\"'
+# calling convention: escape -v var "$arg" (like in bash printf).
+escape ()
+{
+    local __escape_arg__=${3//\\/\\\\}
+    printf -v $2 '%s' "${__escape_arg__//\"/\\\"}"
+}
+
+EMACS=${EMACS:-emacs}
+EMACSCLIENT=${EMACSCLIENT:-emacsclient}
+
+PRINT_ONLY=
+NO_WINDOW=
+USE_EMACSCLIENT=
+AUTO_DAEMON=
+CREATE_FRAME=
+ELISP=
+MAILTO=
+HELLO=
+
+# Short options compatible with mutt(1).
+while getopts :s:c:b:i:h opt; do
+    # Handle errors and long options.
+    case "${opt}" in
+       :)
+           echo "$0: short option -${OPTARG} requires an argument." >&2
+           exit 1
+           ;;
+       \?)
+           opt=$1
+           if [ "${OPTARG}" != "-" ]; then
+               echo "$0: unknown short option -${OPTARG}." >&2
+               exit 1
+           fi
+
+           case "${opt}" in
+               # Long options with arguments.
+               --subject=*|--to=*|--cc=*|--bcc=*|--body=*)
+                   OPTARG=${opt#--*=}
+                   opt=${opt%%=*}
+                   ;;
+               # Long options without arguments.
+               --help|--print|--no-window-system|--client|--auto-daemon|--create-frame|--hello)
+                   ;;
+               *)
+                   echo "$0: unknown long option ${opt}, or argument mismatch." >&2
+                   exit 1
+                   ;;
+           esac
+           # getopts does not do this for what it considers errors.
+           OPTIND=$((OPTIND + 1))
+           ;;
+    esac
+
+    escape -v OPTARG "${OPTARG-none}"
+
+    case "${opt}" in
+       --help|h)
+           exec man notmuch-emacs-mua
+           ;;
+       --subject|s)
+           ELISP="${ELISP} (message-goto-subject) (insert \"${OPTARG}\")"
+           ;;
+       --to)
+           ELISP="${ELISP} (message-goto-to) (insert \"${OPTARG}, \")"
+           ;;
+       --cc|c)
+           ELISP="${ELISP} (message-goto-cc) (insert \"${OPTARG}, \")"
+           ;;
+       --bcc|b)
+           ELISP="${ELISP} (message-goto-bcc) (insert \"${OPTARG}, \")"
+           ;;
+       --body|i)
+           ELISP="${ELISP} (message-goto-body) (insert-file \"${OPTARG}\")"
+           ;;
+       --print)
+           PRINT_ONLY=1
+           ;;
+       --no-window-system)
+           NO_WINDOW="-nw"
+           ;;
+       --client)
+           USE_EMACSCLIENT="yes"
+           ;;
+       --auto-daemon)
+           AUTO_DAEMON="--alternate-editor="
+           CREATE_FRAME="-c"
+           ;;
+       --create-frame)
+           CREATE_FRAME="-c"
+           ;;
+       --hello)
+           HELLO=1
+           ;;
+       *)
+           # We should never end up here.
+           echo "$0: internal error (option ${opt})." >&2
+           exit 1
+           ;;
+    esac
+
+    shift $((OPTIND - 1))
+    OPTIND=1
+done
+
+# Positional parameters.
+for arg; do
+    escape -v arg "${arg}"
+    case $arg in
+       mailto:*)
+           if [ -n "${MAILTO}" ]; then
+               echo "$0: more than one mailto: argument." >&2
+               exit 1
+           fi
+           MAILTO="${arg}"
+           ;;
+       *)
+           ELISP="${ELISP} (message-goto-to) (insert \"${arg}, \")"
+           ;;
+    esac
+done
+
+if [ -n "${MAILTO}" ]; then
+    if [ -n "${ELISP}" ]; then
+       echo "$0: mailto: is not compatible with other message parameters." >&2
+       exit 1
+    fi
+    ELISP="(browse-url-mail \"${MAILTO}\")"
+elif [ -z "${ELISP}" -a -n "${HELLO}" ]; then
+    ELISP="(notmuch)"
+else
+    ELISP="(notmuch-mua-new-mail) ${ELISP}"
+fi
+
+# Kill the terminal/frame if we're creating one.
+if [ -z "$USE_EMACSCLIENT" -o -n "$CREATE_FRAME" -o -n "$NO_WINDOW" ]; then
+    ELISP="${ELISP} (message-add-action #'save-buffers-kill-terminal 'exit)"
+fi
+
+escape -v pwd "$PWD"
+
+# The crux of it all: construct an elisp progn and eval it.
+ELISP="(prog1 'done (require 'notmuch) (cd \"$pwd\") ${ELISP})"
+
+if [ -n "$PRINT_ONLY" ]; then
+    echo ${ELISP}
+    exit 0
+fi
+
+if [ -n "$USE_EMACSCLIENT" ]; then
+    # Evaluate the progn.
+    exec ${EMACSCLIENT} ${NO_WINDOW} ${CREATE_FRAME} ${AUTO_DAEMON} --eval "${ELISP}"
+else
+    exec ${EMACS} ${NO_WINDOW} --eval "${ELISP}"
+fi
diff --git a/emacs/notmuch-emacs-mua.desktop b/emacs/notmuch-emacs-mua.desktop
new file mode 100644 (file)
index 0000000..f160047
--- /dev/null
@@ -0,0 +1,7 @@
+[Desktop Entry]
+Name=Notmuch (emacs interface)
+Exec=emacs -f notmuch
+Icon=emblem-mail
+Terminal=false
+Type=Application
+Categories=Network;Email;
index d582bff7a4914588a7e6f62c89728139a4a93bba..c858a20b6cc751d73d457272b0586da3d20e49db 100644 (file)
@@ -604,10 +604,11 @@ with `notmuch-hello-query-counts'."
 
 (defimage notmuch-hello-logo ((:type png :file "notmuch-logo.png")))
 
-(defun notmuch-hello-update (&optional no-display)
-  "Update the current notmuch view."
+(defun notmuch-hello-update ()
+  "Update the notmuch-hello buffer."
   ;; Lazy - rebuild everything.
-  (notmuch-hello no-display))
+  (interactive)
+  (notmuch-hello t))
 
 (defun notmuch-hello-window-configuration-change ()
   "Hook function to update the hello buffer when it is switched to."
index 963253c972656e2fe67e44b424e2397e69048da8..3e20b8c7afc68e6b9d56a709ecc74283b173ae38 100644 (file)
@@ -104,7 +104,7 @@ not appear in the pop-up buffer.
           (copy-sequence minibuffer-prompt-properties)
           'face))
         ;; Build the keymap with our bindings
-        (minibuffer-map (notmuch-jump--make-keymap action-map))
+        (minibuffer-map (notmuch-jump--make-keymap action-map prompt))
         ;; The bindings save the the action in notmuch-jump--action
         (notmuch-jump--action nil))
     ;; Read the action
@@ -161,18 +161,47 @@ buffer."
     (set-keymap-parent map minibuffer-local-map)
     ;; Make this like a special-mode keymap, with no self-insert-command
     (suppress-keymap map)
+    (define-key map (kbd "DEL") 'exit-minibuffer)
     map)
   "Base keymap for notmuch-jump's minibuffer keymap.")
 
-(defun notmuch-jump--make-keymap (action-map)
+(defun notmuch-jump--make-keymap (action-map prompt)
   "Translate ACTION-MAP into a minibuffer keymap."
   (let ((map (make-sparse-keymap)))
     (set-keymap-parent map notmuch-jump-minibuffer-map)
     (dolist (action action-map)
-      (define-key map (first action)
-       `(lambda () (interactive)
-          (setq notmuch-jump--action ',(third action))
-          (exit-minibuffer))))
+      (if (= (length (first action)) 1)
+         (define-key map (first action)
+           `(lambda () (interactive)
+              (setq notmuch-jump--action ',(third action))
+              (exit-minibuffer)))))
+    ;; By doing this in two passes (and checking if we already have a
+    ;; binding) we avoid problems if the user specifies a binding which
+    ;; is a prefix of another binding.
+    (dolist (action action-map)
+      (if (> (length (first action)) 1)
+         (let* ((key (elt (first action) 0))
+                (keystr (string key))
+                (new-prompt (concat prompt (format-kbd-macro keystr) " "))
+                (action-submap nil))
+           (unless (lookup-key map keystr)
+             (dolist (act action-map)
+               (when (= key (elt (first act) 0))
+                 (push (list (substring (first act) 1)
+                             (second act)
+                             (third act))
+                       action-submap)))
+             ;; We deal with backspace specially
+             (push (list (kbd "DEL")
+                         "Backup"
+                         (apply-partially #'notmuch-jump action-map prompt))
+                   action-submap)
+             (setq action-submap (nreverse action-submap))
+             (define-key map keystr
+               `(lambda () (interactive)
+                  (setq notmuch-jump--action
+                        ',(apply-partially #'notmuch-jump action-submap new-prompt))
+                  (exit-minibuffer)))))))
     map))
 
 ;;
index 23bd81c1d0e7251d69506ea0564e5ab489b1c647..337b20ace74f36b91b5ce76614f7fc3552a54ac5 100644 (file)
 
 (custom-add-to-group 'notmuch-send 'message 'custom-group)
 
+(defgroup notmuch-tag nil
+  "Tags and tagging in Notmuch."
+  :group 'notmuch)
+
 (defgroup notmuch-crypto nil
   "Processing and display of cryptographic MIME parts."
   :group 'notmuch)
   "Running external commands from within Notmuch."
   :group 'notmuch)
 
+(defgroup notmuch-address nil
+  "Address completion."
+  :group 'notmuch)
+
 (defgroup notmuch-faces nil
   "Graphical attributes for displaying text"
   :group 'notmuch)
@@ -148,6 +156,7 @@ For example, if you wanted to remove an \"inbox\" tag and add an
     (define-key map "z" 'notmuch-tree)
     (define-key map "m" 'notmuch-mua-new-mail)
     (define-key map "=" 'notmuch-refresh-this-buffer)
+    (define-key map (kbd "M-=") 'notmuch-refresh-all-buffers)
     (define-key map "G" 'notmuch-poll-and-refresh-this-buffer)
     (define-key map "j" 'notmuch-jump-search)
     map)
@@ -414,10 +423,8 @@ of its command symbol."
   "Refresh the current buffer."
   (interactive)
   (when notmuch-buffer-refresh-function
-    (if (commandp notmuch-buffer-refresh-function)
-       ;; Pass prefix argument, etc.
-       (call-interactively notmuch-buffer-refresh-function)
-      (funcall notmuch-buffer-refresh-function))))
+    ;; Pass prefix argument, etc.
+    (call-interactively notmuch-buffer-refresh-function)))
 
 (defun notmuch-poll-and-refresh-this-buffer ()
   "Invoke `notmuch-poll' to import mail, then refresh the current buffer."
@@ -425,6 +432,21 @@ of its command symbol."
   (notmuch-poll)
   (notmuch-refresh-this-buffer))
 
+(defun notmuch-refresh-all-buffers ()
+  "Invoke `notmuch-refresh-this-buffer' on all notmuch major-mode buffers.
+
+The buffers are silently refreshed, i.e. they are not forced to
+be displayed."
+  (interactive)
+  (dolist (buffer (buffer-list))
+    (let ((buffer-mode (buffer-local-value 'major-mode buffer)))
+      (when (memq buffer-mode '(notmuch-show-mode
+                               notmuch-tree-mode
+                               notmuch-search-mode
+                               notmuch-hello-mode))
+       (with-current-buffer buffer
+         (notmuch-refresh-this-buffer))))))
+
 (defun notmuch-prettify-subject (subject)
   ;; This function is used by `notmuch-search-process-filter' which
   ;; requires that we not disrupt its' matching state.
index a754b60c7ea3d828202b89482cc52c5785cd6599..777658ccfb4aa89124910448f0bc519d7f12edfa 100644 (file)
@@ -276,7 +276,7 @@ If CREATE is non-nil then create the folder if necessary."
 (defun notmuch-maildir-fcc-make-uniq-maildir-id ()
    (let* ((ftime (float-time))
          (microseconds (mod (* 1000000 ftime) 1000000))
-         (hostname (notmuch-maildir-fcc-host-fixer system-name)))
+         (hostname (notmuch-maildir-fcc-host-fixer (system-name))))
      (setq notmuch-maildir-fcc-count (+ notmuch-maildir-fcc-count 1))
      (format "%d.%d_%d_%d.%s"
             ftime
index 55bc267205756392fdbd6f243a420e0762b6c0a5..93747b1cb280c94ded422145b04a94094cf76dd9 100644 (file)
 
 (require 'notmuch-lib)
 (require 'notmuch-address)
+(require 'notmuch-draft)
 
 (eval-when-compile (require 'cl))
 
 (declare-function notmuch-show-insert-body "notmuch-show" (msg body depth))
 (declare-function notmuch-fcc-header-setup "notmuch-maildir-fcc" ())
 (declare-function notmuch-maildir-message-do-fcc "notmuch-maildir-fcc" ())
+(declare-function notmuch-draft-postpone "notmuch-draft" ())
+(declare-function notmuch-draft-save "notmuch-draft" ())
 
 ;;
 
@@ -251,6 +254,10 @@ mutiple parts get a header."
                       (notmuch-show-max-text-part-size 0)
                       ;; Insert headers for parts as appropriate for replying.
                       (notmuch-show-insert-header-p-function notmuch-mua-reply-insert-header-p-function)
+                      ;; Ensure that any encrypted parts are
+                      ;; decrypted during the generation of the reply
+                      ;; text.
+                      (notmuch-show-process-crypto process-crypto)
                       ;; Don't indent multipart sub-parts.
                       (notmuch-show-indent-multipart nil))
                    ;; We don't want sigstatus buttons (an information leak and usually wrong anyway).
@@ -285,6 +292,8 @@ mutiple parts get a header."
 
 (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)
+(define-key notmuch-message-mode-map (kbd "C-c C-p") #'notmuch-draft-postpone)
+(define-key notmuch-message-mode-map (kbd "C-x C-s") #'notmuch-draft-save)
 
 (defun notmuch-mua-pop-to-buffer (name switch-function)
   "Pop to buffer NAME, and warn if it already exists and is
@@ -490,15 +499,64 @@ will be addressed to all recipients of the source message."
     (notmuch-mua-reply query-string sender reply-all)
     (deactivate-mark)))
 
+(defun notmuch-mua-check-no-misplaced-secure-tag ()
+  "Query user if there is a misplaced secure mml tag.
+
+Emacs message-send will (probably) ignore a secure mml tag unless
+it is at the start of the body. Returns t if there is no such
+tag, or the user confirms they mean it."
+  (save-excursion
+    (let ((body-start (progn (message-goto-body) (point))))
+      (goto-char (point-max))
+      (or
+       ;; We are always fine if there is no secure tag.
+       (not (search-backward "<#secure" nil 't))
+       ;; There is a secure tag, so it must be at the start of the
+       ;; body, with no secure tag earlier (i.e., in the headers).
+       (and (= (point) body-start)
+           (not (search-backward "<#secure" nil 't)))
+       ;; The user confirms they means it.
+       (yes-or-no-p "\
+There is a <#secure> tag not at the start of the body. It is
+likely that the message will be sent unsigned and unencrypted.
+Really send? ")))))
+
+(defun notmuch-mua-check-secure-tag-has-newline ()
+  "Query if the secure mml tag has a newline following it.
+
+Emacs message-send will (probably) ignore a correctly placed
+secure mml tag unless it is followed by a newline. Returns t if
+any secure tag is followed by a newline, or the user confirms
+they mean it."
+  (save-excursion
+    (message-goto-body)
+    (or
+     ;; There is no (correctly placed) secure tag.
+     (not (looking-at "<#secure"))
+     ;; The secure tag is followed by a newline.
+     (looking-at "<#secure[^\n>]*>\n")
+     ;; The user confirms they means it.
+     (yes-or-no-p "\
+The <#secure> tag at the start of the body is not followed by a
+newline. It is likely that the message will be sent unsigned and
+unencrypted.  Really send? "))))
+
+(defun notmuch-mua-send-common (arg &optional exit)
+  (interactive "P")
+  (when (and (notmuch-mua-check-no-misplaced-secure-tag)
+            (notmuch-mua-check-secure-tag-has-newline))
+    (letf (((symbol-function 'message-do-fcc) #'notmuch-maildir-message-do-fcc))
+         (if exit
+             (message-send-and-exit arg)
+           (message-send arg)))))
+
 (defun notmuch-mua-send-and-exit (&optional arg)
   (interactive "P")
-  (letf (((symbol-function 'message-do-fcc) #'notmuch-maildir-message-do-fcc))
-       (message-send-and-exit arg)))
+  (notmuch-mua-send-common arg 't))
 
 (defun notmuch-mua-send (&optional arg)
   (interactive "P")
-  (letf (((symbol-function 'message-do-fcc) #'notmuch-maildir-message-do-fcc))
-       (message-send arg)))
+  (notmuch-mua-send-common arg))
 
 (defun notmuch-mua-kill-buffer ()
   (interactive)
index e7d16f81da5fd6fcab37cbb6b85d77fed62b9143..c670160d93f4445cd622e6e10e7d5d23dddd4f02 100644 (file)
@@ -38,6 +38,7 @@
 (require 'notmuch-mua)
 (require 'notmuch-crypto)
 (require 'notmuch-print)
+(require 'notmuch-draft)
 
 (declare-function notmuch-call-notmuch-process "notmuch" (&rest args))
 (declare-function notmuch-search-next-thread "notmuch" nil)
@@ -50,6 +51,7 @@
                  (&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))
+(declare-function notmuch-draft-resume "notmuch-draft" (id))
 
 (defcustom notmuch-message-headers '("Subject" "To" "Cc" "Date")
   "Headers that should be shown in a message, in this order.
@@ -1225,7 +1227,15 @@ matched."
   (interactive "sNotmuch show: \nP")
   (let ((buffer-name (generate-new-buffer-name
                      (or buffer-name
-                         (concat "*notmuch-" thread-id "*")))))
+                         (concat "*notmuch-" thread-id "*"))))
+       ;; We override mm-inline-override-types to stop application/*
+       ;; parts from being displayed unless the user has customized
+       ;; it themselves.
+       (mm-inline-override-types
+        (if (equal mm-inline-override-types
+                   (eval (car (get 'mm-inline-override-types 'standard-value))))
+            (cons "application/*" mm-inline-override-types)
+          mm-inline-override-types)))
     (switch-to-buffer (get-buffer-create buffer-name))
     ;; No need to track undo information for this buffer.
     (setq buffer-undo-list t)
@@ -1263,6 +1273,18 @@ matched."
        (message "No messages matched the query!")
        nil))))
 
+(defun notmuch-show--build-queries (thread context)
+  "Return a list of queries to try for this search.
+
+THREAD and CONTEXT are both strings, though CONTEXT may be nil.
+When CONTEXT is not nil, the first query is the conjunction of it
+and THREAD.  The next query is THREAD alone, and serves as a
+fallback if the prior matches no messages."
+  (let (queries)
+    (push (list thread) queries)
+    (if context (push (list thread "and (" context ")") queries))
+    queries))
+
 (defun notmuch-show--build-buffer (&optional state)
   "Display messages matching the current buffer context.
 
@@ -1270,25 +1292,20 @@ Apply the previously saved STATE if supplied, otherwise show the
 first relevant message.
 
 If no messages match the query return NIL."
-  (let* ((basic-args (list notmuch-show-thread-id))
-        (args (if notmuch-show-query-context
-                  (append (list "\'") basic-args
-                          (list "and (" notmuch-show-query-context ")\'"))
-                (append (list "\'") basic-args (list "\'"))))
-        (cli-args (cons "--exclude=false"
+  (let* ((cli-args (cons "--exclude=false"
                         (when notmuch-show-elide-non-matching-messages
                           (list "--entire-thread=false"))))
-
-        (forest (or (notmuch-query-get-threads (append cli-args args))
-                    ;; If a query context reduced the number of
-                    ;; results to zero, try again without it.
-                    (and notmuch-show-query-context
-                         (notmuch-query-get-threads (append cli-args basic-args)))))
-
+        (queries (notmuch-show--build-queries
+                  notmuch-show-thread-id notmuch-show-query-context))
+        (forest nil)
         ;; Must be reset every time we are going to start inserting
         ;; messages into the buffer.
         (notmuch-show-previous-subject ""))
-
+    ;; Use results from the first query that returns some.
+    (while (and (not forest) queries)
+      (setq forest (notmuch-query-get-threads
+                   (append cli-args (list "'") (car queries) (list "'"))))
+      (setq queries (cdr queries)))
     (when forest
       (notmuch-show-insert-forest forest)
 
@@ -1319,8 +1336,13 @@ If no messages match the query return NIL."
 
 This includes:
  - the list of open messages,
- - the current message."
-  (list (notmuch-show-get-message-id) (notmuch-show-get-message-ids-for-open-messages)))
+ - the combination of current message id with/for each visible window."
+  (let* ((win-list (get-buffer-window-list (current-buffer) nil t))
+        (win-id-combo (mapcar (lambda (win)
+                                (with-selected-window win
+                                  (list win (notmuch-show-get-message-id))))
+                              win-list)))
+    (list win-id-combo (notmuch-show-get-message-ids-for-open-messages))))
 
 (defun notmuch-show-get-query ()
   "Return the current query in this show buffer"
@@ -1347,8 +1369,8 @@ This includes:
 This includes:
  - opening the messages previously opened,
  - closing all other messages,
- - moving to the correct current message."
-  (let ((current (car state))
+ - moving to the correct current message in every displayed window."
+  (let ((win-msg-alist (car state))
        (open (cadr state)))
 
     ;; Open those that were open.
@@ -1357,8 +1379,10 @@ This includes:
                                           (member (notmuch-show-get-message-id) open))
          until (not (notmuch-show-goto-message-next)))
 
-    ;; Go to the previously open message.
-    (notmuch-show-goto-message current)))
+    (dolist (win-msg-pair win-msg-alist)
+      (with-selected-window (car win-msg-pair)
+       ;; Go to the previously open message in this window
+       (notmuch-show-goto-message (cadr win-msg-pair))))))
 
 (defun notmuch-show-refresh-view (&optional reset-state)
   "Refresh the current view.
@@ -1431,8 +1455,10 @@ reset based on the original query."
     (define-key map "|" 'notmuch-show-pipe-message)
     (define-key map "w" 'notmuch-show-save-attachments)
     (define-key map "V" 'notmuch-show-view-raw-message)
+    (define-key map "e" 'notmuch-show-resume-message)
     (define-key map "c" 'notmuch-show-stash-map)
     (define-key map "h" 'notmuch-show-toggle-visibility-headers)
+    (define-key map "k" 'notmuch-tag-jump)
     (define-key map "*" 'notmuch-show-tag-all)
     (define-key map "-" 'notmuch-show-remove-tag)
     (define-key map "+" 'notmuch-show-add-tag)
@@ -1967,6 +1993,11 @@ to show, nil otherwise."
     (setq buffer-read-only t)
     (view-buffer buf 'kill-buffer-if-not-modified)))
 
+(defun notmuch-show-resume-message ()
+  "Resume EDITING the current draft message."
+  (interactive)
+  (notmuch-draft-resume (notmuch-show-get-message-id)))
+
 (put 'notmuch-show-pipe-message 'notmuch-doc
      "Pipe the contents of the current message to a command.")
 (put 'notmuch-show-pipe-message 'notmuch-prefix-doc
index 6c8b6a758a696b060d8d0733039118fa88be8ce8..09d182dfb818d6844efdab4ca3d626e9f2dd7d89 100644 (file)
 (require 'crm)
 (require 'notmuch-lib)
 
+(declare-function notmuch-search-tag "notmuch" tag-changes)
+(declare-function notmuch-show-tag "notmuch-show" tag-changes)
+(declare-function notmuch-tree-tag "notmuch-tree" tag-changes)
+
+(autoload 'notmuch-jump "notmuch-jump")
+
+(define-widget 'notmuch-tag-key-type 'list
+  "A single key tagging binding."
+  :format "%v"
+  :args '((list :inline t
+               :format "%v"
+               (key-sequence :tag "Key")
+               (radio :tag "Tag operations" (repeat :tag "Tag list" (string :format "%v" :tag "change"))
+                      (variable :tag "Tag variable"))
+               (string :tag "Name"))))
+
+(defcustom notmuch-tagging-keys
+  `((,(kbd "a") notmuch-archive-tags "Archive")
+    (,(kbd "u") notmuch-show-mark-read-tags "Mark read")
+    (,(kbd "f") ("+flagged") "Flag")
+    (,(kbd "s") ("+spam" "-inbox") "Mark as spam")
+    (,(kbd "d") ("+deleted" "-inbox") "Delete"))
+  "A list of keys and corresponding tagging operations.
+
+For each key (or key sequence) you can specify a sequence of
+tagging operations to apply, or a variable which contains a list
+of tagging operations such as `notmuch-archive-tags'. The final
+element is a name for this tagging operation. If the name is
+omitted or empty then the list of tag changes, or the variable
+name is used as the name.
+
+The key `notmuch-tag-jump-reverse-key' (k by default) should not
+be used (either as a key, or as the start of a key sequence) as
+it is already bound: it switches the menu to a menu of the
+reverse tagging operations. The reverse of a tagging operation is
+the same list of individual tag-ops but with `+tag` replaced by
+`-tag` and vice versa.
+
+If setting this variable outside of customize then it should be a
+list of triples (lists of three elements). Each triple should be
+of the form (key-binding tagging-operations name). KEY-BINDING
+can be a single character or a key sequence; TAGGING-OPERATIONS
+should either be a list of individual tag operations each of the
+form `+tag` or `-tag`, or the variable name of a variable that is
+a list of tagging operations; NAME should be a name for the
+tagging operation, if omitted or empty than then name is taken
+from TAGGING-OPERATIONS."
+  :tag "List of tagging bindings"
+  :type '(repeat notmuch-tag-key-type)
+  :group 'notmuch-tag)
+
 (define-widget 'notmuch-tag-format-type 'lazy
   "Customize widget for notmuch-tag-format and friends"
   :type '(alist :key-type (regexp :tag "Tag")
@@ -64,7 +115,12 @@ Used in the default value of `notmuch-tag-formats`."
   :group 'notmuch-faces)
 
 (defface notmuch-tag-flagged
-  '((t :foreground "blue"))
+  '((((class color)
+      (background dark))
+     (:foreground "LightBlue1"))
+    (((class color)
+      (background light))
+     (:foreground "blue")))
   "Face used for the flagged tag.
 
 Used in the default value of `notmuch-tag-formats`."
@@ -437,6 +493,55 @@ begin with a \"+\" or a \"-\". If REVERSE is non-nil, replace all
                s)))
          tags))
 
+(defvar notmuch-tag-jump-reverse-key "k"
+  "The key in tag-jump to switch to the reverse tag changes.")
+
+(defun notmuch-tag-jump (reverse)
+  "Create a jump menu for tagging operations.
+
+Creates and displays a jump menu for the tagging operations
+specified in `notmuch-tagging-keys'. If REVERSE is set then it
+offers a menu of the reverses of the operations specified in
+`notmuch-tagging-keys'; i.e. each `+tag` is replaced by `-tag`
+and vice versa."
+  ;; In principle this function is simple, but it has to deal with
+  ;; lots of cases: different modes (search/show/tree), whether a name
+  ;; is specified, whether the tagging operations is a list of
+  ;; tag-ops, or a symbol that evaluates to such a list, and whether
+  ;; REVERSE is specified.
+  (interactive "P")
+  (let (action-map)
+    (dolist (binding notmuch-tagging-keys)
+      (let* ((tag-function (case major-mode
+                            (notmuch-search-mode #'notmuch-search-tag)
+                            (notmuch-show-mode #'notmuch-show-tag)
+                            (notmuch-tree-mode #'notmuch-tree-tag)))
+            (key (first binding))
+            (forward-tag-change (if (symbolp (second binding))
+                                    (symbol-value (second binding))
+                                  (second binding)))
+            (tag-change (if reverse
+                            (notmuch-tag-change-list forward-tag-change 't)
+                          forward-tag-change))
+            (name (or (and (not (string= (third binding) ""))
+                           (third binding))
+                      (and (symbolp (second binding))
+                           (symbol-name (second binding)))))
+            (name-string (if name
+                             (if reverse (concat "Reverse " name)
+                               name)
+                           (mapconcat #'identity tag-change " "))))
+       (push (list key name-string
+                    `(lambda () (,tag-function ',tag-change)))
+             action-map)))
+    (push (list notmuch-tag-jump-reverse-key
+               (if reverse
+                   "Forward tag changes "
+                 "Reverse tag changes")
+               (apply-partially 'notmuch-tag-jump (not reverse)))
+         action-map)
+    (setq action-map (nreverse action-map))
+    (notmuch-jump action-map "Tag: ")))
 
 ;;
 
index 658c4f90870dfe4de726514b41908b53dde85661..7bebdbabb6cc492997f6afee04e0e1077365efae 100644 (file)
@@ -209,6 +209,13 @@ open (if the message pane is closed it does nothing)."
        (with-selected-window notmuch-tree-message-window
         (call-interactively #',func)))))
 
+(defun notmuch-tree-inherit-from-message-pane (sym)
+  "Return value of SYM in message-pane if open, or tree-pane if not"
+  (if (window-live-p notmuch-tree-message-window)
+      (with-selected-window notmuch-tree-message-window
+       (symbol-value sym))
+    (symbol-value sym)))
+
 (defun notmuch-tree-button-activate (&optional button)
   "Activate BUTTON or button at point
 
@@ -226,8 +233,10 @@ FUNC."
   `(lambda ()
       ,(concat "(Close message pane and) " (documentation func t))
      (interactive)
-     (notmuch-tree-close-message-window)
-     (call-interactively #',func)))
+     (let ((notmuch-show-process-crypto
+           (notmuch-tree-inherit-from-message-pane 'notmuch-show-process-crypto)))
+       (notmuch-tree-close-message-window)
+       (call-interactively #',func))))
 
 (defvar notmuch-tree-mode-map
   (let ((map (make-sparse-keymap)))
@@ -257,7 +266,7 @@ FUNC."
     (define-key map (kbd "M-TAB") (notmuch-tree-to-message-pane #'notmuch-show-previous-button))
     (define-key map (kbd "<backtab>")  (notmuch-tree-to-message-pane #'notmuch-show-previous-button))
     (define-key map (kbd "TAB") (notmuch-tree-to-message-pane #'notmuch-show-next-button))
-    (define-key map "e" (notmuch-tree-to-message-pane #'notmuch-tree-button-activate))
+    (define-key map "$" (notmuch-tree-to-message-pane #'notmuch-show-toggle-process-crypto))
 
     ;; bindings from show (or elsewhere) but we close the message pane first.
     (define-key map "f" (notmuch-tree-close-message-pane-and #'notmuch-show-forward-message))
@@ -271,7 +280,6 @@ FUNC."
     (define-key map "x" 'notmuch-tree-quit)
     (define-key map "A" 'notmuch-tree-archive-thread)
     (define-key map "a" 'notmuch-tree-archive-message-then-next)
-    (define-key map "=" 'notmuch-tree-refresh-view)
     (define-key map "z" 'notmuch-tree-to-tree)
     (define-key map "n" 'notmuch-tree-next-matching-message)
     (define-key map "p" 'notmuch-tree-prev-matching-message)
@@ -279,11 +287,13 @@ FUNC."
     (define-key map "P" 'notmuch-tree-prev-message)
     (define-key map (kbd "M-p") 'notmuch-tree-prev-thread)
     (define-key map (kbd "M-n") 'notmuch-tree-next-thread)
+    (define-key map "k" 'notmuch-tag-jump)
     (define-key map "-" 'notmuch-tree-remove-tag)
     (define-key map "+" 'notmuch-tree-add-tag)
     (define-key map "*" 'notmuch-tree-tag-thread)
     (define-key map " " 'notmuch-tree-scroll-or-next)
     (define-key map (kbd "DEL") 'notmuch-tree-scroll-message-window-back)
+    (define-key map "e" 'notmuch-tree-resume-message)
     map))
 (fset 'notmuch-tree-mode-map notmuch-tree-mode-map)
 
@@ -364,12 +374,18 @@ updated."
 (defun notmuch-tree-tag-update-display (&optional tag-changes)
   "Update display for TAG-CHANGES to current message.
 
-Does NOT change the database."
+Updates the message in the message pane if appropriate, but does
+NOT change the database."
   (let* ((current-tags (notmuch-tree-get-tags))
-        (new-tags (notmuch-update-tags current-tags tag-changes)))
+        (new-tags (notmuch-update-tags current-tags tag-changes))
+        (tree-msg-id (notmuch-tree-get-message-id)))
     (unless (equal current-tags new-tags)
       (notmuch-tree-set-tags new-tags)
-      (notmuch-tree-refresh-result))))
+      (notmuch-tree-refresh-result)
+      (when (window-live-p notmuch-tree-message-window)
+       (with-selected-window notmuch-tree-message-window
+         (when (string= tree-msg-id (notmuch-show-get-message-id))
+           (notmuch-show-update-tags new-tags)))))))
 
 (defun notmuch-tree-tag (tag-changes)
   "Change tags for the current message"
@@ -390,6 +406,15 @@ Does NOT change the database."
    (list (notmuch-read-tag-changes (notmuch-tree-get-tags) "Tag message" "-")))
   (notmuch-tree-tag tag-changes))
 
+(defun notmuch-tree-resume-message ()
+  "Resume EDITING the current draft message."
+  (interactive)
+  (notmuch-tree-close-message-window)
+  (let ((id (notmuch-tree-get-message-id)))
+    (if id
+       (notmuch-draft-resume id)
+      (message "No message to resume!"))))
+
 ;; The next two functions close the message window before calling
 ;; notmuch-search or notmuch-tree but they do so after the user has
 ;; entered the query (in case the user was basing the query on
index 46f14fea1dd0ff112a57e64154cc5e5eeb19ab37..d8d3afeb69b983312b637a20d05f145125c2f098 100644 (file)
@@ -169,6 +169,7 @@ there will be called at other points of notmuch execution."
     (define-key map "t" 'notmuch-search-filter-by-tag)
     (define-key map "l" 'notmuch-search-filter)
     (define-key map [mouse-1] 'notmuch-search-show-thread)
+    (define-key map "k" 'notmuch-tag-jump)
     (define-key map "*" 'notmuch-search-tag-all)
     (define-key map "a" 'notmuch-search-archive-thread)
     (define-key map "-" 'notmuch-search-remove-tag)
@@ -312,7 +313,11 @@ there will be called at other points of notmuch execution."
   :group 'notmuch-faces)
 
 (defface notmuch-search-flagged-face
-  '((t
+  '((((class color)
+      (background dark))
+     (:foreground "LightBlue1"))
+    (((class color)
+      (background light))
      (:foreground "blue")))
   "Face used in search mode face for flagged threads.
 
@@ -561,12 +566,15 @@ Returns (TAG-CHANGES REGION-BEGIN REGION-END)."
 See `notmuch-tag' for information on the format of TAG-CHANGES.
 When called interactively, this uses the region if the region is
 active.  When called directly, BEG and END provide the region.
-If these are nil or not provided, this applies to the thread at
-point.
+If these are nil or not provided, then, if the region is active
+this applied to all threads meeting the region, and if the region
+is inactive this applies to the thread at point.
 
 If ONLY-MATCHED is non-nil, only tag matched messages."
   (interactive (notmuch-search-interactive-tag-changes))
-  (unless (and beg end) (setq beg (point) end (point)))
+  (unless (and beg end)
+    (setq beg (car (notmuch-search-interactive-region))
+         end (cadr (notmuch-search-interactive-region))))
   (let ((search-string (notmuch-search-find-stable-query-region
                        beg end only-matched)))
     (notmuch-tag search-string tag-changes)
@@ -900,9 +908,10 @@ PROMPT is the string to prompt with."
                 (process-lines notmuch-command "search" "--output=tags" "*")))
        (completions
         (append (list "folder:" "path:" "thread:" "id:" "date:" "from:" "to:"
-                      "subject:" "attachment:" "mimetype:")
+                      "subject:" "attachment:")
                 (mapcar (lambda (tag) (concat "tag:" tag)) all-tags)
-                (mapcar (lambda (tag) (concat "is:" tag)) all-tags))))
+                (mapcar (lambda (tag) (concat "is:" tag)) all-tags)
+                (mapcar (lambda (mimetype) (concat "mimetype:" mimetype)) (mailcap-mime-types)))))
     (let ((keymap (copy-keymap minibuffer-local-map))
          (current-query (case major-mode
                           (notmuch-search-mode (notmuch-search-get-query))
@@ -933,7 +942,7 @@ PROMPT is the string to prompt with."
 
 (put 'notmuch-search 'notmuch-doc "Search for messages.")
 ;;;###autoload
-(defun notmuch-search (&optional query oldest-first target-thread target-line)
+(defun notmuch-search (&optional query oldest-first target-thread target-line no-display)
   "Display threads matching QUERY in a notmuch-search buffer.
 
 If QUERY is nil, it is read interactively from the minibuffer.
@@ -944,6 +953,9 @@ Other optional parameters are used as follows:
                  current if it appears in the search results.
   TARGET-LINE: The line number to move to if the target thread does not
                appear in the search results.
+  NO-DISPLAY: Do not try to foreground the search results buffer. If it is
+              already foregrounded i.e. displayed in a window, this has no
+              effect, meaning the buffer will remain visible.
 
 When called interactively, this will prompt for a query and use
 the configured default sort order."
@@ -957,7 +969,9 @@ the configured default sort order."
 
   (let* ((query (or query (notmuch-read-query "Notmuch search: ")))
         (buffer (get-buffer-create (notmuch-search-buffer-title query))))
-    (switch-to-buffer buffer)
+    (if no-display
+       (set-buffer buffer)
+      (switch-to-buffer buffer))
     (notmuch-search-mode)
     ;; Don't track undo information for this buffer
     (set 'buffer-undo-list t)
@@ -993,17 +1007,18 @@ the configured default sort order."
 (defun notmuch-search-refresh-view ()
   "Refresh the current view.
 
-Kills the current buffer and runs a new search with the same
+Erases the current buffer and runs a new search with the same
 query string as the current search. If the current thread is in
 the new search results, then point will be placed on the same
 thread. Otherwise, point will be moved to attempt to be in the
 same relative position within the new buffer."
+  (interactive)
   (let ((target-line (line-number-at-pos))
        (oldest-first notmuch-search-oldest-first)
        (target-thread (notmuch-search-find-thread-id 'bare))
        (query notmuch-search-query-string))
-    (notmuch-bury-or-kill-this-buffer)
-    (notmuch-search query oldest-first target-thread target-line)
+    ;; notmuch-search erases the current buffer.
+    (notmuch-search query oldest-first target-thread target-line t)
     (goto-char (point-min))))
 
 (defun notmuch-search-toggle-order ()
index 3d1030a567404bc88ad1f887f5c452240dcecfba..b77e57803288cc622bf22b9c34fa511440ee62e9 100644 (file)
@@ -35,7 +35,6 @@ libnotmuch_c_srcs =           \
        $(notmuch_compat_srcs)  \
        $(dir)/filenames.c      \
        $(dir)/string-list.c    \
-       $(dir)/libsha1.c        \
        $(dir)/message-file.c   \
        $(dir)/messages.c       \
        $(dir)/sha1.c           \
index ca71a92f6c26893d92cc1435d0713d4cc69e5d96..06882439fc580f88b827b6648b267855b7e5abd4 100644 (file)
 
 #include "notmuch-private.h"
 
+#ifdef SILENCE_XAPIAN_DEPRECATION_WARNINGS
+#define XAPIAN_DEPRECATED(D) D
+#endif
+
 #include <xapian.h>
 
 #pragma GCC visibility push(hidden)
@@ -144,6 +148,30 @@ operator&=(_notmuch_features &a, _notmuch_features b)
     return a;
 }
 
+/*
+ * Configuration options for xapian database fields */
+typedef enum notmuch_field_flags {
+    NOTMUCH_FIELD_NO_FLAGS = 0,
+    NOTMUCH_FIELD_EXTERNAL = 1 << 0,
+    NOTMUCH_FIELD_PROBABILISTIC = 1 << 1
+} notmuch_field_flag_t;
+
+/*
+ * define bitwise operators to hide casts */
+inline notmuch_field_flag_t
+operator|(notmuch_field_flag_t a, notmuch_field_flag_t b)
+{
+    return static_cast<notmuch_field_flag_t>(
+       static_cast<unsigned>(a) | static_cast<unsigned>(b));
+}
+
+inline notmuch_field_flag_t
+operator&(notmuch_field_flag_t a, notmuch_field_flag_t b)
+{
+    return static_cast<notmuch_field_flag_t>(
+       static_cast<unsigned>(a) & static_cast<unsigned>(b));
+}
+
 #define NOTMUCH_QUERY_PARSER_FLAGS (Xapian::QueryParser::FLAG_BOOLEAN | \
                                    Xapian::QueryParser::FLAG_PHRASE | \
                                    Xapian::QueryParser::FLAG_LOVEHATE | \
@@ -179,14 +207,14 @@ struct _notmuch_database {
     unsigned long revision;
     const char *uuid;
 
+    /* Keep track of the number of times the database has been re-opened
+     * (or other global invalidations of notmuch's caching)
+     */
+    unsigned long view;
     Xapian::QueryParser *query_parser;
     Xapian::TermGenerator *term_gen;
     Xapian::ValueRangeProcessor *value_range_processor;
     Xapian::ValueRangeProcessor *date_range_processor;
-#if HAVE_XAPIAN_FIELD_PROCESSOR
-    Xapian::FieldProcessor *date_field_processor;
-    Xapian::FieldProcessor *query_field_processor;
-#endif
     Xapian::ValueRangeProcessor *last_mod_range_processor;
 };
 
index eddb780c69ebc76ab8870fb93dfb23d01e1a6176..ba440d4db87e4608b043167860cc09e911f4aeda 100644 (file)
@@ -42,6 +42,7 @@ using namespace std;
 typedef struct {
     const char *name;
     const char *prefix;
+    notmuch_field_flag_t flags;
 } prefix_t;
 
 #define NOTMUCH_DATABASE_VERSION 3
@@ -247,37 +248,38 @@ typedef struct {
  * nearly universal to all mail messages).
  */
 
-static prefix_t BOOLEAN_PREFIX_INTERNAL[] = {
-    { "type",                  "T" },
-    { "reference",             "XREFERENCE" },
-    { "replyto",               "XREPLYTO" },
-    { "directory",             "XDIRECTORY" },
-    { "file-direntry",         "XFDIRENTRY" },
-    { "directory-direntry",    "XDDIRENTRY" },
-};
-
-static prefix_t BOOLEAN_PREFIX_EXTERNAL[] = {
-    { "thread",                        "G" },
-    { "tag",                   "K" },
-    { "is",                    "K" },
-    { "id",                    "Q" },
-    { "path",                  "P" },
-    { "property",              "XPROPERTY" },
+static const
+prefix_t prefix_table[] = {
+    /* name                    term prefix     flags */
+    { "type",                  "T",            NOTMUCH_FIELD_NO_FLAGS },
+    { "reference",             "XREFERENCE",   NOTMUCH_FIELD_NO_FLAGS },
+    { "replyto",               "XREPLYTO",     NOTMUCH_FIELD_NO_FLAGS },
+    { "directory",             "XDIRECTORY",   NOTMUCH_FIELD_NO_FLAGS },
+    { "file-direntry",         "XFDIRENTRY",   NOTMUCH_FIELD_NO_FLAGS },
+    { "directory-direntry",    "XDDIRENTRY",   NOTMUCH_FIELD_NO_FLAGS },
+    { "thread",                        "G",            NOTMUCH_FIELD_EXTERNAL },
+    { "tag",                   "K",            NOTMUCH_FIELD_EXTERNAL },
+    { "is",                    "K",            NOTMUCH_FIELD_EXTERNAL },
+    { "id",                    "Q",            NOTMUCH_FIELD_EXTERNAL },
+    { "path",                  "P",            NOTMUCH_FIELD_EXTERNAL },
+    { "property",              "XPROPERTY",    NOTMUCH_FIELD_EXTERNAL },
     /*
      * Unconditionally add ':' to reduce potential ambiguity with
      * overlapping prefixes and/or terms that start with capital
      * letters. See Xapian document termprefixes.html for related
      * discussion.
      */
-    { "folder",                        "XFOLDER:" },
-};
-
-static prefix_t PROBABILISTIC_PREFIX[]= {
-    { "from",                  "XFROM" },
-    { "to",                    "XTO" },
-    { "attachment",            "XATTACHMENT" },
-    { "mimetype",              "XMIMETYPE"},
-    { "subject",               "XSUBJECT"},
+    { "folder",                        "XFOLDER:",     NOTMUCH_FIELD_EXTERNAL },
+    { "from",                  "XFROM",        NOTMUCH_FIELD_EXTERNAL |
+                                               NOTMUCH_FIELD_PROBABILISTIC },
+    { "to",                    "XTO",          NOTMUCH_FIELD_EXTERNAL |
+                                               NOTMUCH_FIELD_PROBABILISTIC },
+    { "attachment",            "XATTACHMENT",  NOTMUCH_FIELD_EXTERNAL |
+                                               NOTMUCH_FIELD_PROBABILISTIC },
+    { "mimetype",              "XMIMETYPE",    NOTMUCH_FIELD_EXTERNAL |
+                                               NOTMUCH_FIELD_PROBABILISTIC },
+    { "subject",               "XSUBJECT",     NOTMUCH_FIELD_EXTERNAL |
+                                               NOTMUCH_FIELD_PROBABILISTIC },
 };
 
 const char *
@@ -285,19 +287,9 @@ _find_prefix (const char *name)
 {
     unsigned int i;
 
-    for (i = 0; i < ARRAY_SIZE (BOOLEAN_PREFIX_INTERNAL); i++) {
-       if (strcmp (name, BOOLEAN_PREFIX_INTERNAL[i].name) == 0)
-           return BOOLEAN_PREFIX_INTERNAL[i].prefix;
-    }
-
-    for (i = 0; i < ARRAY_SIZE (BOOLEAN_PREFIX_EXTERNAL); i++) {
-       if (strcmp (name, BOOLEAN_PREFIX_EXTERNAL[i].name) == 0)
-           return BOOLEAN_PREFIX_EXTERNAL[i].prefix;
-    }
-
-    for (i = 0; i < ARRAY_SIZE (PROBABILISTIC_PREFIX); i++) {
-       if (strcmp (name, PROBABILISTIC_PREFIX[i].name) == 0)
-           return PROBABILISTIC_PREFIX[i].prefix;
+    for (i = 0; i < ARRAY_SIZE (prefix_table); i++) {
+       if (strcmp (name, prefix_table[i].name) == 0)
+           return prefix_table[i].prefix;
     }
 
     INTERNAL_ERROR ("No prefix exists for '%s'\n", name);
@@ -959,6 +951,7 @@ notmuch_database_open_verbose (const char *path,
 
     notmuch->mode = mode;
     notmuch->atomic_nesting = 0;
+    notmuch->view = 1;
     try {
        string last_thread_id;
        string last_mod;
@@ -1038,10 +1031,14 @@ notmuch_database_open_verbose (const char *path,
 #if HAVE_XAPIAN_FIELD_PROCESSOR
        /* This currently relies on the query parser to pass anything
         * with a .. to the range processor */
-       notmuch->date_field_processor = new DateFieldProcessor();
-       notmuch->query_parser->add_boolean_prefix("date", notmuch->date_field_processor);
-       notmuch->query_field_processor = new QueryFieldProcessor (*notmuch->query_parser, notmuch);
-       notmuch->query_parser->add_boolean_prefix("query", notmuch->query_field_processor);
+       {
+           Xapian::FieldProcessor * date_fp = new DateFieldProcessor();
+           Xapian::FieldProcessor * query_fp =
+               new QueryFieldProcessor (*notmuch->query_parser, notmuch);
+
+           notmuch->query_parser->add_boolean_prefix("date", date_fp->release ());
+           notmuch->query_parser->add_boolean_prefix("query", query_fp->release ());
+       }
 #endif
        notmuch->last_mod_range_processor = new Xapian::NumberValueRangeProcessor (NOTMUCH_VALUE_LAST_MOD, "lastmod:");
 
@@ -1053,15 +1050,16 @@ notmuch_database_open_verbose (const char *path,
        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];
-           notmuch->query_parser->add_boolean_prefix (prefix->name,
-                                                      prefix->prefix);
-       }
-
-       for (i = 0; i < ARRAY_SIZE (PROBABILISTIC_PREFIX); i++) {
-           prefix_t *prefix = &PROBABILISTIC_PREFIX[i];
-           notmuch->query_parser->add_prefix (prefix->name, prefix->prefix);
+       for (i = 0; i < ARRAY_SIZE (prefix_table); i++) {
+           const prefix_t *prefix = &prefix_table[i];
+           if (prefix->flags & NOTMUCH_FIELD_EXTERNAL) {
+               if (prefix->flags & NOTMUCH_FIELD_PROBABILISTIC) {
+                   notmuch->query_parser->add_prefix (prefix->name, prefix->prefix);
+               } else {
+                   notmuch->query_parser->add_boolean_prefix (prefix->name,
+                                                              prefix->prefix);
+               }
+           }
        }
     } catch (const Xapian::Error &error) {
        IGNORE_RESULT (asprintf (&message, "A Xapian exception occurred opening database: %s\n",
@@ -1133,16 +1131,31 @@ notmuch_database_close (notmuch_database_t *notmuch)
     delete notmuch->last_mod_range_processor;
     notmuch->last_mod_range_processor = NULL;
 
-#if HAVE_XAPIAN_FIELD_PROCESSOR
-    delete notmuch->date_field_processor;
-    notmuch->date_field_processor = NULL;
-    delete notmuch->query_field_processor;
-    notmuch->query_field_processor = NULL;
-#endif
-
     return status;
 }
 
+notmuch_status_t
+_notmuch_database_reopen (notmuch_database_t *notmuch)
+{
+    if (notmuch->mode != NOTMUCH_DATABASE_MODE_READ_ONLY)
+       return NOTMUCH_STATUS_UNSUPPORTED_OPERATION;
+
+    try {
+       notmuch->xapian_db->reopen ();
+    } catch (const Xapian::Error &error) {
+       if (! notmuch->exception_reported) {
+           _notmuch_database_log (notmuch, "Error: A Xapian exception reopening database: %s\n",
+                                  error.get_msg ().c_str ());
+           notmuch->exception_reported = TRUE;
+       }
+       return NOTMUCH_STATUS_XAPIAN_EXCEPTION;
+    }
+
+    notmuch->view++;
+
+    return NOTMUCH_STATUS_SUCCESS;
+}
+
 static int
 unlink_cb (const char *path,
           unused (const struct stat *sb),
@@ -1711,7 +1724,7 @@ notmuch_database_end_atomic (notmuch_database_t *notmuch)
         * However, we rely on flushing to test atomicity. */
        const char *thresh = getenv ("XAPIAN_FLUSH_THRESHOLD");
        if (thresh && atoi (thresh) == 1)
-           db->flush ();
+           db->commit ();
     } catch (const Xapian::Error &error) {
        _notmuch_database_log (notmuch, "A Xapian exception occurred committing transaction: %s.\n",
                 error.get_msg().c_str());
diff --git a/lib/libsha1.c b/lib/libsha1.c
deleted file mode 100644 (file)
index aaaa4eb..0000000
+++ /dev/null
@@ -1,244 +0,0 @@
-/*
- ---------------------------------------------------------------------------
- Copyright (c) 2002, Dr Brian Gladman, Worcester, UK.   All rights reserved.
-
- LICENSE TERMS
-
- The free distribution and use of this software in both source and binary
- form is allowed (with or without changes) provided that:
-
-   1. distributions of this source code include the above copyright
-      notice, this list of conditions and the following disclaimer;
-
-   2. distributions in binary form include the above copyright
-      notice, this list of conditions and the following disclaimer
-      in the documentation and/or other associated materials;
-
-   3. the copyright holder's name is not used to endorse products
-      built using this software without specific written permission.
-
- ALTERNATIVELY, provided that this notice is retained in full, this product
- may be distributed under the terms of the GNU General Public License (GPL),
- in which case the provisions of the GPL apply INSTEAD OF those given above.
-
- DISCLAIMER
-
- This software is provided 'as is' with no explicit or implied warranties
- in respect of its properties, including, but not limited to, correctness
- and/or fitness for purpose.
- ---------------------------------------------------------------------------
- Issue Date: 01/08/2005
-
- This is a byte oriented version of SHA1 that operates on arrays of bytes
- stored in memory.
-*/
-
-#include <string.h>     /* for memcpy() etc.        */
-#include "endian-util.h"
-#include "libsha1.h"
-
-#if defined(__cplusplus)
-extern "C"
-{
-#endif
-
-#define SHA1_BLOCK_SIZE  64
-
-#define rotl32(x,n)   (((x) << n) | ((x) >> (32 - n)))
-#define rotr32(x,n)   (((x) >> n) | ((x) << (32 - n)))
-
-#define bswap_32(x) ((rotr32((x), 24) & 0x00ff00ff) | (rotr32((x), 8) & 0xff00ff00))
-
-#if (UTIL_BYTE_ORDER == UTIL_ORDER_LITTLE_ENDIAN)
-#  define bsw_32(p,n) \
-     { int _i = (n); while(_i--) ((uint32_t*)p)[_i] = bswap_32(((uint32_t*)p)[_i]); }
-#elif (UTIL_BYTE_ORDER == UTIL_ORDER_BIG_ENDIAN)
-#  define bsw_32(p,n)
-#else
-#  error "Unsupported byte order"
-#endif
-
-#define SHA1_MASK   (SHA1_BLOCK_SIZE - 1)
-
-#if 0
-
-#define ch(x,y,z)       (((x) & (y)) ^ (~(x) & (z)))
-#define parity(x,y,z)   ((x) ^ (y) ^ (z))
-#define maj(x,y,z)      (((x) & (y)) ^ ((x) & (z)) ^ ((y) & (z)))
-
-#else   /* Discovered by Rich Schroeppel and Colin Plumb   */
-
-#define ch(x,y,z)       ((z) ^ ((x) & ((y) ^ (z))))
-#define parity(x,y,z)   ((x) ^ (y) ^ (z))
-#define maj(x,y,z)      (((x) & (y)) | ((z) & ((x) ^ (y))))
-
-#endif
-
-/* Compile 64 bytes of hash data into SHA1 context. Note    */
-/* that this routine assumes that the byte order in the     */
-/* ctx->wbuf[] at this point is in such an order that low   */
-/* address bytes in the ORIGINAL byte stream will go in     */
-/* this buffer to the high end of 32-bit words on BOTH big  */
-/* and little endian systems                                */
-
-#ifdef ARRAY
-#define q(v,n)  v[n]
-#else
-#define q(v,n)  v##n
-#endif
-
-#define one_cycle(v,a,b,c,d,e,f,k,h)            \
-    q(v,e) += rotr32(q(v,a),27) +               \
-              f(q(v,b),q(v,c),q(v,d)) + k + h;  \
-    q(v,b)  = rotr32(q(v,b), 2)
-
-#define five_cycle(v,f,k,i)                 \
-    one_cycle(v, 0,1,2,3,4, f,k,hf(i  ));   \
-    one_cycle(v, 4,0,1,2,3, f,k,hf(i+1));   \
-    one_cycle(v, 3,4,0,1,2, f,k,hf(i+2));   \
-    one_cycle(v, 2,3,4,0,1, f,k,hf(i+3));   \
-    one_cycle(v, 1,2,3,4,0, f,k,hf(i+4))
-
-static void sha1_compile(sha1_ctx ctx[1])
-{   uint32_t    *w = ctx->wbuf;
-
-#ifdef ARRAY
-    uint32_t    v[5];
-    memcpy(v, ctx->hash, 5 * sizeof(uint32_t));
-#else
-    uint32_t    v0, v1, v2, v3, v4;
-    v0 = ctx->hash[0]; v1 = ctx->hash[1];
-    v2 = ctx->hash[2]; v3 = ctx->hash[3];
-    v4 = ctx->hash[4];
-#endif
-
-#define hf(i)   w[i]
-
-    five_cycle(v, ch, 0x5a827999,  0);
-    five_cycle(v, ch, 0x5a827999,  5);
-    five_cycle(v, ch, 0x5a827999, 10);
-    one_cycle(v,0,1,2,3,4, ch, 0x5a827999, hf(15)); \
-
-#undef  hf
-#define hf(i) (w[(i) & 15] = rotl32(                    \
-                 w[((i) + 13) & 15] ^ w[((i) + 8) & 15] \
-               ^ w[((i) +  2) & 15] ^ w[(i) & 15], 1))
-
-    one_cycle(v,4,0,1,2,3, ch, 0x5a827999, hf(16));
-    one_cycle(v,3,4,0,1,2, ch, 0x5a827999, hf(17));
-    one_cycle(v,2,3,4,0,1, ch, 0x5a827999, hf(18));
-    one_cycle(v,1,2,3,4,0, ch, 0x5a827999, hf(19));
-
-    five_cycle(v, parity, 0x6ed9eba1,  20);
-    five_cycle(v, parity, 0x6ed9eba1,  25);
-    five_cycle(v, parity, 0x6ed9eba1,  30);
-    five_cycle(v, parity, 0x6ed9eba1,  35);
-
-    five_cycle(v, maj, 0x8f1bbcdc,  40);
-    five_cycle(v, maj, 0x8f1bbcdc,  45);
-    five_cycle(v, maj, 0x8f1bbcdc,  50);
-    five_cycle(v, maj, 0x8f1bbcdc,  55);
-
-    five_cycle(v, parity, 0xca62c1d6,  60);
-    five_cycle(v, parity, 0xca62c1d6,  65);
-    five_cycle(v, parity, 0xca62c1d6,  70);
-    five_cycle(v, parity, 0xca62c1d6,  75);
-
-#ifdef ARRAY
-    ctx->hash[0] += v[0]; ctx->hash[1] += v[1];
-    ctx->hash[2] += v[2]; ctx->hash[3] += v[3];
-    ctx->hash[4] += v[4];
-#else
-    ctx->hash[0] += v0; ctx->hash[1] += v1;
-    ctx->hash[2] += v2; ctx->hash[3] += v3;
-    ctx->hash[4] += v4;
-#endif
-}
-
-void sha1_begin(sha1_ctx ctx[1])
-{
-    ctx->count[0] = ctx->count[1] = 0;
-    ctx->hash[0] = 0x67452301;
-    ctx->hash[1] = 0xefcdab89;
-    ctx->hash[2] = 0x98badcfe;
-    ctx->hash[3] = 0x10325476;
-    ctx->hash[4] = 0xc3d2e1f0;
-}
-
-/* SHA1 hash data in an array of bytes into hash buffer and */
-/* call the hash_compile function as required.              */
-
-void sha1_hash(const unsigned char data[], unsigned long len, sha1_ctx ctx[1])
-{   uint32_t pos = (uint32_t)(ctx->count[0] & SHA1_MASK),
-            space = SHA1_BLOCK_SIZE - pos;
-    const unsigned char *sp = data;
-
-    if((ctx->count[0] += len) < len)
-        ++(ctx->count[1]);
-
-    while(len >= space)     /* transfer whole blocks if possible  */
-    {
-        memcpy(((unsigned char*)ctx->wbuf) + pos, sp, space);
-        sp += space; len -= space; space = SHA1_BLOCK_SIZE; pos = 0;
-        bsw_32(ctx->wbuf, SHA1_BLOCK_SIZE >> 2);
-        sha1_compile(ctx);
-    }
-
-    memcpy(((unsigned char*)ctx->wbuf) + pos, sp, len);
-}
-
-/* SHA1 final padding and digest calculation  */
-
-void sha1_end(unsigned char hval[], sha1_ctx ctx[1])
-{   uint32_t    i = (uint32_t)(ctx->count[0] & SHA1_MASK);
-
-    /* put bytes in the buffer in an order in which references to   */
-    /* 32-bit words will put bytes with lower addresses into the    */
-    /* top of 32 bit words on BOTH big and little endian machines   */
-    bsw_32(ctx->wbuf, (i + 3) >> 2);
-
-    /* we now need to mask valid bytes and add the padding which is */
-    /* a single 1 bit and as many zero bits as necessary. Note that */
-    /* we can always add the first padding byte here because the    */
-    /* buffer always has at least one empty slot                    */
-    ctx->wbuf[i >> 2] &= 0xffffff80 << 8 * (~i & 3);
-    ctx->wbuf[i >> 2] |= 0x00000080 << 8 * (~i & 3);
-
-    /* we need 9 or more empty positions, one for the padding byte  */
-    /* (above) and eight for the length count. If there is not      */
-    /* enough space, pad and empty the buffer                       */
-    if(i > SHA1_BLOCK_SIZE - 9)
-    {
-        if(i < 60) ctx->wbuf[15] = 0;
-        sha1_compile(ctx);
-        i = 0;
-    }
-    else    /* compute a word index for the empty buffer positions  */
-        i = (i >> 2) + 1;
-
-    while(i < 14) /* and zero pad all but last two positions        */
-        ctx->wbuf[i++] = 0;
-
-    /* the following 32-bit length fields are assembled in the      */
-    /* wrong byte order on little endian machines but this is       */
-    /* corrected later since they are only ever used as 32-bit      */
-    /* word values.                                                 */
-    ctx->wbuf[14] = (ctx->count[1] << 3) | (ctx->count[0] >> 29);
-    ctx->wbuf[15] = ctx->count[0] << 3;
-    sha1_compile(ctx);
-
-    /* extract the hash value as bytes in case the hash buffer is   */
-    /* misaligned for 32-bit words                                  */
-    for(i = 0; i < SHA1_DIGEST_SIZE; ++i)
-        hval[i] = (unsigned char)(ctx->hash[i >> 2] >> (8 * (~i & 3)));
-}
-
-void sha1(unsigned char hval[], const unsigned char data[], unsigned long len)
-{   sha1_ctx    cx[1];
-
-    sha1_begin(cx); sha1_hash(data, len, cx); sha1_end(hval, cx);
-}
-
-#if defined(__cplusplus)
-}
-#endif
diff --git a/lib/libsha1.h b/lib/libsha1.h
deleted file mode 100644 (file)
index 56f445a..0000000
+++ /dev/null
@@ -1,71 +0,0 @@
-/*
- ---------------------------------------------------------------------------
- Copyright (c) 2002, Dr Brian Gladman, Worcester, UK.   All rights reserved.
-
- LICENSE TERMS
-
- The free distribution and use of this software in both source and binary
- form is allowed (with or without changes) provided that:
-
-   1. distributions of this source code include the above copyright
-      notice, this list of conditions and the following disclaimer;
-
-   2. distributions in binary form include the above copyright
-      notice, this list of conditions and the following disclaimer
-      in the documentation and/or other associated materials;
-
-   3. the copyright holder's name is not used to endorse products
-      built using this software without specific written permission.
-
- ALTERNATIVELY, provided that this notice is retained in full, this product
- may be distributed under the terms of the GNU General Public License (GPL),
- in which case the provisions of the GPL apply INSTEAD OF those given above.
-
- DISCLAIMER
-
- This software is provided 'as is' with no explicit or implied warranties
- in respect of its properties, including, but not limited to, correctness
- and/or fitness for purpose.
- ---------------------------------------------------------------------------
- Issue Date: 01/08/2005
-*/
-
-#ifndef _SHA1_H
-#define _SHA1_H
-
-#if defined(__cplusplus)
-extern "C"
-{
-#endif
-#if 0
-} /* Appeasing Emacs */
-#endif
-
-#include <stdint.h>
-
-#pragma GCC visibility push(hidden)
-
-/* Size of SHA1 digest */
-
-#define SHA1_DIGEST_SIZE 20
-
-/* type to hold the SHA1 context  */
-
-typedef struct
-{   uint32_t count[2];
-    uint32_t hash[5];
-    uint32_t wbuf[16];
-} sha1_ctx;
-
-void sha1_begin(sha1_ctx ctx[1]);
-void sha1_hash(const unsigned char data[], unsigned long len, sha1_ctx ctx[1]);
-void sha1_end(unsigned char hval[], sha1_ctx ctx[1]);
-void sha1(unsigned char hval[], const unsigned char data[], unsigned long len);
-
-#pragma GCC visibility pop
-
-#if defined(__cplusplus)
-}
-#endif
-
-#endif
index 9d3e80712fd5655f8a5b412b43803df18794af61..007f11715eb8f97d495f0ed68e1180e9fcc86452 100644 (file)
@@ -49,6 +49,9 @@ struct visible _notmuch_message {
     /* Message document modified since last sync */
     notmuch_bool_t modified;
 
+    /* last view of database the struct is synced with */
+    unsigned long last_view;
+
     Xapian::Document doc;
     Xapian::termcount termpos;
 };
@@ -110,6 +113,9 @@ _notmuch_message_create_for_document (const void *talloc_owner,
     message->flags = 0;
     message->lazy_flags = 0;
 
+    /* the message is initially not synchronized with Xapian */
+    message->last_view = 0;
+
     /* Each of these will be lazily created as needed. */
     message->message_id = NULL;
     message->thread_id = NULL;
@@ -310,10 +316,14 @@ _notmuch_message_get_term (notmuch_message_t *message,
     return value;
 }
 
-void
-_notmuch_message_ensure_metadata (notmuch_message_t *message)
+static void
+_notmuch_message_ensure_metadata (notmuch_message_t *message, void *field)
 {
     Xapian::TermIterator i, end;
+
+    if (field && (message->last_view >= message->notmuch->view))
+       return;
+
     const char *thread_prefix = _find_prefix ("thread"),
        *tag_prefix = _find_prefix ("tag"),
        *id_prefix = _find_prefix ("id"),
@@ -327,73 +337,88 @@ _notmuch_message_ensure_metadata (notmuch_message_t *message)
      * slightly more costly than looking up individual fields if only
      * one field of the message object is actually used, it's a huge
      * win as more fields are used. */
+    for (int count=0; count < 3; count++) {
+       try {
+           i = message->doc.termlist_begin ();
+           end = message->doc.termlist_end ();
+
+           /* Get thread */
+           if (!message->thread_id)
+               message->thread_id =
+                   _notmuch_message_get_term (message, i, end, thread_prefix);
+
+           /* Get tags */
+           assert (strcmp (thread_prefix, tag_prefix) < 0);
+           if (!message->tag_list) {
+               message->tag_list =
+                   _notmuch_database_get_terms_with_prefix (message, i, end,
+                                                            tag_prefix);
+               _notmuch_string_list_sort (message->tag_list);
+           }
 
-    i = message->doc.termlist_begin ();
-    end = message->doc.termlist_end ();
+           /* Get id */
+           assert (strcmp (tag_prefix, id_prefix) < 0);
+           if (!message->message_id)
+               message->message_id =
+                   _notmuch_message_get_term (message, i, end, id_prefix);
+
+           /* Get document type */
+           assert (strcmp (id_prefix, type_prefix) < 0);
+           if (! NOTMUCH_TEST_BIT (message->lazy_flags, NOTMUCH_MESSAGE_FLAG_GHOST)) {
+               i.skip_to (type_prefix);
+               /* "T" is the prefix "type" fields.  See
+                * BOOLEAN_PREFIX_INTERNAL. */
+               if (*i == "Tmail")
+                   NOTMUCH_CLEAR_BIT (&message->flags, NOTMUCH_MESSAGE_FLAG_GHOST);
+               else if (*i == "Tghost")
+                   NOTMUCH_SET_BIT (&message->flags, NOTMUCH_MESSAGE_FLAG_GHOST);
+               else
+                   INTERNAL_ERROR ("Message without type term");
+               NOTMUCH_SET_BIT (&message->lazy_flags, NOTMUCH_MESSAGE_FLAG_GHOST);
+           }
 
-    /* Get thread */
-    if (!message->thread_id)
-       message->thread_id =
-           _notmuch_message_get_term (message, i, end, thread_prefix);
-
-    /* Get tags */
-    assert (strcmp (thread_prefix, tag_prefix) < 0);
-    if (!message->tag_list) {
-       message->tag_list =
-           _notmuch_database_get_terms_with_prefix (message, i, end,
-                                                    tag_prefix);
-       _notmuch_string_list_sort (message->tag_list);
-    }
+           /* Get filename list.  Here we get only the terms.  We lazily
+            * expand them to full file names when needed in
+            * _notmuch_message_ensure_filename_list. */
+           assert (strcmp (type_prefix, filename_prefix) < 0);
+           if (!message->filename_term_list && !message->filename_list)
+               message->filename_term_list =
+                   _notmuch_database_get_terms_with_prefix (message, i, end,
+                                                            filename_prefix);
 
-    /* Get id */
-    assert (strcmp (tag_prefix, id_prefix) < 0);
-    if (!message->message_id)
-       message->message_id =
-           _notmuch_message_get_term (message, i, end, id_prefix);
-
-    /* Get document type */
-    assert (strcmp (id_prefix, type_prefix) < 0);
-    if (! NOTMUCH_TEST_BIT (message->lazy_flags, NOTMUCH_MESSAGE_FLAG_GHOST)) {
-       i.skip_to (type_prefix);
-       /* "T" is the prefix "type" fields.  See
-        * BOOLEAN_PREFIX_INTERNAL. */
-       if (*i == "Tmail")
-           NOTMUCH_CLEAR_BIT (&message->flags, NOTMUCH_MESSAGE_FLAG_GHOST);
-       else if (*i == "Tghost")
-           NOTMUCH_SET_BIT (&message->flags, NOTMUCH_MESSAGE_FLAG_GHOST);
-       else
-           INTERNAL_ERROR ("Message without type term");
-       NOTMUCH_SET_BIT (&message->lazy_flags, NOTMUCH_MESSAGE_FLAG_GHOST);
-    }
 
-    /* Get filename list.  Here we get only the terms.  We lazily
-     * expand them to full file names when needed in
-     * _notmuch_message_ensure_filename_list. */
-    assert (strcmp (type_prefix, filename_prefix) < 0);
-    if (!message->filename_term_list && !message->filename_list)
-       message->filename_term_list =
-           _notmuch_database_get_terms_with_prefix (message, i, end,
-                                                    filename_prefix);
-
-
-    /* Get property terms. Mimic the setup with filenames above */
-    assert (strcmp (filename_prefix, property_prefix) < 0);
-    if (!message->property_map && !message->property_term_list)
-       message->property_term_list =
-           _notmuch_database_get_terms_with_prefix (message, i, end,
-                                                    property_prefix);
-
-    /* Get reply to */
-    assert (strcmp (property_prefix, replyto_prefix) < 0);
-    if (!message->in_reply_to)
-       message->in_reply_to =
-           _notmuch_message_get_term (message, i, end, replyto_prefix);
-
-
-    /* It's perfectly valid for a message to have no In-Reply-To
-     * header. For these cases, we return an empty string. */
-    if (!message->in_reply_to)
-       message->in_reply_to = talloc_strdup (message, "");
+           /* Get property terms. Mimic the setup with filenames above */
+           assert (strcmp (filename_prefix, property_prefix) < 0);
+           if (!message->property_map && !message->property_term_list)
+               message->property_term_list =
+                   _notmuch_database_get_terms_with_prefix (message, i, end,
+                                                        property_prefix);
+
+           /* Get reply to */
+           assert (strcmp (property_prefix, replyto_prefix) < 0);
+           if (!message->in_reply_to)
+               message->in_reply_to =
+                   _notmuch_message_get_term (message, i, end, replyto_prefix);
+
+
+           /* It's perfectly valid for a message to have no In-Reply-To
+            * header. For these cases, we return an empty string. */
+           if (!message->in_reply_to)
+               message->in_reply_to = talloc_strdup (message, "");
+
+           /* all the way without an exception */
+           break;
+       } catch (const Xapian::DatabaseModifiedError &error) {
+           notmuch_status_t status = _notmuch_database_reopen (message->notmuch);
+           if (status != NOTMUCH_STATUS_SUCCESS)
+               INTERNAL_ERROR ("unhandled error from notmuch_database_reopen: %s\n",
+                               notmuch_status_to_string (status));
+       } catch (const Xapian::Error &error) {
+           INTERNAL_ERROR ("A Xapian exception occurred fetching message metadata: %s\n",
+                           error.get_msg().c_str());
+       }
+    }
+    message->last_view = message->notmuch->view;
 }
 
 void
@@ -448,8 +473,7 @@ _notmuch_message_get_doc_id (notmuch_message_t *message)
 const char *
 notmuch_message_get_message_id (notmuch_message_t *message)
 {
-    if (!message->message_id)
-       _notmuch_message_ensure_metadata (message);
+    _notmuch_message_ensure_metadata (message, message->message_id);
     if (!message->message_id)
        INTERNAL_ERROR ("Message with document ID of %u has no message ID.\n",
                        message->doc_id);
@@ -524,16 +548,14 @@ notmuch_message_get_header (notmuch_message_t *message, const char *header)
 const char *
 _notmuch_message_get_in_reply_to (notmuch_message_t *message)
 {
-    if (!message->in_reply_to)
-       _notmuch_message_ensure_metadata (message);
+    _notmuch_message_ensure_metadata (message, message->in_reply_to);
     return message->in_reply_to;
 }
 
 const char *
 notmuch_message_get_thread_id (notmuch_message_t *message)
 {
-    if (!message->thread_id)
-       _notmuch_message_ensure_metadata (message);
+    _notmuch_message_ensure_metadata (message, message->thread_id);
     if (!message->thread_id)
        INTERNAL_ERROR ("Message with document ID of %u has no thread ID.\n",
                        message->doc_id);
@@ -836,8 +858,7 @@ _notmuch_message_ensure_filename_list (notmuch_message_t *message)
     if (message->filename_list)
        return;
 
-    if (!message->filename_term_list)
-       _notmuch_message_ensure_metadata (message);
+    _notmuch_message_ensure_metadata (message, message->filename_term_list);
 
     message->filename_list = _notmuch_string_list_create (message);
     node = message->filename_term_list->head;
@@ -931,7 +952,7 @@ notmuch_message_get_flag (notmuch_message_t *message,
 {
     if (flag == NOTMUCH_MESSAGE_FLAG_GHOST &&
        ! NOTMUCH_TEST_BIT (message->lazy_flags, flag))
-       _notmuch_message_ensure_metadata (message);
+       _notmuch_message_ensure_metadata (message, NULL);
 
     return NOTMUCH_TEST_BIT (message->flags, flag);
 }
@@ -972,8 +993,7 @@ notmuch_message_get_tags (notmuch_message_t *message)
 {
     notmuch_tags_t *tags;
 
-    if (!message->tag_list)
-       _notmuch_message_ensure_metadata (message);
+    _notmuch_message_ensure_metadata (message, message->tag_list);
 
     tags = _notmuch_tags_create (message, message->tag_list);
     /* _notmuch_tags_create steals the reference to the tag_list, but
@@ -1801,7 +1821,7 @@ _notmuch_message_database (notmuch_message_t *message)
     return message->notmuch;
 }
 
-void
+static void
 _notmuch_message_ensure_property_map (notmuch_message_t *message)
 {
     notmuch_string_node_t *node;
@@ -1809,8 +1829,7 @@ _notmuch_message_ensure_property_map (notmuch_message_t *message)
     if (message->property_map)
        return;
 
-    if (!message->property_term_list)
-       _notmuch_message_ensure_metadata (message);
+    _notmuch_message_ensure_metadata (message, message->property_term_list);
 
     message->property_map = _notmuch_string_map_create (message);
 
index 7b35fc5b0ca8bc4656841a14c6212814f05d2fbc..8587e86ca57ad6697c4966f8fcf8ee9fc677c941 100644 (file)
@@ -192,6 +192,9 @@ _notmuch_message_id_compressed (void *ctx, const char *message_id);
 notmuch_status_t
 _notmuch_database_ensure_writable (notmuch_database_t *notmuch);
 
+notmuch_status_t
+_notmuch_database_reopen (notmuch_database_t *notmuch);
+
 void
 _notmuch_database_log (notmuch_database_t *notmuch,
                       const char *format, ...);
index 18678c0a766c535f6ea2b1bafb94f7e72e9ab4fb..16da8be987fda37a25e6854963ab0637c7fd04e3 100644 (file)
@@ -126,15 +126,15 @@ typedef enum _notmuch_status {
     NOTMUCH_STATUS_READ_ONLY_DATABASE,
     /**
      * A Xapian exception occurred.
+     *
+     * @todo We don't really want to expose this lame XAPIAN_EXCEPTION
+     * value. Instead we should map to things like DATABASE_LOCKED or
+     * whatever.
      */
     NOTMUCH_STATUS_XAPIAN_EXCEPTION,
     /**
      * An error occurred trying to read or write to a file (this could
      * be file not found, permission denied, etc.)
-     *
-     * @todo We don't really want to expose this lame XAPIAN_EXCEPTION
-     * value. Instead we should map to things like DATABASE_LOCKED or
-     * whatever.
      */
     NOTMUCH_STATUS_FILE_ERROR,
     /**
@@ -1811,7 +1811,7 @@ const char *
 notmuch_message_properties_key (notmuch_message_properties_t *properties);
 
 /**
- * Return the key from the current (key,value) pair.
+ * Return the value from the current (key,value) pair.
  *
  * This could be useful if iterating for a prefix.
  *
index 53efd4e18fc1698d88c0b8ac476fd33094f2e2fc..4ccd81046128052f3461622e8a58c5c63af782f0 100644 (file)
@@ -606,8 +606,9 @@ _notmuch_query_count_documents (notmuch_query_t *query, const char *type, unsign
        /*
         * Set the checkatleast parameter to the number of documents
         * in the database to make get_matches_estimated() exact.
+        * Set the max parameter to 0 to avoid fetching documents we will discard.
         */
-       mset = enquire.get_mset (0, notmuch->xapian_db->get_doccount (),
+       mset = enquire.get_mset (0, 0,
                                 notmuch->xapian_db->get_doccount ());
 
        count = mset.get_matches_estimated();
index b7dea1c2a577f4277455eaa0cfe9d2096aaab7c9..cb55b49a4f11a324744ccccc3e7876c945d297ca 100644 (file)
 
 #include "notmuch-private.h"
 
-#include "libsha1.h"
-
-/* Just some simple interfaces on top of libsha1 so that we can leave
- * libsha1 as untouched as possible. */
-
-static char *
-_hex_of_sha1_digest (const unsigned char digest[SHA1_DIGEST_SIZE])
-{
-    char *result, *r;
-    int i;
-
-    result = xcalloc (SHA1_DIGEST_SIZE * 2 + 1, 1);
-
-    for (r = result, i = 0;
-        i < SHA1_DIGEST_SIZE;
-        r += 2, i++)
-    {
-       sprintf (r, "%02x", digest[i]);
-    }
-
-    return result;
-}
+#include <glib.h>
 
 /* Create a hexadecimal string version of the SHA-1 digest of 'str'
  * (including its null terminating character).
@@ -52,16 +31,15 @@ _hex_of_sha1_digest (const unsigned char digest[SHA1_DIGEST_SIZE])
 char *
 _notmuch_sha1_of_string (const char *str)
 {
-    sha1_ctx sha1;
-    unsigned char digest[SHA1_DIGEST_SIZE];
-
-    sha1_begin (&sha1);
+    GChecksum *sha1;
+    char *digest;
 
-    sha1_hash ((unsigned char *) str, strlen (str) + 1, &sha1);
+    sha1 = g_checksum_new (G_CHECKSUM_SHA1);
+    g_checksum_update (sha1, (const guchar *) str, strlen (str) + 1);
+    digest = xstrdup (g_checksum_get_string (sha1));
+    g_checksum_free (sha1);
 
-    sha1_end (digest, &sha1);
-
-    return _hex_of_sha1_digest (digest);
+    return digest;
 }
 
 /* Create a hexadecimal string version of the SHA-1 digest of the
@@ -80,35 +58,36 @@ _notmuch_sha1_of_file (const char *filename)
 #define BLOCK_SIZE 4096
     unsigned char block[BLOCK_SIZE];
     size_t bytes_read;
-    sha1_ctx sha1;
-    unsigned char digest[SHA1_DIGEST_SIZE];
-    char *result;
+    GChecksum *sha1;
+    char *digest = NULL;
 
     file = fopen (filename, "r");
     if (file == NULL)
        return NULL;
 
-    sha1_begin (&sha1);
+    sha1 = g_checksum_new (G_CHECKSUM_SHA1);
+    if (sha1 == NULL)
+       goto DONE;
 
     while (1) {
        bytes_read = fread (block, 1, 4096, file);
        if (bytes_read == 0) {
-           if (feof (file)) {
+           if (feof (file))
                break;
-           } else if (ferror (file)) {
-               fclose (file);
-               return NULL;
-           }
+           else if (ferror (file))
+               goto DONE;
        } else {
-           sha1_hash (block, bytes_read, &sha1);
+           g_checksum_update (sha1, block, bytes_read);
        }
     }
 
-    sha1_end (digest, &sha1);
-
-    result = _hex_of_sha1_digest (digest);
+    digest = xstrdup (g_checksum_get_string (sha1));
 
-    fclose (file);
+  DONE:
+    if (sha1)
+       g_checksum_free (sha1);
+    if (file)
+       fclose (file);
 
-    return result;
+    return digest;
 }
index d026e6004239a1868dd44dde4b16de58fc0351a6..21b087980a17f45a8d7537b5d7116fbdee449313 100644 (file)
@@ -145,7 +145,7 @@ chomp_newline (char *str)
  * this.  New (required) map fields can be added without increasing
  * this.
  */
-#define NOTMUCH_FORMAT_CUR 2
+#define NOTMUCH_FORMAT_CUR 3
 /* The minimum supported structured output format version.  Requests
  * for format versions below this will return an error. */
 #define NOTMUCH_FORMAT_MIN 1
index e5d42a0cbfd505ed9f1ba5b6984251a7324a3061..b202bb1e229988f21b2807e49b0c62c60cf5991e 100644 (file)
@@ -202,6 +202,85 @@ get_username_from_passwd_file (void *ctx)
     return name;
 }
 
+static notmuch_bool_t
+get_config_from_file (notmuch_config_t *config, notmuch_bool_t create_new)
+{
+    #define BUF_SIZE 4096
+    char *config_str = NULL;
+    int config_len = 0;
+    int config_bufsize = BUF_SIZE;
+    size_t len;
+    GError *error = NULL;
+    notmuch_bool_t ret = FALSE;
+
+    FILE *fp = fopen(config->filename, "r");
+    if (fp == NULL) {
+       /* If create_new is true, then the caller is prepared for a
+        * default configuration file in the case of FILE NOT FOUND.
+        */
+       if (create_new) {
+           config->is_new = TRUE;
+           ret = TRUE;
+           goto out;
+       } else if (errno == ENOENT) {
+           fprintf (stderr, "Configuration file %s not found.\n"
+                    "Try running 'notmuch setup' to create a configuration.\n",
+                    config->filename);
+           goto out;
+       } else {
+           fprintf (stderr, "Error opening config file '%s': %s\n"
+                    "Try running 'notmuch setup' to create a configuration.\n",
+                    config->filename, strerror(errno));
+           goto out;
+       }
+    }
+
+    config_str = talloc_zero_array (config, char, config_bufsize);
+    if (config_str == NULL) {
+       fprintf (stderr, "Error reading '%s': Out of memory\n", config->filename);
+       goto out;
+    }
+
+    while ((len = fread (config_str + config_len, 1,
+                        config_bufsize - config_len, fp)) > 0) {
+       config_len += len;
+       if (config_len == config_bufsize) {
+           config_bufsize += BUF_SIZE;
+           config_str = talloc_realloc (config, config_str, char, config_bufsize);
+           if (config_str == NULL) {
+               fprintf (stderr, "Error reading '%s': Failed to reallocate memory\n",
+                        config->filename);
+               goto out;
+           }
+       }
+    }
+
+    if (ferror (fp)) {
+       fprintf (stderr, "Error reading '%s': I/O error\n", config->filename);
+       goto out;
+    }
+
+    if (g_key_file_load_from_data (config->key_file, config_str, config_len,
+                                  G_KEY_FILE_KEEP_COMMENTS, &error)) {
+       ret = TRUE;
+       goto out;
+    }
+
+    fprintf (stderr, "Error parsing config file '%s': %s\n",
+            config->filename, error->message);
+
+    g_error_free (error);
+
+out:
+    if (fp)
+       fclose(fp);
+
+    if (config_str)
+       talloc_free(config_str);
+
+    return ret;
+}
+
 /* Open the named notmuch configuration file. If the filename is NULL,
  * the value of the environment variable $NOTMUCH_CONFIG will be used.
  * If $NOTMUCH_CONFIG is unset, the default configuration file
@@ -255,7 +334,7 @@ notmuch_config_open (void *ctx,
     int file_had_search_group;
     int file_had_crypto_group;
 
-    notmuch_config_t *config = talloc (ctx, notmuch_config_t);
+    notmuch_config_t *config = talloc_zero (ctx, notmuch_config_t);
     if (config == NULL) {
        fprintf (stderr, "Out of memory.\n");
        return NULL;
@@ -263,6 +342,9 @@ notmuch_config_open (void *ctx,
     
     talloc_set_destructor (config, notmuch_config_destructor);
 
+    /* non-zero defaults */
+    config->maildir_synchronize_flags = TRUE;
+
     if (filename) {
        config->filename = talloc_strdup (config, filename);
     } else if ((notmuch_config_env = getenv ("NOTMUCH_CONFIG"))) {
@@ -274,51 +356,9 @@ notmuch_config_open (void *ctx,
 
     config->key_file = g_key_file_new ();
 
-    config->is_new = FALSE;
-    config->database_path = NULL;
-    config->user_name = NULL;
-    config->user_primary_email = NULL;
-    config->user_other_email = NULL;
-    config->user_other_email_length = 0;
-    config->new_tags = NULL;
-    config->new_tags_length = 0;
-    config->new_ignore = NULL;
-    config->new_ignore_length = 0;
-    config->maildir_synchronize_flags = TRUE;
-    config->search_exclude_tags = NULL;
-    config->search_exclude_tags_length = 0;
-    config->crypto_gpg_path = NULL;
-
-    if (! g_key_file_load_from_file (config->key_file,
-                                    config->filename,
-                                    G_KEY_FILE_KEEP_COMMENTS,
-                                    &error))
-    {
-       if (error->domain == G_FILE_ERROR && error->code == G_FILE_ERROR_NOENT) {
-           /* If create_new is true, then the caller is prepared for a
-            * default configuration file in the case of FILE NOT
-            * FOUND.
-            */
-           if (create_new) {
-               g_error_free (error);
-               config->is_new = TRUE;
-           } else {
-               fprintf (stderr, "Configuration file %s not found.\n"
-                        "Try running 'notmuch setup' to create a configuration.\n",
-                        config->filename);
-               talloc_free (config);
-               g_error_free (error);
-               return NULL;
-           }
-       }
-       else
-       {
-           fprintf (stderr, "Error reading configuration file %s: %s\n",
-                    config->filename, error->message);
-           talloc_free (config);
-           g_error_free (error);
-           return NULL;
-       }
+    if (! get_config_from_file (config, create_new)) {
+       talloc_free (config);
+       return NULL;
     }
 
     /* Whenever we know of configuration sections that don't appear in
@@ -591,11 +631,11 @@ _config_get_list (notmuch_config_t *config,
 
 static void
 _config_set_list (notmuch_config_t *config,
-                 const char *group, const char *name,
+                 const char *group, const char *key,
                  const char *list[],
                  size_t length, const char ***config_var )
 {
-    g_key_file_set_string_list (config->key_file, group, name, list, length);
+    g_key_file_set_string_list (config->key_file, group, key, list, length);
 
     /* drop the cached value */
     talloc_free (*config_var);
diff --git a/notmuch-emacs-mua b/notmuch-emacs-mua
deleted file mode 100755 (executable)
index f9d8371..0000000
+++ /dev/null
@@ -1,153 +0,0 @@
-#!/usr/bin/env bash
-#
-# notmuch-emacs-mua - start composing a mail on the command line
-#
-# Copyright Â© 2014 Jani Nikula
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program.  If not, see https://www.gnu.org/licenses/ .
-#
-# Authors: Jani Nikula <jani@nikula.org>
-#
-
-set -eu
-
-# escape: "expand" '\' as '\\' and '"' as '\"'
-# calling convention: escape -v var "$arg" (like in bash printf).
-escape ()
-{
-    local __escape_arg__=${3//\\/\\\\}
-    printf -v $2 '%s' "${__escape_arg__//\"/\\\"}"
-}
-
-EMACS=${EMACS:-emacs}
-EMACSCLIENT=${EMACSCLIENT:-emacsclient}
-
-PRINT_ONLY=
-NO_WINDOW=
-USE_EMACSCLIENT=
-AUTO_DAEMON=
-CREATE_FRAME=
-
-escape -v pwd "$PWD"
-
-# The crux of it all: construct an elisp progn and eval it.
-ELISP="(prog1 'done (require 'notmuch) (cd \"$pwd\") (notmuch-mua-new-mail)"
-
-# Short options compatible with mutt(1).
-while getopts :s:c:b:i:h opt; do
-    # Handle errors and long options.
-    case "${opt}" in
-       :)
-           echo "$0: short option -${OPTARG} requires an argument." >&2
-           exit 1
-           ;;
-       \?)
-           opt=$1
-           if [ "${OPTARG}" != "-" ]; then
-               echo "$0: unknown short option -${OPTARG}." >&2
-               exit 1
-           fi
-
-           case "${opt}" in
-               # Long options with arguments.
-               --subject=*|--to=*|--cc=*|--bcc=*|--body=*)
-                   OPTARG=${opt#--*=}
-                   opt=${opt%%=*}
-                   ;;
-               # Long options without arguments.
-               --help|--print|--no-window-system|--client|--auto-daemon|--create-frame)
-                   ;;
-               *)
-                   echo "$0: unknown long option ${opt}, or argument mismatch." >&2
-                   exit 1
-                   ;;
-           esac
-           # getopts does not do this for what it considers errors.
-           OPTIND=$((OPTIND + 1))
-           ;;
-    esac
-
-    escape -v OPTARG "${OPTARG-none}"
-
-    case "${opt}" in
-       --help|h)
-           exec man notmuch-emacs-mua
-           ;;
-       --subject|s)
-           ELISP="${ELISP} (message-goto-subject) (insert \"${OPTARG}\")"
-           ;;
-       --to)
-           ELISP="${ELISP} (message-goto-to) (insert \"${OPTARG}, \")"
-           ;;
-       --cc|c)
-           ELISP="${ELISP} (message-goto-cc) (insert \"${OPTARG}, \")"
-           ;;
-       --bcc|b)
-           ELISP="${ELISP} (message-goto-bcc) (insert \"${OPTARG}, \")"
-           ;;
-       --body|i)
-           ELISP="${ELISP} (message-goto-body) (insert-file \"${OPTARG}\")"
-           ;;
-       --print)
-           PRINT_ONLY=1
-           ;;
-       --no-window-system)
-           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.
-           echo "$0: internal error (option ${opt})." >&2
-           exit 1
-           ;;
-    esac
-
-    shift $((OPTIND - 1))
-    OPTIND=1
-done
-
-# Positional parameters.
-for arg; do
-    escape -v arg "${arg}"
-    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} (message-add-action #'save-buffers-kill-terminal 'exit)"
-fi
-
-# End progn.
-ELISP="${ELISP})"
-
-if [ -n "$PRINT_ONLY" ]; then
-    echo ${ELISP}
-    exit 0
-fi
-
-if [ -n "$USE_EMACSCLIENT" ]; then
-    # Evaluate the progn.
-    exec ${EMACSCLIENT} ${NO_WINDOW} ${CREATE_FRAME} ${AUTO_DAEMON} --eval "${ELISP}"
-else
-    exec ${EMACS} ${NO_WINDOW} --eval "${ELISP}"
-fi
index cc680b412a45e023e1b1b6cec0b92d37cf8c774c..13212639cc8ec669df4ea3d47f6c0d4d9a3fd834 100644 (file)
@@ -738,18 +738,20 @@ count_files (const char *path, int *count, add_files_state_t *state)
         entry = fs_entries[i];
 
        /* Ignore special directories to avoid infinite recursion.
-        * Also ignore the .notmuch directory and files/directories
-        * the user has configured to be ignored.
+        * Also ignore the .notmuch directory.
         */
        if (strcmp (entry->d_name, ".") == 0 ||
            strcmp (entry->d_name, "..") == 0 ||
-           strcmp (entry->d_name, ".notmuch") == 0 ||
-           _entry_in_ignore_list (entry->d_name, state))
-       {
-           if (state->debug && _entry_in_ignore_list (entry->d_name, state))
+           strcmp (entry->d_name, ".notmuch") == 0)
+           continue;
+
+       /* Ignore any files/directories the user has configured to be
+        * ignored
+        */
+       if (_entry_in_ignore_list (entry->d_name, state)) {
+           if (state->debug)
                printf ("(D) count_files: explicitly ignoring %s/%s\n",
-                       path,
-                       entry->d_name);
+                       path, entry->d_name);
            continue;
        }
 
index 22fa655ad20d8deea55297af4616c2cec7e292d0..744b62727c26afed43e3685a160b2b1b52d29865 100644 (file)
@@ -110,6 +110,17 @@ _get_one_line_summary (const void *ctx, notmuch_message_t *message)
                            from, relative_date, tags);
 }
 
+static const char *_get_disposition(GMimeObject *meta)
+{
+    GMimeContentDisposition *disposition;
+
+    disposition = g_mime_object_get_content_disposition (meta);
+    if (!disposition)
+       return NULL;
+
+    return g_mime_content_disposition_get_disposition (disposition);
+}
+
 /* Emit a sequence of key/value pairs for the metadata of message.
  * The caller should begin a map before calling this. */
 static void
@@ -133,7 +144,20 @@ format_message_sprinter (sprinter_t *sp, notmuch_message_t *message)
     sp->boolean (sp, notmuch_message_get_flag (message, NOTMUCH_MESSAGE_FLAG_EXCLUDED));
 
     sp->map_key (sp, "filename");
-    sp->string (sp, notmuch_message_get_filename (message));
+    if (notmuch_format_version >= 3) {
+       notmuch_filenames_t *filenames;
+
+       sp->begin_list (sp);
+       for (filenames = notmuch_message_get_filenames (message);
+            notmuch_filenames_valid (filenames);
+            notmuch_filenames_move_to_next (filenames)) {
+           sp->string (sp, notmuch_filenames_get (filenames));
+       }
+       notmuch_filenames_destroy (filenames);
+       sp->end (sp);
+    } else {
+       sp->string (sp, notmuch_message_get_filename (message));
+    }
 
     sp->map_key (sp, "timestamp");
     date = notmuch_message_get_date (message);
@@ -450,14 +474,13 @@ format_part_text (const void *ctx, sprinter_t *sp, mime_node_t *node,
                notmuch_message_get_flag (message, NOTMUCH_MESSAGE_FLAG_EXCLUDED) ? 1 : 0,
                notmuch_message_get_filename (message));
     } else {
-       GMimeContentDisposition *disposition = g_mime_object_get_content_disposition (meta);
+       const char *disposition = _get_disposition (meta);
        const char *cid = g_mime_object_get_content_id (meta);
        const char *filename = leaf ?
            g_mime_part_get_filename (GMIME_PART (node->part)) : NULL;
 
        if (disposition &&
-           strcasecmp (g_mime_content_disposition_get_disposition (disposition),
-                       GMIME_DISPOSITION_ATTACHMENT) == 0)
+           strcasecmp (disposition, GMIME_DISPOSITION_ATTACHMENT) == 0)
            part_type = "attachment";
        else
            part_type = "part";
@@ -572,6 +595,7 @@ format_part_sprinter (const void *ctx, sprinter_t *sp, mime_node_t *node,
     GMimeObject *meta = node->envelope_part ?
        GMIME_OBJECT (node->envelope_part) : node->part;
     GMimeContentType *content_type = g_mime_object_get_content_type (meta);
+    const char *disposition = _get_disposition (meta);
     const char *cid = g_mime_object_get_content_id (meta);
     const char *filename = GMIME_IS_PART (node->part) ?
        g_mime_part_get_filename (GMIME_PART (node->part)) : NULL;
@@ -601,6 +625,11 @@ format_part_sprinter (const void *ctx, sprinter_t *sp, mime_node_t *node,
     sp->map_key (sp, "content-type");
     sp->string (sp, g_mime_content_type_to_string (content_type));
 
+    if (disposition) {
+       sp->map_key (sp, "content-disposition");
+       sp->string (sp, disposition);
+    }
+
     if (cid) {
        sp->map_key (sp, "content-id");
        sp->string (sp, cid);
index 38b73c1d2accea68fa9d667fccfe518f97ab86ea..b9c320329dd526000b436210d52cceee0ced0717 100644 (file)
--- a/notmuch.c
+++ b/notmuch.c
@@ -363,6 +363,39 @@ notmuch_command (notmuch_config_t *config,
     return EXIT_SUCCESS;
 }
 
+/*
+ * Try to run subcommand in argv[0] as notmuch- prefixed external
+ * command. argv must be NULL terminated (argv passed to main always
+ * is).
+ *
+ * Does not return if the external command is found and
+ * executed. Return TRUE if external command is not found. Return
+ * FALSE on errors.
+ */
+static notmuch_bool_t try_external_command(char *argv[])
+{
+    char *old_argv0 = argv[0];
+    notmuch_bool_t ret = TRUE;
+
+    argv[0] = talloc_asprintf (NULL, "notmuch-%s", old_argv0);
+
+    /*
+     * This will only return on errors. Not finding an external
+     * command (ENOENT) is not an error from our perspective.
+     */
+    execvp (argv[0], argv);
+    if (errno != ENOENT) {
+       fprintf (stderr, "Error: Running external command '%s' failed: %s\n",
+                argv[0], strerror(errno));
+       ret = FALSE;
+    }
+
+    talloc_free (argv[0]);
+    argv[0] = old_argv0;
+
+    return ret;
+}
+
 int
 main (int argc, char *argv[])
 {
@@ -406,8 +439,10 @@ main (int argc, char *argv[])
 
     command = find_command (command_name);
     if (!command) {
-       fprintf (stderr, "Error: Unknown command '%s' (see \"notmuch help\")\n",
-                command_name);
+       /* This won't return if the external command is found. */
+       if (try_external_command(argv + opt_index))
+           fprintf (stderr, "Error: Unknown command '%s' (see \"notmuch help\")\n",
+                    command_name);
        ret = EXIT_FAILURE;
        goto DONE;
     }
diff --git a/notmuch.desktop b/notmuch.desktop
deleted file mode 100644 (file)
index f160047..0000000
+++ /dev/null
@@ -1,7 +0,0 @@
-[Desktop Entry]
-Name=Notmuch (emacs interface)
-Exec=emacs -f notmuch
-Icon=emblem-mail
-Terminal=false
-Type=Application
-Categories=Network;Email;
index 91b369367bdf0814706815ddd9a1698bf170b651..f8cf90d07e2dc5c1acd59ad13531800a783eb7a5 100644 (file)
@@ -77,4 +77,4 @@ check: test
 SRCS := $(SRCS) $(test_srcs)
 CLEAN += $(TEST_BINARIES) $(addsuffix .o,$(TEST_BINARIES)) \
         $(dir)/database-test.o \
-        $(dir)/corpus.mail $(dir)/test-results $(dir)/tmp.*
+        $(dir)/corpora.mail $(dir)/test-results $(dir)/tmp.*
index d6811bd1075639d71986b283880d6bb40312a3b1..0a8d6cdf40fc3ecc83d05f5347cd73c67e62f5f8 100755 (executable)
@@ -92,7 +92,7 @@ test_expect_equal \
     "$(echo $PATH|cut -f1 -d: | sed -e 's,/test/valgrind/bin$,,')"
 
 test_begin_subtest 'notmuch is compiled with debugging symbols'
-readelf --sections $(which notmuch) | grep \.debug
+readelf --sections $(command -v notmuch) | grep \.debug
 test_expect_equal 0 $?
 
 test_done
index beeb574a3b30e36fb6e7694b385a769701f0e54e..9115de820770181bd6ff8f8c9b655d047e9fd8c9 100755 (executable)
@@ -298,4 +298,38 @@ output=$(NOTMUCH_NEW --debug 2>&1 | sed 's/: .*$//' )
 chmod u+w  ${MAIL_DIR}/.notmuch/xapian/*.${db_ending}
 test_expect_equal "$output" "A Xapian exception occurred opening database"
 
+
+test_begin_subtest "Handle files vanishing between scandir and add_file"
+
+# A file for scandir to find. It won't get indexed, so can be empty.
+touch ${MAIL_DIR}/vanish
+
+# Breakpoint to remove the file before indexing
+cat <<EOF > notmuch-new-vanish.gdb
+set breakpoint pending on
+set logging file notmuch-new-vanish-gdb.log
+set logging on
+break add_file
+commands
+shell rm -f ${MAIL_DIR}/vanish
+continue
+end
+run
+EOF
+
+${TEST_GDB} --batch-silent --return-child-result -x notmuch-new-vanish.gdb \
+    --args notmuch new 2>OUTPUT 1>/dev/null
+echo "exit status: $?" >> OUTPUT
+
+# Clean up the file in case gdb isn't available.
+rm -f ${MAIL_DIR}/vanish
+
+cat <<EOF > EXPECTED
+Unexpected error with file ${MAIL_DIR}/vanish
+add_file: Something went wrong trying to read or write a file
+Error opening ${MAIL_DIR}/vanish: No such file or directory
+exit status: 75
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
 test_done
index d6933a7689712fed3ea97fb819d075597d550b57..d27e1babd0a600a60a29203b44a6d5d8ea74d39b 100755 (executable)
@@ -115,7 +115,7 @@ EOF
 
 backup_database
 test_begin_subtest "error message from query_search_messages"
-gdb --batch-silent --return-child-result -x count-files.gdb \
+${TEST_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
@@ -126,4 +126,32 @@ sed 's/^\(A Xapian exception [^:]*\):.*$/\1/' < OUTPUT > OUTPUT.clean
 test_expect_equal_file EXPECTED OUTPUT.clean
 restore_database
 
+test_begin_subtest "count library function is non-destructive"
+test_subtest_known_broken
+cat<<EOF > EXPECTED
+1: 52 messages
+2: 52 messages
+Exclude 'spam'
+3: 52 messages
+4: 52 messages
+EOF
+test_python <<EOF
+import sys
+import notmuch
+
+query_string = 'tag:inbox or tag:spam'
+tag_string = 'spam'
+
+database = notmuch.Database(mode=notmuch.Database.MODE.READ_ONLY)
+query = notmuch.Query(database, query_string)
+
+print("1: {} messages".format(query.count_messages()))
+print("2: {} messages".format(query.count_messages()))
+print("Exclude '{}'".format(tag_string))
+query.exclude_tag(tag_string)
+print("3: {} messages".format(query.count_messages()))
+print("4: {} messages".format(query.count_messages()))
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
 test_done
index 57472b913964e4ca0200de2cc5ccea5bb98253f5..9120debabf8c3f6ffe07c6d2b74a5cc17f746698 100755 (executable)
@@ -43,7 +43,7 @@ expected='[[[{
  "id": "'"${gen_msg_id}"'",
  "match": true,
  "excluded": false,
- "filename": "'"${cur_msg_filename}"'",
+ "filename": ["'"${cur_msg_filename}"'"],
  "timestamp": 946728000,
  "date_relative": "2000-01-01",
  "tags": ["inbox","unread"],
@@ -60,7 +60,7 @@ test_expect_equal_json "$output" "$expected"
 
 test_begin_subtest "Insert duplicate message"
 notmuch insert +duptag -unread < "$gen_msg_filename"
-output=$(notmuch search --output=files "subject:insert-subject" | wc -l)
+output=$((`notmuch search --output=files "subject:insert-subject" | wc -l`))
 test_expect_equal "$output" 2
 
 test_begin_subtest "Duplicate message does not change tags"
@@ -206,22 +206,22 @@ gen_insert_msg
 
 for code in  FILE_NOT_EMAIL READ_ONLY_DATABASE UPGRADE_REQUIRED PATH_ERROR; do
     test_expect_code 1 "EXIT_FAILURE when add_message returns $code" \
-         "gdb --batch-silent --return-child-result \
+         "${TEST_GDB} --batch-silent --return-child-result \
             -ex 'set args insert < $gen_msg_filename' \
             -x index-file-$code.gdb notmuch"
     test_expect_code 0 "success exit with --keep when add_message returns $code" \
-         "gdb --batch-silent --return-child-result \
+         "${TEST_GDB} --batch-silent --return-child-result \
             -ex 'set args insert --keep < $gen_msg_filename' \
             -x index-file-$code.gdb notmuch"
 done
 
 for code in OUT_OF_MEMORY XAPIAN_EXCEPTION ; do
     test_expect_code 75 "EX_TEMPFAIL when add_message returns $code" \
-         "gdb --batch-silent --return-child-result \
+         "${TEST_GDB} --batch-silent --return-child-result \
             -ex 'set args insert < $gen_msg_filename' \
             -x index-file-$code.gdb notmuch"
     test_expect_code 0 "success exit with --keep when add_message returns $code" \
-         "gdb --batch-silent --return-child-result \
+         "${TEST_GDB} --batch-silent --return-child-result \
             -ex 'set args insert --keep < $gen_msg_filename' \
             -x index-file-$code.gdb notmuch"
 done
index b346f37ee4717d43e8229079ec5d0482d84d08fe..5a954e3baf0d477e26bc570985c768fc7c20a10c 100755 (executable)
@@ -5,16 +5,16 @@ test_description="--format=json output"
 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\""
 output=$(notmuch show --format=json "json-show-message")
-test_expect_equal_json "$output" "[[[{\"id\": \"${gen_msg_id}\", \"match\": true, \"excluded\": false, \"filename\": \"${gen_msg_filename}\", \"timestamp\": 946728000, \"date_relative\": \"2000-01-01\", \"tags\": [\"inbox\",\"unread\"], \"headers\": {\"Subject\": \"json-show-subject\", \"From\": \"Notmuch Test Suite <test_suite@notmuchmail.org>\", \"To\": \"Notmuch Test Suite <test_suite@notmuchmail.org>\", \"Bcc\": \"test_suite+bcc@notmuchmail.org\", \"Reply-To\": \"test_suite+replyto@notmuchmail.org\", \"Date\": \"Sat, 01 Jan 2000 12:00:00 +0000\"}, \"body\": [{\"id\": 1, \"content-type\": \"text/plain\", \"content\": \"json-show-message\n\"}]}, []]]]"
+test_expect_equal_json "$output" "[[[{\"id\": \"${gen_msg_id}\", \"match\": true, \"excluded\": false, \"filename\": [\"${gen_msg_filename}\"], \"timestamp\": 946728000, \"date_relative\": \"2000-01-01\", \"tags\": [\"inbox\",\"unread\"], \"headers\": {\"Subject\": \"json-show-subject\", \"From\": \"Notmuch Test Suite <test_suite@notmuchmail.org>\", \"To\": \"Notmuch Test Suite <test_suite@notmuchmail.org>\", \"Bcc\": \"test_suite+bcc@notmuchmail.org\", \"Reply-To\": \"test_suite+replyto@notmuchmail.org\", \"Date\": \"Sat, 01 Jan 2000 12:00:00 +0000\"}, \"body\": [{\"id\": 1, \"content-type\": \"text/plain\", \"content\": \"json-show-message\n\"}]}, []]]]"
 
 # This should be the same output as above.
 test_begin_subtest "Show message: json --body=true"
 output=$(notmuch show --format=json --body=true "json-show-message")
-test_expect_equal_json "$output" "[[[{\"id\": \"${gen_msg_id}\", \"match\": true, \"excluded\": false, \"filename\": \"${gen_msg_filename}\", \"timestamp\": 946728000, \"date_relative\": \"2000-01-01\", \"tags\": [\"inbox\",\"unread\"], \"headers\": {\"Subject\": \"json-show-subject\", \"From\": \"Notmuch Test Suite <test_suite@notmuchmail.org>\", \"To\": \"Notmuch Test Suite <test_suite@notmuchmail.org>\", \"Bcc\": \"test_suite+bcc@notmuchmail.org\", \"Reply-To\": \"test_suite+replyto@notmuchmail.org\", \"Date\": \"Sat, 01 Jan 2000 12:00:00 +0000\"}, \"body\": [{\"id\": 1, \"content-type\": \"text/plain\", \"content\": \"json-show-message\n\"}]}, []]]]"
+test_expect_equal_json "$output" "[[[{\"id\": \"${gen_msg_id}\", \"match\": true, \"excluded\": false, \"filename\": [\"${gen_msg_filename}\"], \"timestamp\": 946728000, \"date_relative\": \"2000-01-01\", \"tags\": [\"inbox\",\"unread\"], \"headers\": {\"Subject\": \"json-show-subject\", \"From\": \"Notmuch Test Suite <test_suite@notmuchmail.org>\", \"To\": \"Notmuch Test Suite <test_suite@notmuchmail.org>\", \"Bcc\": \"test_suite+bcc@notmuchmail.org\", \"Reply-To\": \"test_suite+replyto@notmuchmail.org\", \"Date\": \"Sat, 01 Jan 2000 12:00:00 +0000\"}, \"body\": [{\"id\": 1, \"content-type\": \"text/plain\", \"content\": \"json-show-message\n\"}]}, []]]]"
 
 test_begin_subtest "Show message: json --body=false"
 output=$(notmuch show --format=json --body=false "json-show-message")
-test_expect_equal_json "$output" "[[[{\"id\": \"${gen_msg_id}\", \"match\": true, \"excluded\": false, \"filename\": \"${gen_msg_filename}\", \"timestamp\": 946728000, \"date_relative\": \"2000-01-01\", \"tags\": [\"inbox\",\"unread\"], \"headers\": {\"Subject\": \"json-show-subject\", \"From\": \"Notmuch Test Suite <test_suite@notmuchmail.org>\", \"To\": \"Notmuch Test Suite <test_suite@notmuchmail.org>\", \"Bcc\": \"test_suite+bcc@notmuchmail.org\", \"Reply-To\": \"test_suite+replyto@notmuchmail.org\", \"Date\": \"Sat, 01 Jan 2000 12:00:00 +0000\"}}, []]]]"
+test_expect_equal_json "$output" "[[[{\"id\": \"${gen_msg_id}\", \"match\": true, \"excluded\": false, \"filename\": [\"${gen_msg_filename}\"], \"timestamp\": 946728000, \"date_relative\": \"2000-01-01\", \"tags\": [\"inbox\",\"unread\"], \"headers\": {\"Subject\": \"json-show-subject\", \"From\": \"Notmuch Test Suite <test_suite@notmuchmail.org>\", \"To\": \"Notmuch Test Suite <test_suite@notmuchmail.org>\", \"Bcc\": \"test_suite+bcc@notmuchmail.org\", \"Reply-To\": \"test_suite+replyto@notmuchmail.org\", \"Date\": \"Sat, 01 Jan 2000 12:00:00 +0000\"}}, []]]]"
 
 test_begin_subtest "Search message: json"
 add_message "[subject]=\"json-search-subject\"" "[date]=\"Sat, 01 Jan 2000 12:00:00 -0000\"" "[body]=\"json-search-message\""
@@ -33,7 +33,7 @@ test_expect_equal_json "$output" "[{\"thread\": \"XXX\",
 test_begin_subtest "Show message: json, utf-8"
 add_message "[subject]=\"json-show-utf8-body-sübjéct\"" "[date]=\"Sat, 01 Jan 2000 12:00:00 -0000\"" "[body]=\"jsön-show-méssage\""
 output=$(notmuch show --format=json "jsön-show-méssage")
-test_expect_equal_json "$output" "[[[{\"id\": \"${gen_msg_id}\", \"match\": true, \"excluded\": false, \"filename\": \"${gen_msg_filename}\", \"timestamp\": 946728000, \"date_relative\": \"2000-01-01\", \"tags\": [\"inbox\",\"unread\"], \"headers\": {\"Subject\": \"json-show-utf8-body-sübjéct\", \"From\": \"Notmuch Test Suite <test_suite@notmuchmail.org>\", \"To\": \"Notmuch Test Suite <test_suite@notmuchmail.org>\", \"Date\": \"Sat, 01 Jan 2000 12:00:00 +0000\"}, \"body\": [{\"id\": 1, \"content-type\": \"text/plain\", \"content\": \"jsön-show-méssage\n\"}]}, []]]]"
+test_expect_equal_json "$output" "[[[{\"id\": \"${gen_msg_id}\", \"match\": true, \"excluded\": false, \"filename\": [\"${gen_msg_filename}\"], \"timestamp\": 946728000, \"date_relative\": \"2000-01-01\", \"tags\": [\"inbox\",\"unread\"], \"headers\": {\"Subject\": \"json-show-utf8-body-sübjéct\", \"From\": \"Notmuch Test Suite <test_suite@notmuchmail.org>\", \"To\": \"Notmuch Test Suite <test_suite@notmuchmail.org>\", \"Date\": \"Sat, 01 Jan 2000 12:00:00 +0000\"}, \"body\": [{\"id\": 1, \"content-type\": \"text/plain\", \"content\": \"jsön-show-méssage\n\"}]}, []]]]"
 
 test_begin_subtest "Show message: json, inline attachment filename"
 subject='json-show-inline-attachment-filename'
@@ -48,7 +48,7 @@ output=$(notmuch show --format=json "id:$id")
 filename=$(notmuch search --output=files "id:$id")
 # Get length of README after base64-encoding, minus additional newline.
 attachment_length=$(( $(base64 $TEST_DIRECTORY/README | wc -c) - 1 ))
-test_expect_equal_json "$output" "[[[{\"id\": \"$id\", \"match\": true, \"excluded\": false, \"filename\": \"$filename\", \"timestamp\": 946728000, \"date_relative\": \"2000-01-01\", \"tags\": [\"inbox\"], \"headers\": {\"Subject\": \"$subject\", \"From\": \"Notmuch Test Suite <test_suite@notmuchmail.org>\", \"To\": \"test_suite@notmuchmail.org\", \"Date\": \"Sat, 01 Jan 2000 12:00:00 +0000\"}, \"body\": [{\"id\": 1, \"content-type\": \"multipart/mixed\", \"content\": [{\"id\": 2, \"content-type\": \"text/plain\", \"content\": \"This is a test message with inline attachment with a filename\"}, {\"id\": 3, \"content-type\": \"application/octet-stream\", \"content-length\": $attachment_length, \"content-transfer-encoding\": \"base64\", \"filename\": \"README\"}]}]}, []]]]"
+test_expect_equal_json "$output" "[[[{\"id\": \"$id\", \"match\": true, \"excluded\": false, \"filename\": [\"$filename\"], \"timestamp\": 946728000, \"date_relative\": \"2000-01-01\", \"tags\": [\"inbox\"], \"headers\": {\"Subject\": \"$subject\", \"From\": \"Notmuch Test Suite <test_suite@notmuchmail.org>\", \"To\": \"test_suite@notmuchmail.org\", \"Date\": \"Sat, 01 Jan 2000 12:00:00 +0000\"}, \"body\": [{\"id\": 1, \"content-type\": \"multipart/mixed\", \"content\": [{\"id\": 2, \"content-type\": \"text/plain\", \"content\": \"This is a test message with inline attachment with a filename\"}, {\"id\": 3, \"content-type\": \"application/octet-stream\", \"content-length\": $attachment_length, \"content-transfer-encoding\": \"base64\", \"content-disposition\": \"inline\", \"filename\": \"README\"}]}]}, []]]]"
 
 test_begin_subtest "Search message: json, utf-8"
 add_message "[subject]=\"json-search-utf8-body-sübjéct\"" "[date]=\"Sat, 01 Jan 2000 12:00:00 -0000\"" "[body]=\"jsön-search-méssage\""
@@ -70,4 +70,71 @@ test_expect_code 20 "Format version: too low" \
 test_expect_code 21 "Format version: too high" \
     "notmuch search --format-version=999 \\*"
 
+test_begin_subtest "Show message: multiple filenames"
+add_message "[id]=message-id@example.com [filename]=copy1"
+add_message "[id]=message-id@example.com [filename]=copy2"
+cat <<EOF > EXPECTED
+[
+    [
+        [
+            {
+                "date_relative": "2001-01-05",
+                "excluded": false,
+                "filename": [
+                    "${MAIL_DIR}/copy1",
+                    "${MAIL_DIR}/copy2"
+                ],
+                "headers": {
+                    "Date": "Fri, 05 Jan 2001 15:43:52 +0000",
+                    "From": "Notmuch Test Suite <test_suite@notmuchmail.org>",
+                    "Subject": "Show message: multiple filenames",
+                    "To": "Notmuch Test Suite <test_suite@notmuchmail.org>"
+                },
+                "id": "message-id@example.com",
+                "match": true,
+                "tags": [
+                    "inbox",
+                    "unread"
+                ],
+                "timestamp": 978709432
+            },
+            []
+        ]
+    ]
+]
+EOF
+output=$(notmuch show --format=json --body=false id:message-id@example.com)
+test_expect_equal_json "$output" "$(cat EXPECTED)"
+
+test_begin_subtest "Show message: multiple filenames, format version 2"
+cat <<EOF > EXPECTED
+[
+    [
+        [
+            {
+                "date_relative": "2001-01-05",
+                "excluded": false,
+                "filename": "${MAIL_DIR}/copy1",
+                "headers": {
+                    "Date": "Fri, 05 Jan 2001 15:43:52 +0000",
+                    "From": "Notmuch Test Suite <test_suite@notmuchmail.org>",
+                    "Subject": "Show message: multiple filenames",
+                    "To": "Notmuch Test Suite <test_suite@notmuchmail.org>"
+                },
+                "id": "message-id@example.com",
+                "match": true,
+                "tags": [
+                    "inbox",
+                    "unread"
+                ],
+                "timestamp": 978709432
+            },
+            []
+        ]
+    ]
+]
+EOF
+output=$(notmuch show --format=json --body=false --format-version=2 id:message-id@example.com)
+test_expect_equal_json "$output" "$(cat EXPECTED)"
+
 test_done
index 800ebc6310e7f6cd7e9b5dac50303461e673008e..40e5e21d62cc2c87b2b70e0a67933b932505f40c 100755 (executable)
@@ -5,16 +5,16 @@ test_description="--format=sexp output"
 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\""
 output=$(notmuch show --format=sexp "sexp-show-message")
-test_expect_equal "$output" "((((:id \"${gen_msg_id}\" :match t :excluded nil :filename \"${gen_msg_filename}\" :timestamp 946728000 :date_relative \"2000-01-01\" :tags (\"inbox\" \"unread\") :headers (:Subject \"sexp-show-subject\" :From \"Notmuch Test Suite <test_suite@notmuchmail.org>\" :To \"Notmuch Test Suite <test_suite@notmuchmail.org>\" :Bcc \"test_suite+bcc@notmuchmail.org\" :Reply-To \"test_suite+replyto@notmuchmail.org\" :Date \"Sat, 01 Jan 2000 12:00:00 +0000\") :body ((:id 1 :content-type \"text/plain\" :content \"sexp-show-message\n\"))) ())))"
+test_expect_equal "$output" "((((:id \"${gen_msg_id}\" :match t :excluded nil :filename (\"${gen_msg_filename}\") :timestamp 946728000 :date_relative \"2000-01-01\" :tags (\"inbox\" \"unread\") :headers (:Subject \"sexp-show-subject\" :From \"Notmuch Test Suite <test_suite@notmuchmail.org>\" :To \"Notmuch Test Suite <test_suite@notmuchmail.org>\" :Bcc \"test_suite+bcc@notmuchmail.org\" :Reply-To \"test_suite+replyto@notmuchmail.org\" :Date \"Sat, 01 Jan 2000 12:00:00 +0000\") :body ((:id 1 :content-type \"text/plain\" :content \"sexp-show-message\n\"))) ())))"
 
 # This should be the same output as above.
 test_begin_subtest "Show message: sexp --body=true"
 output=$(notmuch show --format=sexp --body=true "sexp-show-message")
-test_expect_equal "$output" "((((:id \"${gen_msg_id}\" :match t :excluded nil :filename \"${gen_msg_filename}\" :timestamp 946728000 :date_relative \"2000-01-01\" :tags (\"inbox\" \"unread\") :headers (:Subject \"sexp-show-subject\" :From \"Notmuch Test Suite <test_suite@notmuchmail.org>\" :To \"Notmuch Test Suite <test_suite@notmuchmail.org>\" :Bcc \"test_suite+bcc@notmuchmail.org\" :Reply-To \"test_suite+replyto@notmuchmail.org\" :Date \"Sat, 01 Jan 2000 12:00:00 +0000\") :body ((:id 1 :content-type \"text/plain\" :content \"sexp-show-message\n\"))) ())))"
+test_expect_equal "$output" "((((:id \"${gen_msg_id}\" :match t :excluded nil :filename (\"${gen_msg_filename}\") :timestamp 946728000 :date_relative \"2000-01-01\" :tags (\"inbox\" \"unread\") :headers (:Subject \"sexp-show-subject\" :From \"Notmuch Test Suite <test_suite@notmuchmail.org>\" :To \"Notmuch Test Suite <test_suite@notmuchmail.org>\" :Bcc \"test_suite+bcc@notmuchmail.org\" :Reply-To \"test_suite+replyto@notmuchmail.org\" :Date \"Sat, 01 Jan 2000 12:00:00 +0000\") :body ((:id 1 :content-type \"text/plain\" :content \"sexp-show-message\n\"))) ())))"
 
 test_begin_subtest "Show message: sexp --body=false"
 output=$(notmuch show --format=sexp --body=false "sexp-show-message")
-test_expect_equal "$output" "((((:id \"${gen_msg_id}\" :match t :excluded nil :filename \"${gen_msg_filename}\" :timestamp 946728000 :date_relative \"2000-01-01\" :tags (\"inbox\" \"unread\") :headers (:Subject \"sexp-show-subject\" :From \"Notmuch Test Suite <test_suite@notmuchmail.org>\" :To \"Notmuch Test Suite <test_suite@notmuchmail.org>\" :Bcc \"test_suite+bcc@notmuchmail.org\" :Reply-To \"test_suite+replyto@notmuchmail.org\" :Date \"Sat, 01 Jan 2000 12:00:00 +0000\")) ())))"
+test_expect_equal "$output" "((((:id \"${gen_msg_id}\" :match t :excluded nil :filename (\"${gen_msg_filename}\") :timestamp 946728000 :date_relative \"2000-01-01\" :tags (\"inbox\" \"unread\") :headers (:Subject \"sexp-show-subject\" :From \"Notmuch Test Suite <test_suite@notmuchmail.org>\" :To \"Notmuch Test Suite <test_suite@notmuchmail.org>\" :Bcc \"test_suite+bcc@notmuchmail.org\" :Reply-To \"test_suite+replyto@notmuchmail.org\" :Date \"Sat, 01 Jan 2000 12:00:00 +0000\")) ())))"
 
 test_begin_subtest "Search message: sexp"
 add_message "[subject]=\"sexp-search-subject\"" "[date]=\"Sat, 01 Jan 2000 12:00:00 -0000\"" "[body]=\"sexp-search-message\""
@@ -24,7 +24,7 @@ test_expect_equal "$output" "((:thread \"0000000000000002\" :timestamp 946728000
 test_begin_subtest "Show message: sexp, utf-8"
 add_message "[subject]=\"sexp-show-utf8-body-sübjéct\"" "[date]=\"Sat, 01 Jan 2000 12:00:00 -0000\"" "[body]=\"jsön-show-méssage\""
 output=$(notmuch show --format=sexp "jsön-show-méssage")
-test_expect_equal "$output" "((((:id \"${gen_msg_id}\" :match t :excluded nil :filename \"${gen_msg_filename}\" :timestamp 946728000 :date_relative \"2000-01-01\" :tags (\"inbox\" \"unread\") :headers (:Subject \"sexp-show-utf8-body-sübjéct\" :From \"Notmuch Test Suite <test_suite@notmuchmail.org>\" :To \"Notmuch Test Suite <test_suite@notmuchmail.org>\" :Date \"Sat, 01 Jan 2000 12:00:00 +0000\") :body ((:id 1 :content-type \"text/plain\" :content \"jsön-show-méssage\n\"))) ())))"
+test_expect_equal "$output" "((((:id \"${gen_msg_id}\" :match t :excluded nil :filename (\"${gen_msg_filename}\") :timestamp 946728000 :date_relative \"2000-01-01\" :tags (\"inbox\" \"unread\") :headers (:Subject \"sexp-show-utf8-body-sübjéct\" :From \"Notmuch Test Suite <test_suite@notmuchmail.org>\" :To \"Notmuch Test Suite <test_suite@notmuchmail.org>\" :Date \"Sat, 01 Jan 2000 12:00:00 +0000\") :body ((:id 1 :content-type \"text/plain\" :content \"jsön-show-méssage\n\"))) ())))"
 
 test_begin_subtest "Show message: sexp, inline attachment filename"
 subject='sexp-show-inline-attachment-filename'
@@ -39,7 +39,7 @@ output=$(notmuch show --format=sexp "id:$id")
 filename=$(notmuch search --output=files "id:$id")
 # Get length of README after base64-encoding, minus additional newline.
 attachment_length=$(( $(base64 $TEST_DIRECTORY/README | wc -c) - 1 ))
-test_expect_equal "$output" "((((:id \"$id\" :match t :excluded nil :filename \"$filename\" :timestamp 946728000 :date_relative \"2000-01-01\" :tags (\"inbox\") :headers (:Subject \"sexp-show-inline-attachment-filename\" :From \"Notmuch Test Suite <test_suite@notmuchmail.org>\" :To \"test_suite@notmuchmail.org\" :Date \"Sat, 01 Jan 2000 12:00:00 +0000\") :body ((:id 1 :content-type \"multipart/mixed\" :content ((:id 2 :content-type \"text/plain\" :content \"This is a test message with inline attachment with a filename\") (:id 3 :content-type \"application/octet-stream\" :filename \"README\" :content-transfer-encoding \"base64\" :content-length $attachment_length))))) ())))"
+test_expect_equal "$output" "((((:id \"$id\" :match t :excluded nil :filename (\"$filename\") :timestamp 946728000 :date_relative \"2000-01-01\" :tags (\"inbox\") :headers (:Subject \"sexp-show-inline-attachment-filename\" :From \"Notmuch Test Suite <test_suite@notmuchmail.org>\" :To \"test_suite@notmuchmail.org\" :Date \"Sat, 01 Jan 2000 12:00:00 +0000\") :body ((:id 1 :content-type \"multipart/mixed\" :content ((:id 2 :content-type \"text/plain\" :content \"This is a test message with inline attachment with a filename\") (:id 3 :content-type \"application/octet-stream\" :content-disposition \"inline\" :filename \"README\" :content-transfer-encoding \"base64\" :content-length $attachment_length))))) ())))"
 
 test_begin_subtest "Search message: sexp, utf-8"
 add_message "[subject]=\"sexp-search-utf8-body-sübjéct\"" "[date]=\"Sat, 01 Jan 2000 12:00:00 -0000\"" "[body]=\"jsön-search-méssage\""
index 35678909b0ce110ca650c108a9478aae4139e5dd..a31d61e2aa20bd77850783a7e27e7c477e9494d1 100755 (executable)
@@ -345,14 +345,14 @@ test_expect_success \
 test_begin_subtest "--format=json --part=0, full message"
 notmuch show --format=json --part=0 'id:87liy5ap00.fsf@yoom.home.cworth.org' >OUTPUT
 cat <<EOF >EXPECTED
-{"id": "87liy5ap00.fsf@yoom.home.cworth.org", "match": true, "excluded": false, "filename": "${MAIL_DIR}/multipart", "timestamp": 978709437, "date_relative": "2001-01-05", "tags": ["attachment","inbox","signed","unread"], "headers": {"Subject": "Multipart message", "From": "Carl Worth <cworth@cworth.org>", "To": "cworth@cworth.org", "Date": "Fri, 05 Jan 2001 15:43:57 +0000"}, "body": [
+{"id": "87liy5ap00.fsf@yoom.home.cworth.org", "match": true, "excluded": false, "filename": ["${MAIL_DIR}/multipart"], "timestamp": 978709437, "date_relative": "2001-01-05", "tags": ["attachment","inbox","signed","unread"], "headers": {"Subject": "Multipart message", "From": "Carl Worth <cworth@cworth.org>", "To": "cworth@cworth.org", "Date": "Fri, 05 Jan 2001 15:43:57 +0000"}, "body": [
 {"id": 1, "content-type": "multipart/signed", "content": [
 {"id": 2, "content-type": "multipart/mixed", "content": [
-{"id": 3, "content-type": "message/rfc822", "content": [{"headers": {"Subject": "html message", "From": "Carl Worth <cworth@cworth.org>", "To": "cworth@cworth.org", "Date": "Fri, 05 Jan 2001 15:42:57 +0000"}, "body": [
+{"id": 3, "content-type": "message/rfc822", "content-disposition": "inline", "content": [{"headers": {"Subject": "html message", "From": "Carl Worth <cworth@cworth.org>", "To": "cworth@cworth.org", "Date": "Fri, 05 Jan 2001 15:42:57 +0000"}, "body": [
 {"id": 4, "content-type": "multipart/alternative", "content": [
 {"id": 5, "content-type": "text/html", "content-length": 71},
 {"id": 6, "content-type": "text/plain", "content": "This is an embedded message, with a multipart/alternative part.\n"}]}]}]}, 
-{"id": 7, "content-type": "text/plain", "filename": "attachment", "content": "This is a text attachment.\n"}, 
+{"id": 7, "content-type": "text/plain", "content-disposition": "attachment", "filename": "attachment", "content": "This is a text attachment.\n"},
 {"id": 8, "content-type": "text/plain", "content": "And this message is signed.\n\n-Carl\n"}]}, 
 {"id": 9, "content-type": "application/pgp-signature", "content-length": 197}]}]}
 EOF
@@ -363,11 +363,11 @@ notmuch show --format=json --part=1 'id:87liy5ap00.fsf@yoom.home.cworth.org' >OU
 cat <<EOF >EXPECTED
 {"id": 1, "content-type": "multipart/signed", "content": [
 {"id": 2, "content-type": "multipart/mixed", "content": [
-{"id": 3, "content-type": "message/rfc822", "content": [{"headers": {"Subject": "html message", "From": "Carl Worth <cworth@cworth.org>", "To": "cworth@cworth.org", "Date": "Fri, 05 Jan 2001 15:42:57 +0000"}, "body": [
+{"id": 3, "content-type": "message/rfc822", "content-disposition": "inline", "content": [{"headers": {"Subject": "html message", "From": "Carl Worth <cworth@cworth.org>", "To": "cworth@cworth.org", "Date": "Fri, 05 Jan 2001 15:42:57 +0000"}, "body": [
 {"id": 4, "content-type": "multipart/alternative", "content": [
 {"id": 5, "content-type": "text/html", "content-length": 71},
 {"id": 6, "content-type": "text/plain", "content": "This is an embedded message, with a multipart/alternative part.\n"}]}]}]}, 
-{"id": 7, "content-type": "text/plain", "filename": "attachment", "content": "This is a text attachment.\n"}, 
+{"id": 7, "content-type": "text/plain", "content-disposition": "attachment", "filename": "attachment", "content": "This is a text attachment.\n"},
 {"id": 8, "content-type": "text/plain", "content": "And this message is signed.\n\n-Carl\n"}]}, 
 {"id": 9, "content-type": "application/pgp-signature", "content-length": 197}]}
 EOF
@@ -377,11 +377,11 @@ test_begin_subtest "--format=json --part=2, multipart/mixed"
 notmuch show --format=json --part=2 'id:87liy5ap00.fsf@yoom.home.cworth.org' >OUTPUT
 cat <<EOF >EXPECTED
 {"id": 2, "content-type": "multipart/mixed", "content": [
-{"id": 3, "content-type": "message/rfc822", "content": [{"headers": {"Subject": "html message", "From": "Carl Worth <cworth@cworth.org>", "To": "cworth@cworth.org", "Date": "Fri, 05 Jan 2001 15:42:57 +0000"}, "body": [
+{"id": 3, "content-type": "message/rfc822", "content-disposition": "inline", "content": [{"headers": {"Subject": "html message", "From": "Carl Worth <cworth@cworth.org>", "To": "cworth@cworth.org", "Date": "Fri, 05 Jan 2001 15:42:57 +0000"}, "body": [
 {"id": 4, "content-type": "multipart/alternative", "content": [
 {"id": 5, "content-type": "text/html", "content-length": 71},
 {"id": 6, "content-type": "text/plain", "content": "This is an embedded message, with a multipart/alternative part.\n"}]}]}]}, 
-{"id": 7, "content-type": "text/plain", "filename": "attachment", "content": "This is a text attachment.\n"}, 
+{"id": 7, "content-type": "text/plain", "content-disposition": "attachment", "filename": "attachment", "content": "This is a text attachment.\n"},
 {"id": 8, "content-type": "text/plain", "content": "And this message is signed.\n\n-Carl\n"}]}
 EOF
 test_expect_equal_json "$(cat OUTPUT)" "$(cat EXPECTED)"
@@ -389,7 +389,7 @@ test_expect_equal_json "$(cat OUTPUT)" "$(cat EXPECTED)"
 test_begin_subtest "--format=json --part=3, rfc822 part"
 notmuch show --format=json --part=3 'id:87liy5ap00.fsf@yoom.home.cworth.org' >OUTPUT
 cat <<EOF >EXPECTED
-{"id": 3, "content-type": "message/rfc822", "content": [{"headers": {"Subject": "html message", "From": "Carl Worth <cworth@cworth.org>", "To": "cworth@cworth.org", "Date": "Fri, 05 Jan 2001 15:42:57 +0000"}, "body": [
+{"id": 3, "content-type": "message/rfc822", "content-disposition": "inline", "content": [{"headers": {"Subject": "html message", "From": "Carl Worth <cworth@cworth.org>", "To": "cworth@cworth.org", "Date": "Fri, 05 Jan 2001 15:42:57 +0000"}, "body": [
 {"id": 4, "content-type": "multipart/alternative", "content": [
 {"id": 5, "content-type": "text/html", "content-length": 71},
 {"id": 6, "content-type": "text/plain", "content": "This is an embedded message, with a multipart/alternative part.\n"}]}]}]}
@@ -422,7 +422,11 @@ test_expect_equal_json "$(cat OUTPUT)" "$(cat EXPECTED)"
 test_begin_subtest "--format=json --part=7, inline attachment"
 notmuch show --format=json --part=7 'id:87liy5ap00.fsf@yoom.home.cworth.org' >OUTPUT
 cat <<EOF >EXPECTED
-{"id": 7, "content-type": "text/plain", "filename": "attachment", "content": "This is a text attachment.\n"}
+{"id": 7,
+ "content-type": "text/plain",
+ "filename": "attachment",
+ "content": "This is a text attachment.\n",
+ "content-disposition": "attachment"}
 EOF
 test_expect_equal_json "$(cat OUTPUT)" "$(cat EXPECTED)"
 
@@ -627,7 +631,7 @@ notmuch_json_show_sanitize <<EOF >EXPECTED
  "original": {"id": "XXXXX",
  "match": false,
  "excluded": false,
- "filename": "YYYYY",
+ "filename": ["YYYYY"],
  "timestamp": 978709437,
  "date_relative": "2001-01-05",
  "tags": ["attachment","inbox","signed","unread"],
@@ -641,6 +645,7 @@ notmuch_json_show_sanitize <<EOF >EXPECTED
  "content-type": "multipart/mixed",
  "content": [{"id": 3,
  "content-type": "message/rfc822",
+ "content-disposition": "inline",
  "content": [{"headers": {"Subject": "html message",
  "From": "Carl Worth <cworth@cworth.org>",
  "To": "cworth@cworth.org",
@@ -655,6 +660,7 @@ notmuch_json_show_sanitize <<EOF >EXPECTED
  "content": "This is an embedded message, with a multipart/alternative part.\n"}]}]}]},
  {"id": 7,
  "content-type": "text/plain",
+ "content-disposition": "attachment",
  "filename": "attachment",
  "content": "This is a text attachment.\n"},
  {"id": 8,
@@ -715,7 +721,7 @@ cat_expected_head ()
         cat <<EOF
 [[[{"id": "htmlmessage", "match":true, "excluded": false, "date_relative":"2000-01-01",
    "timestamp": 946684800,
-   "filename": "${MAIL_DIR}/include-html",
+   "filename": ["${MAIL_DIR}/include-html"],
    "tags": ["inbox", "unread"],
    "headers": { "Date": "Sat, 01 Jan 2000 00:00:00 +0000", "From": "A <a@example.com>",
                 "Subject": "html message", "To": "B <b@example.com>"},
index 818a86541496c21adc6ceaf382df2af0e6f5f141..17741e0d7a85db77ca35e686f612d5e0ee6e99b4 100755 (executable)
@@ -229,7 +229,7 @@ test_expect_equal_json "$output" '
         ],
         "date_relative": "2010-01-05",
         "excluded": false,
-        "filename": "'${MAIL_DIR}'/msg-012",
+        "filename": ["'${MAIL_DIR}'/msg-012"],
         "headers": {
             "Date": "Tue, 05 Jan 2010 15:43:56 +0000",
             "From": "\u2603 <snowman@example.com>",
index f720c99817a1561ef8df2c1300644d8a2ee600db..89f4d1be4816a8926a0663eeea46db54750e77f3 100755 (executable)
@@ -4,7 +4,7 @@ test_description="threading when messages received out of order"
 
 # Generate all single-root four message thread structures.  We'll use
 # this for multiple tests below.
-THREADS=$(python ${TEST_DIRECTORY}/gen-threads.py 4)
+THREADS=$($NOTMUCH_PYTHON ${TEST_DIRECTORY}/gen-threads.py 4)
 nthreads=$(wc -l <<< "$THREADS")
 
 test_begin_subtest "Messages with one parent get linked in all delivery orders"
index efeaa3f60d6e28fa4489a05dad2837ff844dd4fa..b474bf46e4be4ac6c51f4913d35b4c977bd4726e 100755 (executable)
@@ -39,7 +39,7 @@ output=$(notmuch show --format=json id:${gen_msg_id} | notmuch_json_show_sanitiz
 test_expect_equal_json "$output" '[[[{"id": "XXXXX",
 "match": true,
 "excluded": false,
-"filename": "YYYYY",
+"filename": ["YYYYY"],
 "timestamp": 42,
 "date_relative": "2001-01-05",
 "tags": ["inbox","replied"],
index a1e5e206081ff03a5c0d15e0bc648d0835b65420..b15f4fbe77608b3d5d5189fd28307584b0a9aea4 100755 (executable)
@@ -41,7 +41,7 @@ output=$(notmuch show --format=json --verify subject:"test signed message 001" \
 expected='[[[{"id": "XXXXX",
  "match": true,
  "excluded": false,
- "filename": "YYYYY",
+ "filename": ["YYYYY"],
  "timestamp": 946728000,
  "date_relative": "2000-01-01",
  "tags": ["inbox","signed"],
@@ -75,7 +75,7 @@ output=$(notmuch show --format=json --verify subject:"test signed message 001" \
 expected='[[[{"id": "XXXXX",
  "match": true,
  "excluded": false,
- "filename": "YYYYY",
+ "filename": ["YYYYY"],
  "timestamp": 946728000,
  "date_relative": "2000-01-01",
  "tags": ["inbox","signed"],
@@ -109,7 +109,7 @@ output=$(notmuch show --format=json --verify subject:"test signed message 001" \
 expected='[[[{"id": "XXXXX",
  "match": true,
  "excluded": false,
- "filename": "YYYYY",
+ "filename": ["YYYYY"],
  "timestamp": 946728000,
  "date_relative": "2000-01-01",
  "tags": ["inbox","signed"],
@@ -183,7 +183,7 @@ output=$(notmuch show --format=json --decrypt subject:"test encrypted message 00
 expected='[[[{"id": "XXXXX",
  "match": true,
  "excluded": false,
- "filename": "YYYYY",
+ "filename": ["YYYYY"],
  "timestamp": 946728000,
  "date_relative": "2000-01-01",
  "tags": ["encrypted","inbox"],
@@ -205,6 +205,7 @@ expected='[[[{"id": "XXXXX",
  "content": "This is a test encrypted message.\n"},
  {"id": 5,
  "content-type": "application/octet-stream",
+ "content-disposition": "attachment",
  "content-length": "NONZERO",
  "content-transfer-encoding": "base64",
  "filename": "TESTATTACHMENT"}]}]}]},
@@ -240,7 +241,7 @@ output=$(notmuch show --format=json --decrypt subject:"test encrypted message 00
 expected='[[[{"id": "XXXXX",
  "match": true,
  "excluded": false,
- "filename": "YYYYY",
+ "filename": ["YYYYY"],
  "timestamp": 946728000,
  "date_relative": "2000-01-01",
  "tags": ["encrypted","inbox"],
@@ -276,7 +277,7 @@ output=$(notmuch show --format=json --decrypt subject:"test encrypted message 00
 expected='[[[{"id": "XXXXX",
  "match": true,
  "excluded": false,
- "filename": "YYYYY",
+ "filename": ["YYYYY"],
  "timestamp": 946728000,
  "date_relative": "2000-01-01",
  "tags": ["encrypted","inbox"],
@@ -350,7 +351,7 @@ output=$(notmuch show --format=json --verify subject:"test signed message 001" \
 expected='[[[{"id": "XXXXX",
  "match": true,
  "excluded": false,
- "filename": "YYYYY",
+ "filename": ["YYYYY"],
  "timestamp": 946728000,
  "date_relative": "2000-01-01",
  "tags": ["inbox","signed"],
index a8be45e7098e39531bc31112e840d2286434b1eb..1efefcbd7a239ebbcf1461fe07e2d08c0bf47eab 100755 (executable)
@@ -51,7 +51,7 @@ output=$(notmuch show --format=json --verify subject:"test signed message 001" \
 expected='[[[{"id": "XXXXX",
  "match": true,
  "excluded": false,
- "filename": "YYYYY",
+ "filename": ["YYYYY"],
  "timestamp": 946728000,
  "date_relative": "2000-01-01",
  "tags": ["inbox","signed"],
@@ -69,6 +69,7 @@ expected='[[[{"id": "XXXXX",
  "content-type": "text/plain",
  "content": "This is a test signed message.\n"},
  {"id": 3,
+  "content-disposition": "attachment",
   "content-length": "NONZERO",
   "content-transfer-encoding": "base64",
   "content-type": "application/x-pkcs7-signature",
index 845dfde75fd6e8bae20283da3285d3046b498761..c6a9fb980bf0d4678a444939f5bb97ddd957747a 100755 (executable)
@@ -64,7 +64,7 @@ if test_require_external_prereq gdb; then
     # -tty /dev/null works around a conflict between the 'timeout' wrapper
     # and gdb's attempt to control the TTY.
     export MAIL_DIR
-    gdb -tty /dev/null -batch -x $TEST_DIRECTORY/atomicity.py notmuch 1>gdb.out 2>&1
+    ${TEST_GDB} -tty /dev/null -batch -x $TEST_DIRECTORY/atomicity.py notmuch 1>gdb.out 2>&1
 
     # Get the final, golden output
     notmuch search '*' > expected
index 256a885f806a060fbf605a49ebbea6b7b669a7a3..32031e3174d3005413ac0d652b0654f30cfed21f 100755 (executable)
@@ -109,7 +109,7 @@ expected=$(notmuch_json_show_sanitize <<EOF
                 ],
                 "date_relative": "2001-01-05",
                 "excluded": false,
-                "filename": "YYYYY",
+                "filename": ["YYYYY"],
                 "headers": {
                     "Date": "Fri, 05 Jan 2001 15:43:57 +0000",
                     "From": "",
@@ -139,7 +139,7 @@ expected=$(notmuch_json_show_sanitize <<EOF
                 ],
                 "date_relative": "1970-01-01",
                 "excluded": false,
-                "filename": "YYYYY",
+                "filename": ["YYYYY"],
                 "headers": {
                     "Date": "Thu, 01 Jan 1970 00:00:00 +0000",
                     "From": "Notmuch Test Suite <test_suite@notmuchmail.org>",
index 5ab066ac7baab83c3822172993f34fb48161ecf4..fa288bb199038ba445206905713b7b32d647ca60 100755 (executable)
@@ -21,7 +21,7 @@ output=$(notmuch show --format=json 'subject:one' | notmuch_json_show_sanitize)
 expected='[[[{"id": "foo@one.com",
  "match": true,
  "excluded": false,
- "filename": "YYYYY",
+ "filename": ["YYYYY"],
  "timestamp": 978709437,
  "date_relative": "2001-01-05",
  "tags": ["inbox", "unread"],
@@ -34,7 +34,7 @@ expected='[[[{"id": "foo@one.com",
  "content": "This is just a test message (#1)\n"}]},
  [[{"id": "msg-002@notmuch-test-suite",
  "match": true, "excluded": false,
- "filename": "YYYYY",
+ "filename": ["YYYYY"],
  "timestamp": 978709437, "date_relative": "2001-01-05",
  "tags": ["inbox", "unread"], "headers": {"Subject": "Re: one",
  "From": "Notmuch Test Suite <test_suite@notmuchmail.org>",
@@ -54,7 +54,7 @@ add_message '[in-reply-to]="<bar@baz.com>"' \
 output=$(notmuch show --format=json 'subject:two' | notmuch_json_show_sanitize)
 expected='[[[{"id": "foo@two.com",
  "match": true, "excluded": false,
- "filename": "YYYYY",
+ "filename": ["YYYYY"],
  "timestamp": 978709437, "date_relative": "2001-01-05", "tags": ["inbox", "unread"],
  "headers": {"Subject": "two",
  "From": "Notmuch Test Suite <test_suite@notmuchmail.org>",
@@ -63,7 +63,7 @@ expected='[[[{"id": "foo@two.com",
  "body": [{"id": 1, "content-type": "text/plain",
  "content": "This is just a test message (#3)\n"}]},
  [[{"id": "msg-004@notmuch-test-suite", "match": true, "excluded": false,
- "filename": "YYYYY",
+ "filename": ["YYYYY"],
  "timestamp": 978709437, "date_relative": "2001-01-05", "tags": ["inbox", "unread"],
  "headers": {"Subject": "Re: two",
  "From": "Notmuch Test Suite <test_suite@notmuchmail.org>",
@@ -82,7 +82,7 @@ add_message '[in-reply-to]="<foo@three.com>"' \
     '[subject]="Re: three"'
 output=$(notmuch show --format=json 'subject:three' | notmuch_json_show_sanitize)
 expected='[[[{"id": "foo@three.com", "match": true, "excluded": false,
- "filename": "YYYYY",
+ "filename": ["YYYYY"],
  "timestamp": 978709437, "date_relative": "2001-01-05", "tags": ["inbox", "unread"],
  "headers": {"Subject": "three",
  "From": "Notmuch Test Suite <test_suite@notmuchmail.org>",
@@ -90,7 +90,7 @@ expected='[[[{"id": "foo@three.com", "match": true, "excluded": false,
  "Date": "Fri, 05 Jan 2001 15:43:57 +0000"}, "body": [{"id": 1,
  "content-type": "text/plain", "content": "This is just a test message (#5)\n"}]},
  [[{"id": "msg-006@notmuch-test-suite", "match": true, "excluded": false,
- "filename": "YYYYY",
+ "filename": ["YYYYY"],
  "timestamp": 978709437, "date_relative": "2001-01-05", "tags": ["inbox", "unread"],
  "headers": {"Subject": "Re: three",
  "From": "Notmuch Test Suite <test_suite@notmuchmail.org>",
@@ -111,7 +111,7 @@ add_message '[in-reply-to]="<baz@four.com>"' \
     '[subject]="neither"'
 output=$(notmuch show --format=json 'subject:four' | notmuch_json_show_sanitize)
 expected='[[[{"id": "foo@four.com", "match": true, "excluded": false,
- "filename": "YYYYY",
+ "filename": ["YYYYY"],
  "timestamp": 978709437, "date_relative": "2001-01-05", "tags": ["inbox", "unread"],
  "headers": {"Subject": "four",
  "From": "Notmuch Test Suite <test_suite@notmuchmail.org>",
@@ -119,7 +119,7 @@ expected='[[[{"id": "foo@four.com", "match": true, "excluded": false,
  "Date": "Fri, 05 Jan 2001 15:43:57 +0000"}, "body": [{"id": 1,
  "content-type": "text/plain", "content": "This is just a test message (#7)\n"}]},
  [[{"id": "msg-009@notmuch-test-suite", "match": false, "excluded": false,
- "filename": "YYYYY",
+ "filename": ["YYYYY"],
  "timestamp": 978709437, "date_relative": "2001-01-05", "tags": ["inbox", "unread"],
  "headers": {"Subject": "neither",
  "From": "Notmuch Test Suite <test_suite@notmuchmail.org>",
@@ -127,7 +127,7 @@ expected='[[[{"id": "foo@four.com", "match": true, "excluded": false,
  "Date": "Fri, 05 Jan 2001 15:43:57 +0000"}, "body": [{"id": 1,
  "content-type": "text/plain", "content": "This is just a test message (#9)\n"}]},
  []]]]], [[{"id": "bar@four.com", "match": true, "excluded": false,
- "filename": "YYYYY",
+ "filename": ["YYYYY"],
  "timestamp": 978709437, "date_relative": "2001-01-05", "tags": ["inbox", "unread"],
  "headers": {"Subject": "not-four",
  "From": "Notmuch Test Suite <test_suite@notmuchmail.org>",
@@ -145,7 +145,7 @@ add_message '[id]="bar@five.com"' \
     '[subject]="not-five"'
 output=$(notmuch show --format=json 'subject:five' | notmuch_json_show_sanitize)
 expected='[[[{"id": "XXXXX", "match": true, "excluded": false,
- "filename": "YYYYY", "timestamp": 42, "date_relative": "2001-01-05",
+ "filename": ["YYYYY"], "timestamp": 42, "date_relative": "2001-01-05",
  "tags": ["inbox", "unread"], "headers": {"Subject": "five",
  "From": "Notmuch Test Suite <test_suite@notmuchmail.org>",
  "To": "Notmuch Test Suite <test_suite@notmuchmail.org>",
@@ -153,7 +153,7 @@ expected='[[[{"id": "XXXXX", "match": true, "excluded": false,
  "content-type": "text/plain",
  "content": "This is just a test message (#10)\n"}]},
  [[{"id": "XXXXX", "match": true, "excluded": false,
- "filename": "YYYYY", "timestamp": 42, "date_relative": "2001-01-05",
+ "filename": ["YYYYY"], "timestamp": 42, "date_relative": "2001-01-05",
  "tags": ["inbox", "unread"],
  "headers": {"Subject": "not-five",
  "From": "Notmuch Test Suite <test_suite@notmuchmail.org>",
index 6f7106db475a60cb472f073cd7b391ef56b44434..512559a3aff90086809247e2cb3e8ef0a6451e32 100755 (executable)
@@ -15,7 +15,7 @@ count=0
 success=0
 for id in $(notmuch search --output=messages '*'); do
     count=$((count +1))
-    matches=$(notmuch search --output=threads "$id" | wc -l)
+    matches=$((`notmuch search --output=threads "$id" | wc -l`))
     if [ "$matches" = 1 ]; then
        success=$((success + 1))
     fi
diff --git a/test/T630-emacs-draft.sh b/test/T630-emacs-draft.sh
new file mode 100755 (executable)
index 0000000..cd9e33a
--- /dev/null
@@ -0,0 +1,72 @@
+#!/usr/bin/env bash
+test_description="Emacs Draft Handling"
+. ./test-lib.sh || exit 1
+
+add_email_corpus
+
+notmuch config set search.exclude_tags deleted
+
+test_begin_subtest "Saving a draft indexes it"
+test_emacs '(notmuch-mua-mail)
+           (message-goto-subject)
+           (insert "draft-test-0001")
+           (notmuch-draft-save)
+           (test-output)'
+count1=$(notmuch count tag:draft)
+count2=$(notmuch count subject:draft-test-0001)
+test_expect_equal "$count1=$count2" "1=1"
+
+test_begin_subtest "Saving a draft tags previous draft as deleted"
+test_emacs '(notmuch-mua-mail)
+           (message-goto-subject)
+           (insert "draft-test-0002")
+           (notmuch-draft-save)
+           (notmuch-draft-save)
+           (test-output)'
+count1=$(notmuch count tag:draft)
+count2=$(notmuch count subject:draft-test-0002)
+
+test_expect_equal "$count1,$count2" "2,1"
+
+test_begin_subtest "Saving a signed draft adds header"
+test_emacs '(notmuch-mua-mail)
+           (message-goto-subject)
+           (insert "draft-test-0003")
+            ;; We would use (mml-secure-message-sign) but on emacs23
+            ;; that only signs the part, not the whole message.
+            (mml-secure-message mml-secure-method '\''sign)
+           (notmuch-draft-save)
+           (test-output)'
+header_count=$(notmuch show --format=raw subject:draft-test-0003 | grep -c ^X-Notmuch-Emacs-Secure)
+body_count=$(notmuch notmuch show --format=raw subject:draft-test-0003 | grep -c '^\<#secure')
+test_expect_equal "$header_count,$body_count" "1,0"
+
+test_begin_subtest "Refusing to save an encrypted draft"
+test_emacs '(notmuch-mua-mail)
+           (message-goto-subject)
+           (insert "draft-test-0004")
+           (mml-secure-message-sign-encrypt)
+           (let ((notmuch-draft-save-plaintext nil))
+                    (notmuch-draft-save))
+           (test-output)'
+count1=$(notmuch count tag:draft)
+count2=$(notmuch count subject:draft-test-0004)
+
+test_expect_equal "$count1,$count2" "3,0"
+
+test_begin_subtest "Resuming a signed draft"
+
+test_emacs '(notmuch-show "subject:draft-test-0003")
+           (notmuch-show-resume-message)
+           (test-output)'
+notmuch_dir_sanitize OUTPUT > OUTPUT.clean
+cat <<EOF | notmuch_dir_sanitize >EXPECTED
+From: Notmuch Test Suite <test_suite@notmuchmail.org>
+To: 
+Subject: draft-test-0003
+Fcc: MAIL_DIR/sent
+--text follows this line--
+<#secure method=pgpmime mode=sign>
+EOF
+test_expect_equal_file EXPECTED OUTPUT.clean
+test_done
diff --git a/test/T640-database-modified.sh b/test/T640-database-modified.sh
new file mode 100755 (executable)
index 0000000..92758e1
--- /dev/null
@@ -0,0 +1,67 @@
+#!/usr/bin/env bash
+test_description="DatabaseModifiedError handling"
+. ./test-lib.sh || exit 1
+
+# add enough messages to trigger the exception
+add_email_corpus
+
+test_begin_subtest "catching DatabaseModifiedError in _notmuch_message_ensure_metadata"
+# it seems to need to be an early document to trigger the exception
+first_id=$(notmuch search --output=messages '*'| head -1 | sed s/^id://)
+
+test_C ${MAIL_DIR} <<EOF
+#include <unistd.h>
+#include <stdlib.h>
+#include <notmuch-test.h>
+#include <talloc.h>
+#include <assert.h>
+int
+main (int argc, char **argv)
+{
+    const char *path = argv[1];
+
+    notmuch_database_t *rw_db, *ro_db;
+    notmuch_messages_t *messages;
+    notmuch_message_t *message, *ro_message;
+    notmuch_query_t *query;
+    notmuch_tags_t *tags;
+    int i;
+
+    EXPECT0 (notmuch_database_open (path, NOTMUCH_DATABASE_MODE_READ_ONLY, &ro_db));
+    assert(ro_db);
+
+    EXPECT0 (notmuch_database_find_message (ro_db, "${first_id}", &ro_message));
+    assert(ro_message);
+
+    EXPECT0 (notmuch_database_open (path, NOTMUCH_DATABASE_MODE_READ_WRITE, &rw_db));
+    query = notmuch_query_create(rw_db, "");
+    EXPECT0 (notmuch_query_search_messages_st (query, &messages));
+
+    for (;
+        notmuch_messages_valid (messages);
+        notmuch_messages_move_to_next (messages)) {
+       message = notmuch_messages_get (messages);
+       for (i=0; i<200; i++) {
+           char *tag_str = talloc_asprintf(rw_db, "%d", i);
+           EXPECT0 (notmuch_message_add_tag (message, tag_str));
+           talloc_free (tag_str);
+       }
+    }
+
+    notmuch_database_close (rw_db);
+
+    tags = notmuch_message_get_tags (ro_message);
+    if (tags)
+       printf("SUCCESS\n");
+    return 0;
+}
+EOF
+
+cat <<'EOF' >EXPECTED
+== stdout ==
+SUCCESS
+== stderr ==
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_done
index ba1580cd1f3e803130a373c09a766fc82dbf1e7e..d8e159437ca9b8fa159e2a776260adc36025e86a 100644 (file)
@@ -80,6 +80,7 @@ if [[ ( -n "$TEST_EMACS" && -z "$TEST_EMACSCLIENT" ) || \
 fi
 TEST_EMACS=${TEST_EMACS:-${EMACS:-emacs}}
 TEST_EMACSCLIENT=${TEST_EMACSCLIENT:-emacsclient}
+TEST_GDB=${TEST_GDB:-gdb}
 TEST_CC=${TEST_CC:-cc}
 TEST_CFLAGS=${TEST_CFLAGS:-"-g -O0"}
 
@@ -633,15 +634,15 @@ test_expect_equal_file ()
        error "bug in the test script: not 2 or 3 parameters to test_expect_equal"
 
        file1="$1"
-       basename1=`basename "$file1"`
        file2="$2"
-       basename2=`basename "$file2"`
        if ! test_skip "$test_subtest_name"
        then
                if diff -q "$file1" "$file2" >/dev/null ; then
                        test_ok_
                else
                        testname=$this_test.$test_count
+                       basename1=`basename "$file1"`
+                       basename2=`basename "$file2"`
                        cp "$file1" "$testname.$basename1"
                        cp "$file2" "$testname.$basename2"
                        test_failure_ "$(diff -u "$testname.$basename1" "$testname.$basename2")"
@@ -666,7 +667,7 @@ test_expect_equal_json () {
 
 # Sort the top-level list of JSON data from stdin.
 test_sort_json () {
-    PYTHONIOENCODING=utf-8 python -c \
+    PYTHONIOENCODING=utf-8 $NOTMUCH_PYTHON -c \
         "import sys, json; json.dump(sorted(json.load(sys.stdin)),sys.stdout)"
 }
 
@@ -744,7 +745,7 @@ notmuch_json_show_sanitize ()
        -e 's|"id": "[^"]*",|"id": "XXXXX",|g' \
        -e 's|"Date": "Fri, 05 Jan 2001 [^"]*0000"|"Date": "GENERATED_DATE"|g' \
        -e 's|"filename": "signature.asc",||g' \
-       -e 's|"filename": "/[^"]*",|"filename": "YYYYY",|g' \
+       -e 's|"filename": \["/[^"]*"\],|"filename": \["YYYYY"\],|g' \
        -e 's|"timestamp": 97.......|"timestamp": 42|g' \
         -e 's|"content-length": [1-9][0-9]*|"content-length": "NONZERO"|g'
 }
@@ -1393,7 +1394,7 @@ esac
 test_declare_external_prereq dtach
 test_declare_external_prereq emacs
 test_declare_external_prereq ${TEST_EMACSCLIENT}
-test_declare_external_prereq gdb
+test_declare_external_prereq ${TEST_GDB}
 test_declare_external_prereq gpg
 test_declare_external_prereq openssl
 test_declare_external_prereq gpgsm
diff --git a/util/endian-util.h b/util/endian-util.h
deleted file mode 100644 (file)
index bc80c40..0000000
+++ /dev/null
@@ -1,38 +0,0 @@
-/* this file mimics the macros present in recent GCC and CLANG */
-
-#ifndef _ENDIAN_UTIL_H
-#define _ENDIAN_UTIL_H
-
-/* This are prefixed with UTIL to avoid collisions
- *
- * You can use something like the following to define UTIL_BYTE_ORDER
- * in a configure script.
- */
-#if 0
-#include <stdio.h>
-#include <stdint.h>
-uint32_t test = 0x34333231;
-int main() { printf("%.4s\n", (const char*)&test); return 0; }
-#endif
-
-#define UTIL_ORDER_BIG_ENDIAN    4321
-#define UTIL_ORDER_LITTLE_ENDIAN  1234
-
-
-#if !defined(UTIL_BYTE_ORDER) || ((UTIL_BYTE_ORDER != UTIL_ORDER_BIG_ENDIAN) && \
-                                 (UTIL_BYTE_ORDER != UTIL_ORDER_LITTLE_ENDIAN))
-#undef UTIL_BYTE_ORDER
-#ifdef __BYTE_ORDER__
-#  if (__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__)
-#    define UTIL_BYTE_ORDER UTIL_ORDER_LITTLE_ENDIAN
-#  elif (__BYTE_ORDER__ == __ORDER_BIG_ENDIAN__)
-#    define UTIL_BYTE_ORDER UTIL_ORDER_BIG_ENDIAN
-#  else
-#    error "Unsupported __BYTE_ORDER__"
-#  endif
-#else
-#  error "UTIL_BYTE_ORDER not correctly defined and __BYTE_ORDER__ not defined."
-#endif
-#endif
-
-#endif