]> git.notmuchmail.org Git - notmuch/commitdiff
Merge tag 'debian/0.18.1-2'
authorDavid Bremner <david@tethera.net>
Sat, 9 Aug 2014 16:21:57 +0000 (13:21 -0300)
committerDavid Bremner <david@tethera.net>
Sat, 9 Aug 2014 16:21:57 +0000 (13:21 -0300)
uploaded to Debian unstable

48 files changed:
.travis.yml [new file with mode: 0644]
Makefile.local
NEWS
bindings/ruby/defs.h
bindings/ruby/init.c
bindings/ruby/query.c
configure
devel/news2wiki.pl
devel/nmbug/nmbug
devel/nmbug/nmbug-status
doc/.gitignore
doc/Makefile.local
doc/doxygen.cfg
emacs/Makefile.local
emacs/notmuch-hello.el
emacs/notmuch-jump.el [new file with mode: 0644]
emacs/notmuch-lib.el
emacs/notmuch-mua.el
emacs/notmuch-show.el
emacs/notmuch-tree.el
emacs/notmuch.el
lib/database.cc
lib/message-file.c
lib/message.cc
lib/notmuch-private.h
lib/notmuch.h
lib/sha1.c
lib/thread.cc
notmuch-config.c
notmuch-dump.c
notmuch-new.c
performance-test/Makefile.local
test/.gitignore
test/Makefile.local
test/T010-help-test.sh
test/T020-compact.sh
test/T150-tagging.sh
test/T260-thread-order.sh
test/T380-atomicity.sh
test/emacs.expected-output/notmuch-hello
test/emacs.expected-output/notmuch-hello-long-names
test/gen-threads.py [new file with mode: 0644]
test/test-databases/Makefile.local
test/test-lib-common.sh
test/test-lib.el
test/test-lib.sh
util/string-util.c
util/string-util.h

diff --git a/.travis.yml b/.travis.yml
new file mode 100644 (file)
index 0000000..dbd6434
--- /dev/null
@@ -0,0 +1,23 @@
+language: c
+before_install:
+  - sudo apt-get update -qq
+  - sudo apt-get install dtach libxapian-dev libgmime-2.6-dev libtalloc-dev python-sphinx
+
+  # Notmuch requires zlib 1.2.5.2, unfortunately travis runs on Ubuntu 12.04LTS which
+  # ships with zlib 1.2.3.3. We need to update to zlib 1.2.5.2 to be able to build.
+  # TODO: Watch https://github.com/travis-ci/travis-ci/issues/2046 and remove
+  #       this hack once travis-ci switches to Ubuntu 14.04
+  - wget 'https://github.com/notmuch/travis-files/raw/master/zlib1g-dev_1.2.8.dfsg-1ubuntu1_amd64.deb'
+  - wget 'https://github.com/notmuch/travis-files/raw/master/zlib1g_1.2.8.dfsg-1ubuntu1_amd64.deb'
+  - sudo dpkg -i zlib1g-dev_1.2.8.dfsg-1ubuntu1_amd64.deb zlib1g_1.2.8.dfsg-1ubuntu1_amd64.deb
+  - sudo apt-get install -f
+
+script:
+  - ./configure
+  - make test
+
+notifications:
+  irc:
+    channels:
+      - "chat.freenode.net#notmuch"
+    on_success: change
index 4f8f4640e8325b25bfa1a60138e4bfe5b365b3cb..81ee34774386dd8e4de2b6b472896056b434ee83 100644 (file)
 # repository), we let git append identification of the actual commit.
 PACKAGE=notmuch
 
-IS_GIT=$(shell if [ -d .git ] ; then echo yes ; else echo no; fi)
+IS_GIT=$(shell if [ -d ${srcdir}/.git ] ; then echo yes ; else echo no; fi)
 
 ifeq ($(IS_GIT),yes)
-DATE:=$(shell git log --date=short -1 --pretty=format:%cd)
+DATE:=$(shell git --git-dir=${srcdir}/.git log --date=short -1 --pretty=format:%cd)
 else
 DATE:=$(shell date +%F)
 endif
@@ -21,7 +21,7 @@ endif
 VERSION:=$(shell cat ${srcdir}/version)
 ifeq ($(filter release release-message pre-release update-versions,$(MAKECMDGOALS)),)
 ifeq ($(IS_GIT),yes)
-VERSION:=$(shell git describe --match '[0-9.]*'|sed -e s/_/~/ -e s/-/+/ -e s/-/~/)
+VERSION:=$(shell git --git-dir=${srcdir}/.git describe --abbrev=7 --match '[0-9.]*'|sed -e s/_/~/ -e s/-/+/ -e s/-/~/)
 # 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))
@@ -201,11 +201,11 @@ verify-source-tree-and-version: verify-no-dirty-code
 verify-no-dirty-code: release-checks
 ifeq ($(IS_GIT),yes)
        @printf "Checking that source tree is clean..."
-ifneq ($(shell git ls-files -m),)
+ifneq ($(shell git --git-dir=${srcdir}/.git ls-files -m),)
        @echo "No"
        @echo "The following files have been modified since the most recent git commit:"
        @echo ""
-       @git ls-files -m
+       @git --git-dir=${srcdir}/.git ls-files -m
        @echo ""
        @echo "The release will be made from the committed state, but perhaps you meant"
        @echo "to commit this code first? Please clean this up to make it more clear."
@@ -262,6 +262,10 @@ clean:
 distclean: clean
        rm -rf $(DISTCLEAN)
 
+.PHONY: dataclean
+dataclean: distclean
+       rm -rf $(DATACLEAN)
+
 notmuch_client_srcs =          \
        command-line-arguments.c\
        debugger.c              \
@@ -331,9 +335,10 @@ install-desktop:
        desktop-file-install --mode 0644 --dir "$(DESTDIR)$(desktop_dir)" notmuch.desktop
 
 SRCS  := $(SRCS) $(notmuch_client_srcs)
-CLEAN := $(CLEAN) notmuch notmuch-shared $(notmuch_client_modules) version.stamp
+CLEAN := $(CLEAN) notmuch notmuch-shared $(notmuch_client_modules)
+CLEAN := $(CLEAN) version.stamp notmuch-*.tar.gz.tmp
 
-DISTCLEAN := $(DISTCLEAN) .first-build-message Makefile.config
+DISTCLEAN := $(DISTCLEAN) .first-build-message Makefile.config sh.config
 
 DEPS := $(SRCS:%.c=.deps/%.d)
 DEPS := $(DEPS:%.cc=.deps/%.d)
diff --git a/NEWS b/NEWS
index eb8174cdb810af5eb088866a96a30ff09ee13e82..f7aaedf91d07f580ba2eb7be7dacadc986c08bbe 100644 (file)
--- a/NEWS
+++ b/NEWS
@@ -1,3 +1,27 @@
+Notmuch 0.19 (UNRELEASED)
+=========================
+
+Library changes
+---------------
+
+Add return status to notmuch_database_close and
+notmuch_database_destroy
+
+nmbug-status
+------------
+
+`nmbug-status` can now optionally load header and footer templates
+from the config file.  Use something like:
+
+    {
+      "meta": {
+        "header": "<!DOCTYPE html>\n<html lang="en">\n...",
+        "footer": "</body></html>",
+         ...
+      },
+      ...
+    },
+
 Notmuch 0.18.1 (2014-06-25)
 ===========================
 
index 5b44585a6ad0cc329d311975a7aa4470a6a890ee..f4901a047923e3340e373b4bff8ded8335996323 100644 (file)
@@ -231,6 +231,9 @@ notmuch_rb_query_search_messages (VALUE self);
 VALUE
 notmuch_rb_query_count_messages (VALUE self);
 
+VALUE
+notmuch_rb_query_count_threads (VALUE self);
+
 /* threads.c */
 VALUE
 notmuch_rb_threads_destroy (VALUE self);
index 663271d4e598ed46d60cec5467d4e82f127d8729..ab3f22df9c8106624b7e42e28bd4a805fa925686 100644 (file)
@@ -271,6 +271,7 @@ Init_notmuch (void)
     rb_define_method (notmuch_rb_cQuery, "search_threads", notmuch_rb_query_search_threads, 0); /* in query.c */
     rb_define_method (notmuch_rb_cQuery, "search_messages", notmuch_rb_query_search_messages, 0); /* in query.c */
     rb_define_method (notmuch_rb_cQuery, "count_messages", notmuch_rb_query_count_messages, 0); /* in query.c */
+    rb_define_method (notmuch_rb_cQuery, "count_threads", notmuch_rb_query_count_threads, 0); /* in query.c */
 
     /*
      * Document-class: Notmuch::Threads
index 1658edee4dac046e7bcc9f95e751ef77f9cee9da..a7dacba3351edfe631934daac37220c34cd9e781 100644 (file)
@@ -182,3 +182,22 @@ notmuch_rb_query_count_messages (VALUE self)
      */
     return UINT2NUM(notmuch_query_count_messages(query));
 }
+
+/*
+ * call-seq: QUERY.count_threads => Fixnum
+ *
+ * Return an estimate of the number of threads matching a search
+ */
+VALUE
+notmuch_rb_query_count_threads (VALUE self)
+{
+    notmuch_query_t *query;
+
+    Data_Get_Notmuch_Query (self, query);
+
+    /* Xapian exceptions are not handled properly.
+     * (function may return 0 after printing a message)
+     * Thus there is nothing we can do here...
+     */
+    return UINT2NUM(notmuch_query_count_threads(query));
+}
index 99ab74dcfb97c515a8c2b96ffd7d75f551652e0c..86ba2f7c66ead7c11c8738aaa3c40f8a1009249b 100755 (executable)
--- a/configure
+++ b/configure
@@ -43,8 +43,8 @@ fi
 
 # Set several defaults (optionally specified by the user in
 # environment variables)
-CC=${CC:-gcc}
-CXX=${CXX:-g++}
+CC=${CC:-cc}
+CXX=${CXX:-c++}
 CFLAGS=${CFLAGS:--O2}
 CPPFLAGS=${CPPFLAGS:-}
 CXXFLAGS=${CXXFLAGS:-\$(CFLAGS)}
@@ -417,6 +417,15 @@ else
     have_emacs=0
 fi
 
+printf "Checking if doxygen is available... "
+if command -v doxygen > /dev/null 2>&1; then
+    printf "Yes.\n"
+    have_doxygen=1
+else
+    printf "No (so will not install api docs)\n"
+    have_doxygen=0
+fi
+
 printf "Checking if sphinx is available and supports nroff output... "
 if hash sphinx-build > /dev/null 2>&1 && python -m sphinx.writers.manpage > /dev/null 2>&1 ; then
     printf "Yes.\n"
@@ -829,6 +838,9 @@ HAVE_SPHINX=${have_sphinx}
 # Whether there's a rst2man binary available for building documentation
 HAVE_RST2MAN=${have_rst2man}
 
+# Whether there's a doxygen binary available for building api documentation
+HAVE_DOXYGEN=${have_doxygen}
+
 # The directory to which desktop files should be installed
 desktop_dir = \$(prefix)/share/applications
 
@@ -944,3 +956,16 @@ CONFIGURE_CXXFLAGS = -DHAVE_GETLINE=\$(HAVE_GETLINE) \$(GMIME_CFLAGS)    \\
 
 CONFIGURE_LDFLAGS =  \$(GMIME_LDFLAGS) \$(TALLOC_LDFLAGS) \$(ZLIB_LDFLAGS) \$(XAPIAN_LDFLAGS)
 EOF
+
+# construct the sh.config
+cat > sh.config <<EOF
+# This sh.config was automatically generated by the ./configure
+# script of notmuch.
+
+# Whether the Xapian version in use supports compaction
+NOTMUCH_HAVE_XAPIAN_COMPACT=${have_xapian_compact}
+
+# Whether there's either sphinx or rst2man available for building
+# documentation
+NOTMUCH_HAVE_MAN=$((have_sphinx || have_rst2man))
+EOF
index 8066ba7feb74a5147a8a32c8a39b21fb3b6261c1..d966babfbc173feaf7b3c066e48ec70fbe478c86 100755 (executable)
@@ -32,8 +32,7 @@ while (<I>)
 {
     warn "$ARGV[0]:$.: tab(s) in line!\n" if /\t/;
     warn "$ARGV[0]:$.: trailing whitespace\n" if /\s\s$/;
-    # The date part in regex recognizes wip version dates like: (201x-xx-xx).
-    if (/^Notmuch\s+(\S+)\s+\((\w\w\w\w-\w\w-\w\w)\)\s*$/) {
+    if (/^Notmuch\s+(\S+)\s+\((\d\d\d\d-\d\d-\d\d|UNRELEASED)\)\s*$/) {
        # open O... autocloses previously opened file.
        open O, '>', "$ARGV[1]/release-$1.mdwn" or die $!;
        print "+ release-$1.mdwn...\n";
index b18ded7b7c1e48cf7b54a1e6901cb92c7cbf1961..998ee6b4b0d083467617dff92c1eab78132ce2d3 100755 (executable)
@@ -63,13 +63,20 @@ sub git_pipe {
   spawn ($envref, defined $ioref ? $ioref : (), defined $dir ? $dir : (), @_);
 }
 
-sub git {
+sub git_with_status {
   my $fh = git_pipe (@_);
   my $str = join ('', <$fh>);
-  unless (close $fh) {
+  close $fh;
+  my $status = $?;
+  chomp($str);
+  return ($str, $status);
+}
+
+sub git {
+  my ($str, $status) = git_with_status (@_);
+  if ($status) {
     die "'git @_' exited with nonzero value\n";
   }
-  chomp($str);
   return $str;
 }
 
@@ -423,7 +430,10 @@ sub do_status {
 sub is_unmerged {
   my $commit = shift || '@{upstream}';
 
-  my $fetch_head = git ('rev-parse', $commit);
+  my ($fetch_head, $status) = git_with_status ('rev-parse', $commit);
+  if ($status) {
+    return 0;
+  }
   my $base = git ( 'merge-base', 'HEAD', $commit);
 
   return ($base ne $fetch_head);
index 03621bd534491b185db4dbbc63e4ad38b52b6d14..f0809f193e108fb024982524a2a56ec1c3250173 100755 (executable)
@@ -1,10 +1,30 @@
 #!/usr/bin/python
 #
 # Copyright (c) 2011-2012 David Bremner <david@tethera.net>
-# License: Same as notmuch
+#
 # dependencies
 #       - python 2.6 for json
 #       - argparse; either python 2.7, or install separately
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see http://www.gnu.org/licenses/ .
+
+"""Generate HTML for one or more notmuch searches.
+
+Messages matching each search are grouped by thread.  Each message
+that contains both a subject and message-id will have the displayed
+subject link to the Gmane view of the message.
+"""
 
 from __future__ import print_function
 from __future__ import unicode_literals
@@ -242,7 +262,7 @@ class HtmlPage (Page):
     def _slug(self, string):
         return self._slug_regexp.sub('-', string)
 
-parser = argparse.ArgumentParser()
+parser = argparse.ArgumentParser(description=__doc__)
 parser.add_argument('--text', help='output plain text format',
                     action='store_true')
 parser.add_argument('--config', help='load config from given file',
@@ -256,9 +276,7 @@ args = parser.parse_args()
 
 config = read_config(path=args.config)
 
-_PAGES['text'] = Page()
-_PAGES['html'] = HtmlPage(
-    header='''<!DOCTYPE html>
+header_template = config['meta'].get('header', '''<!DOCTYPE html>
 <html lang="en">
 <head>
   <meta http-equiv="Content-Type" content="text/html; charset={encoding}" />
@@ -295,22 +313,43 @@ _PAGES['html'] = HtmlPage(
     tbody:nth-child(4n+3) tr td {{
       background-color: #bce;
     }}
+    hr {{
+      border: 0;
+      height: 1px;
+      color: #ccc;
+      background-color: #ccc;
+    }}
   </style>
 </head>
 <body>
 <h2>{title}</h2>
-<p>
-Generated: {date}<br />
 {blurb}
 </p>
 <h3>Views</h3>
-'''.format(date=datetime.datetime.utcnow().date(),
-           title=config['meta']['title'],
-           blurb=config['meta']['blurb'],
-           encoding=_ENCODING,
-           inter_message_padding='0.25em',
-           border_radius='0.5em'),
-    footer='</body>\n</html>\n',
+''')
+
+footer_template = config['meta'].get('footer', '''
+<hr>
+<p>Generated: {datetime}
+</body>
+</html>
+''')
+
+now = datetime.datetime.utcnow()
+context = {
+    'date': now,
+    'datetime': now.strftime('%Y-%m-%d %H:%M:%SZ'),
+    'title': config['meta']['title'],
+    'blurb': config['meta']['blurb'],
+    'encoding': _ENCODING,
+    'inter_message_padding': '0.25em',
+    'border_radius': '0.5em',
+    }
+
+_PAGES['text'] = Page()
+_PAGES['html'] = HtmlPage(
+    header=header_template.format(**context),
+    footer=footer_template.format(**context),
     )
 
 if args.list_views:
index a60fb31e94198450a2e787f7098147a328d72778..f0cbb9c27b243f68a92d19734b7b461bf057c9c3 100644 (file)
@@ -1,2 +1,3 @@
+*.pyc
 docdeps.mk
 _build
index bbd46100dc2d011bf361ebdd92d7e66bf781c3a0..0887413997e6d6a97b13db47d15ee072212ffd8b 100644 (file)
@@ -12,10 +12,12 @@ 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
 
 .PHONY: sphinx-html sphinx-texinfo sphinx-info
 
-.PHONY: install-man build-man
+.PHONY: install-man build-man apidocs install-apidocs
 
 %.gz: %
        rm -f $@ && gzip --stdout $^ > $@
@@ -56,6 +58,25 @@ else
 endif
        touch ${MAN_ROFF_FILES} $@
 
+install-man: install-apidocs
+
+ifeq ($(HAVE_DOXYGEN),1)
+MAN_GZIP_FILES += ${APIMAN}.gz
+apidocs: $(APIMAN)
+install-apidocs: apidocs
+       mkdir -p "$(DESTDIR)$(mandir)/man3"
+       install -m0644  $(DOCBUILDDIR)/man/man3/*.3.gz  $(DESTDIR)/$(mandir)/man3
+
+$(APIMAN): $(dir)/config.dox $(srcdir)/$(dir)/doxygen.cfg $(srcdir)/lib/notmuch.h
+       mkdir -p $(DOCBUILDDIR)/man/man3
+       doxygen $(DOXYFILE)
+       rm $(DOCBUILDDIR)/man/man3/_*.3
+       perl -pi -e 's/^[.]RI "\\fI/.RI "\\fP/' $(APIMAN)
+else
+apidocs:
+install-apidocs:
+endif
+
 # Do not try to build or install man pages if a man page converter is
 # not available.
 ifeq ($(HAVE_SPHINX)$(HAVE_RST2MAN),00)
@@ -74,8 +95,12 @@ install-man: ${MAN_GZIP_FILES}
        cd $(DESTDIR)/$(mandir)/man1 && ln -sf notmuch.1.gz notmuch-setup.1.gz
 endif
 
+$(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) $(MAN_GZIP_FILES) $(MAN_ROFF_FILES) $(dir)/conf.pyc
+CLEAN := $(CLEAN) $(MAN_GZIP_FILES) $(MAN_ROFF_FILES) $(dir)/conf.pyc $(dir)/config.dox
index bfbfcab37c70f0c77c3f463d2b0d3df236e39251..42b633948113beb2ccae9368293054fb6c600385 100644 (file)
@@ -4,11 +4,11 @@
 # Project related configuration options
 #---------------------------------------------------------------------------
 DOXYFILE_ENCODING      = UTF-8
-PROJECT_NAME           = "Notmuch 0.18"
+@INCLUDE              =  "doc/config.dox"
 PROJECT_NUMBER         =
 PROJECT_BRIEF          =
 PROJECT_LOGO           =
-OUTPUT_DIRECTORY       =
+OUTPUT_DIRECTORY       = doc/_build
 CREATE_SUBDIRS         = NO
 OUTPUT_LANGUAGE        = English
 BRIEF_MEMBER_DESC      = YES
@@ -96,7 +96,6 @@ WARN_LOGFILE           =
 #---------------------------------------------------------------------------
 # configuration options related to the input files
 #---------------------------------------------------------------------------
-INPUT                  = lib/notmuch.h
 INPUT_ENCODING         = UTF-8
 FILE_PATTERNS          =
 RECURSIVE              = NO
@@ -228,8 +227,6 @@ MAN_LINKS              = NO
 #---------------------------------------------------------------------------
 GENERATE_XML           = NO
 XML_OUTPUT             = xml
-XML_SCHEMA             =
-XML_DTD                =
 XML_PROGRAMLISTING     = YES
 #---------------------------------------------------------------------------
 # configuration options related to the DOCBOOK output
index c0d6b190c3c88ffa0b02a8a3471560e3df846ce2..1109cfa6b09033769454ef8f8f27089197d72e7e 100644 (file)
@@ -18,7 +18,8 @@ emacs_sources := \
        $(dir)/notmuch-tag.el \
        $(dir)/coolj.el \
        $(dir)/notmuch-print.el \
-       $(dir)/notmuch-version.el
+       $(dir)/notmuch-version.el \
+       $(dir)/notmuch-jump.el \
 
 $(dir)/notmuch-version.el: $(dir)/Makefile.local version.stamp
 $(dir)/notmuch-version.el: $(srcdir)/$(dir)/notmuch-version.el.tmpl
index 3de52386e86ba713575b66b7d81fc75dd177e7cc..65d062760a71a5f00a940ec945886a87be73c388 100644 (file)
@@ -85,6 +85,7 @@ searches so they still work in customize."
                (group :format "%v" :inline t (const :format "  Query: " :query) (string :format "%v")))
          (checklist :inline t
                     :format "%v"
+                    (group :format "%v" :inline t (const :format "Shortcut key: " :key) (key-sequence :format "%v"))
                     (group :format "%v" :inline t (const :format "Count-Query: " :count-query) (string :format "%v"))
                     (group :format "%v" :inline t (const :format "" :sort-order)
                            (choice :tag " Sort Order"
@@ -92,8 +93,13 @@ searches so they still work in customize."
                                    (const :tag "Oldest-first" oldest-first)
                                    (const :tag "Newest-first" newest-first))))))
 
-(defcustom notmuch-saved-searches '((:name "inbox" :query "tag:inbox")
-                                   (:name "unread" :query "tag:unread"))
+(defcustom notmuch-saved-searches
+  `((:name "inbox" :query "tag:inbox" :key ,(kbd "i"))
+    (:name "unread" :query "tag:unread" :key ,(kbd "u"))
+    (:name "flagged" :query "tag:flagged" :key ,(kbd "f"))
+    (:name "sent" :query "tag:sent" :key ,(kbd "t"))
+    (:name "drafts" :query "tag:draft" :key ,(kbd "d"))
+    (:name "all mail" :query "*" :key ,(kbd "a")))
   "A list of saved searches to display.
 
 The saved search can be given in 3 forms. The preferred way is as
@@ -101,6 +107,7 @@ a plist. Supported properties are
 
   :name            Name of the search (required).
   :query           Search to run (required).
+  :key             Optional shortcut key for `notmuch-jump-search'.
   :count-query     Optional extra query to generate the count
                    shown. If not present then the :query property
                    is used.
diff --git a/emacs/notmuch-jump.el b/emacs/notmuch-jump.el
new file mode 100644 (file)
index 0000000..05bbce5
--- /dev/null
@@ -0,0 +1,173 @@
+;; notmuch-jump.el --- User-friendly shortcut keys
+;;
+;; Copyright Â© Austin Clements
+;;
+;; 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 <http://www.gnu.org/licenses/>.
+;;
+;; Authors: Austin Clements <aclements@csail.mit.edu>
+;;          David Edmondson <dme@dme.org>
+
+(eval-when-compile (require 'cl))
+
+(require 'notmuch-lib)
+(require 'notmuch-hello)
+
+;;;###autoload
+(defun notmuch-jump-search ()
+  "Jump to a saved search by shortcut key.
+
+This prompts for and performs a saved search using the shortcut
+keys configured in the :key property of `notmuch-saved-searches'.
+Typically these shortcuts are a single key long, so this is a
+fast way to jump to a saved search from anywhere in Notmuch."
+  (interactive)
+
+  ;; Build the action map
+  (let (action-map)
+    (dolist (saved-search notmuch-saved-searches)
+      (let* ((saved-search (notmuch-hello-saved-search-to-plist saved-search))
+            (key (plist-get saved-search :key)))
+       (when key
+         (let ((name (plist-get saved-search :name))
+               (query (plist-get saved-search :query))
+               (oldest-first
+                (case (plist-get saved-search :sort-order)
+                  (newest-first nil)
+                  (oldest-first t)
+                  (otherwise (default-value notmuch-search-oldest-first)))))
+           (push (list key name
+                       `(lambda () (notmuch-search ',query ',oldest-first)))
+                 action-map)))))
+    (setq action-map (nreverse action-map))
+
+    (if action-map
+       (notmuch-jump action-map "Search: ")
+      (error "To use notmuch-jump, please customize shortcut keys in notmuch-saved-searches."))))
+
+(defvar notmuch-jump--action nil)
+
+(defun notmuch-jump (action-map prompt)
+  "Interactively prompt for one of the keys in ACTION-MAP.
+
+Displays a summary of all bindings in ACTION-MAP in the
+minibuffer, reads a key from the minibuffer, and performs the
+corresponding action.  The prompt can be canceled with C-g or
+RET.  PROMPT must be a string to use for the prompt.  PROMPT
+should include a space at the end.
+
+ACTION-MAP must be a list of triples of the form
+  (KEY LABEL ACTION)
+where KEY is a key binding, LABEL is a string label to display in
+the buffer, and ACTION is a nullary function to call.  LABEL may
+be null, in which case the action will still be bound, but will
+not appear in the pop-up buffer.
+"
+
+  (let* ((items (notmuch-jump--format-actions action-map))
+        ;; Format the table of bindings and the full prompt
+        (table
+         (with-temp-buffer
+           (notmuch-jump--insert-items (window-body-width) items)
+           (buffer-string)))
+        (full-prompt
+         (concat table "\n\n"
+                 (propertize prompt 'face 'minibuffer-prompt)))
+        ;; By default, the minibuffer applies the minibuffer face to
+        ;; the entire prompt.  However, we want to clearly
+        ;; distinguish bindings (which we put in the prompt face
+        ;; ourselves) from their labels, so disable the minibuffer's
+        ;; own re-face-ing.
+        (minibuffer-prompt-properties
+         (notmuch-plist-delete
+          (copy-sequence minibuffer-prompt-properties)
+          'face))
+        ;; Build the keymap with our bindings
+        (minibuffer-map (notmuch-jump--make-keymap action-map))
+        ;; The bindings save the the action in notmuch-jump--action
+        (notmuch-jump--action nil))
+    ;; Read the action
+    (read-from-minibuffer full-prompt nil minibuffer-map)
+
+    ;; If we got an action, do it
+    (when notmuch-jump--action
+      (funcall notmuch-jump--action))))
+
+(defun notmuch-jump--format-actions (action-map)
+  "Format the actions in ACTION-MAP.
+
+Returns a list of strings, one for each item with a label in
+ACTION-MAP.  These strings can be inserted into a tabular
+buffer."
+
+  ;; Compute the maximum key description width
+  (let ((key-width 1))
+    (dolist (entry action-map)
+      (setq key-width
+           (max key-width
+                (string-width (format-kbd-macro (first entry))))))
+    ;; Format each action
+    (mapcar (lambda (entry)
+             (let ((key (format-kbd-macro (first entry)))
+                   (desc (second entry)))
+               (concat
+                (propertize key 'face 'minibuffer-prompt)
+                (make-string (- key-width (length key)) ? )
+                " " desc)))
+           action-map)))
+
+(defun notmuch-jump--insert-items (width items)
+  "Make a table of ITEMS up to WIDTH wide in the current buffer."
+  (let* ((nitems (length items))
+        (col-width (+ 3 (apply #'max (mapcar #'string-width items))))
+        (ncols (if (> (* col-width nitems) width)
+                   (max 1 (/ width col-width))
+                 ;; Items fit on one line.  Space them out
+                 (setq col-width (/ width nitems))
+                 (length items))))
+    (while items
+      (dotimes (col ncols)
+       (when items
+         (let ((item (pop items)))
+           (insert item)
+           (when (and items (< col (- ncols 1)))
+             (insert (make-string (- col-width (string-width item)) ? ))))))
+      (when items
+       (insert "\n")))))
+
+(defvar notmuch-jump-minibuffer-map
+  (let ((map (make-sparse-keymap)))
+    (set-keymap-parent map minibuffer-local-map)
+    ;; Make this like a special-mode keymap, with no self-insert-command
+    (suppress-keymap map)
+    map)
+  "Base keymap for notmuch-jump's minibuffer keymap.")
+
+(defun notmuch-jump--make-keymap (action-map)
+  "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))))
+    map))
+
+(unless (fboundp 'window-body-width)
+  ;; Compatibility for Emacs pre-24
+  (defun window-body-width (&optional window)
+    (let ((edges (window-inside-edges window)))
+      (- (caddr edges) (car edges)))))
index 2941da3eb2ca98edeefe283330ff706ddf0d6c1e..19269e3c469b9adbf01776617d66bdf4974b1b86 100644 (file)
@@ -25,8 +25,8 @@
 (require 'mm-decode)
 (require 'cl)
 
-(defvar notmuch-command "notmuch"
-  "Command to run the notmuch binary.")
+(autoload 'notmuch-jump-search "notmuch-jump"
+  "Jump to a saved search by shortcut key." t)
 
 (defgroup notmuch nil
   "Notmuch mail reader for Emacs."
   "Graphical attributes for displaying text"
   :group 'notmuch)
 
+(defcustom notmuch-command "notmuch"
+  "Name of the notmuch binary.
+
+This can be a relative or absolute path to the notmuch binary.
+If this is a relative path, it will be searched for in all of the
+directories given in `exec-path' (which is, by default, based on
+$PATH)."
+  :type 'string
+  :group 'notmuch-external)
+
 (defcustom notmuch-search-oldest-first t
   "Show the oldest mail first when searching.
 
@@ -77,7 +87,11 @@ search."
   :group 'notmuch-search)
 
 (defcustom notmuch-poll-script nil
-  "An external script to incorporate new mail into the notmuch database.
+  "[Deprecated] Command to run to incorporate new mail into the notmuch database.
+
+This option has been deprecated in favor of \"notmuch new\"
+hooks (see man notmuch-hooks).  To change the path to the notmuch
+binary, customize `notmuch-command'.
 
 This variable controls the action invoked by
 `notmuch-poll-and-refresh-this-buffer' (bound by default to 'G')
@@ -93,10 +107,7 @@ the user's needs:
 
 1. Invoke a program to transfer mail to the local mail store
 2. Invoke \"notmuch new\" to incorporate the new mail
-3. Invoke one or more \"notmuch tag\" commands to classify the mail
-
-Note that the recommended way of achieving the same is using
-\"notmuch new\" hooks."
+3. Invoke one or more \"notmuch tag\" commands to classify the mail"
   :type '(choice (const :tag "notmuch new" nil)
                 (const :tag "Disabled" "")
                 (string :tag "Custom script"))
@@ -130,6 +141,7 @@ For example, if you wanted to remove an \"inbox\" tag and add an
     (define-key map "m" 'notmuch-mua-new-mail)
     (define-key map "=" 'notmuch-refresh-this-buffer)
     (define-key map "G" 'notmuch-poll-and-refresh-this-buffer)
+    (define-key map "j" 'notmuch-jump-search)
     map)
   "Keymap shared by all notmuch modes.")
 
@@ -464,6 +476,15 @@ This replaces spaces, percents, and double quotes in STR with
       (setq list (cdr list)))
     (nreverse out)))
 
+(defun notmuch-plist-delete (plist property)
+  (let* ((xplist (cons nil plist))
+        (pred xplist))
+    (while (cdr pred)
+      (when (eq (cadr pred) property)
+       (setcdr pred (cdddr pred)))
+      (setq pred (cddr pred)))
+    (cdr xplist)))
+
 (defun notmuch-split-content-type (content-type)
   "Split content/type into 'content' and 'type'"
   (split-string content-type "/"))
index 95e4a4d33ae2933e9503b818a6a6269fc6d33311..2c5888600b6c6d2af795b39302d061935cf7aa0b 100644 (file)
@@ -346,7 +346,8 @@ the From: address first."
     (message-forward-make-body cur)
     ;; `message-forward-make-body' shows the User-agent header.  Hide
     ;; it again.
-    (message-hide-headers)))
+    (message-hide-headers)
+    (set-buffer-modified-p nil)))
 
 (defun notmuch-mua-new-reply (query-string &optional prompt-for-sender reply-all)
   "Compose a reply to the message identified by QUERY-STRING.
index df10d4bad93b3936c00461194774cef4214725c1..7549fbb2d32643d79a2483ba7b2906920aa0ae3e 100644 (file)
@@ -46,6 +46,7 @@
 (declare-function notmuch-save-attachments "notmuch" (mm-handle &optional queryp))
 (declare-function notmuch-tree "notmuch-tree"
                  (&optional query query-context target buffer-name open-target))
+(declare-function notmuch-tree-get-message-properties "notmuch-tree" nil)
 
 (defcustom notmuch-message-headers '("Subject" "To" "Cc" "Date")
   "Headers that should be shown in a message, in this order.
@@ -180,10 +181,21 @@ each attachment handler is logged in buffers with names beginning
     )
   "List of Mailing List Archives to use when stashing links.
 
-These URIs are concatenated with the current message's
-Message-Id in `notmuch-show-stash-mlarchive-link'."
+This list is used for generating a Mailing List Archive reference
+URI with the current message's Message-Id in
+`notmuch-show-stash-mlarchive-link'.
+
+If the cdr of the alist element is not a function, the cdr is
+expected to contain a URI that is concatenated with the current
+message's Message-Id to create a ML archive reference URI.
+
+If the cdr is a function, the function is called with the
+Message-Id as the argument, and the function is expected to
+return the ML archive reference URI."
   :type '(alist :key-type (string :tag "Name")
-               :value-type (string :tag "URL"))
+               :value-type (choice
+                            (string :tag "URL")
+                            (function :tag "Function returning the URL")))
   :group 'notmuch-show)
 
 (defcustom notmuch-show-stash-mlarchive-link-default "Gmane"
@@ -211,6 +223,10 @@ For example, if you wanted to remove an \"unread\" tag and add a
   :type '(repeat string)
   :group 'notmuch-show)
 
+(defcustom notmuch-show-mark-read-function #'notmuch-show-seen-current-message
+  "Function to control which messages are marked read."
+  :type 'function
+  :group 'notmuch-show)
 
 (defmacro with-current-notmuch-show-message (&rest body)
   "Evaluate body with current buffer set to the text of current message"
@@ -1145,6 +1161,8 @@ function is used."
   (let ((inhibit-read-only t))
 
     (notmuch-show-mode)
+    (add-hook 'post-command-hook #'notmuch-show-command-hook nil t)
+
     ;; Don't track undo information for this buffer
     (set 'buffer-undo-list t)
 
@@ -1186,6 +1204,15 @@ This includes:
  - the current message."
   (list (notmuch-show-get-message-id) (notmuch-show-get-message-ids-for-open-messages)))
 
+(defun notmuch-show-get-query ()
+  "Return the current query in this show buffer"
+  (if notmuch-show-query-context
+      (concat notmuch-show-thread-id
+             " and ("
+             notmuch-show-query-context
+             ")")
+    notmuch-show-thread-id))
+
 (defun notmuch-show-apply-state (state)
   "Apply STATE to the current buffer.
 
@@ -1264,46 +1291,46 @@ reset based on the original query."
 (fset 'notmuch-show-part-map notmuch-show-part-map)
 
 (defvar notmuch-show-mode-map
-      (let ((map (make-sparse-keymap)))
-       (set-keymap-parent map notmuch-common-keymap)
-       (define-key map "Z" 'notmuch-tree-from-show-current-query)
-       (define-key map (kbd "<C-tab>") 'widget-backward)
-       (define-key map (kbd "M-TAB") 'notmuch-show-previous-button)
-       (define-key map (kbd "<backtab>") 'notmuch-show-previous-button)
-       (define-key map (kbd "TAB") 'notmuch-show-next-button)
-       (define-key map "f" 'notmuch-show-forward-message)
-       (define-key map "r" 'notmuch-show-reply-sender)
-       (define-key map "R" 'notmuch-show-reply)
-       (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 "c" 'notmuch-show-stash-map)
-       (define-key map "h" 'notmuch-show-toggle-visibility-headers)
-       (define-key map "*" 'notmuch-show-tag-all)
-       (define-key map "-" 'notmuch-show-remove-tag)
-       (define-key map "+" 'notmuch-show-add-tag)
-       (define-key map "X" 'notmuch-show-archive-thread-then-exit)
-       (define-key map "x" 'notmuch-show-archive-message-then-next-or-exit)
-       (define-key map "A" 'notmuch-show-archive-thread-then-next)
-       (define-key map "a" 'notmuch-show-archive-message-then-next-or-next-thread)
-       (define-key map "N" 'notmuch-show-next-message)
-       (define-key map "P" 'notmuch-show-previous-message)
-       (define-key map "n" 'notmuch-show-next-open-message)
-       (define-key map "p" 'notmuch-show-previous-open-message)
-       (define-key map (kbd "M-n") 'notmuch-show-next-thread-show)
-       (define-key map (kbd "M-p") 'notmuch-show-previous-thread-show)
-       (define-key map (kbd "DEL") 'notmuch-show-rewind)
-       (define-key map " " 'notmuch-show-advance-and-archive)
-       (define-key map (kbd "M-RET") 'notmuch-show-open-or-close-all)
-       (define-key map (kbd "RET") 'notmuch-show-toggle-message)
-       (define-key map "#" 'notmuch-show-print-message)
-       (define-key map "!" 'notmuch-show-toggle-elide-non-matching)
-       (define-key map "$" 'notmuch-show-toggle-process-crypto)
-       (define-key map "<" 'notmuch-show-toggle-thread-indentation)
-       (define-key map "t" 'toggle-truncate-lines)
-       (define-key map "." 'notmuch-show-part-map)
-       map)
-      "Keymap for \"notmuch show\" buffers.")
+  (let ((map (make-sparse-keymap)))
+    (set-keymap-parent map notmuch-common-keymap)
+    (define-key map "Z" 'notmuch-tree-from-show-current-query)
+    (define-key map (kbd "<C-tab>") 'widget-backward)
+    (define-key map (kbd "M-TAB") 'notmuch-show-previous-button)
+    (define-key map (kbd "<backtab>") 'notmuch-show-previous-button)
+    (define-key map (kbd "TAB") 'notmuch-show-next-button)
+    (define-key map "f" 'notmuch-show-forward-message)
+    (define-key map "r" 'notmuch-show-reply-sender)
+    (define-key map "R" 'notmuch-show-reply)
+    (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 "c" 'notmuch-show-stash-map)
+    (define-key map "h" 'notmuch-show-toggle-visibility-headers)
+    (define-key map "*" 'notmuch-show-tag-all)
+    (define-key map "-" 'notmuch-show-remove-tag)
+    (define-key map "+" 'notmuch-show-add-tag)
+    (define-key map "X" 'notmuch-show-archive-thread-then-exit)
+    (define-key map "x" 'notmuch-show-archive-message-then-next-or-exit)
+    (define-key map "A" 'notmuch-show-archive-thread-then-next)
+    (define-key map "a" 'notmuch-show-archive-message-then-next-or-next-thread)
+    (define-key map "N" 'notmuch-show-next-message)
+    (define-key map "P" 'notmuch-show-previous-message)
+    (define-key map "n" 'notmuch-show-next-open-message)
+    (define-key map "p" 'notmuch-show-previous-open-message)
+    (define-key map (kbd "M-n") 'notmuch-show-next-thread-show)
+    (define-key map (kbd "M-p") 'notmuch-show-previous-thread-show)
+    (define-key map (kbd "DEL") 'notmuch-show-rewind)
+    (define-key map " " 'notmuch-show-advance-and-archive)
+    (define-key map (kbd "M-RET") 'notmuch-show-open-or-close-all)
+    (define-key map (kbd "RET") 'notmuch-show-toggle-message)
+    (define-key map "#" 'notmuch-show-print-message)
+    (define-key map "!" 'notmuch-show-toggle-elide-non-matching)
+    (define-key map "$" 'notmuch-show-toggle-process-crypto)
+    (define-key map "<" 'notmuch-show-toggle-thread-indentation)
+    (define-key map "t" 'toggle-truncate-lines)
+    (define-key map "." 'notmuch-show-part-map)
+    map)
+  "Keymap for \"notmuch show\" buffers.")
 (fset 'notmuch-show-mode-map notmuch-show-mode-map)
 
 (defun notmuch-show-mode ()
@@ -1448,8 +1475,18 @@ an error if there is no part containing point."
     (notmuch-show-set-message-properties props)))
 
 (defun notmuch-show-get-prop (prop &optional props)
+  "Get property PROP from current message in show or tree mode.
+
+It gets property PROP from PROPS or, if PROPS is nil, the current
+message in either tree or show. This means that several utility
+functions in notmuch-show can be used directly by notmuch-tree as
+they just need the correct message properties."
   (let ((props (or props
-                  (notmuch-show-get-message-properties))))
+                  (cond ((eq major-mode 'notmuch-show-mode)
+                         (notmuch-show-get-message-properties))
+                        ((eq major-mode 'notmuch-tree-mode)
+                         (notmuch-tree-get-message-properties))
+                        (t nil)))))
     (plist-get props prop)))
 
 (defun notmuch-show-get-message-id (&optional bare)
@@ -1533,6 +1570,23 @@ marked as unread, i.e. the tag changes in
     (apply 'notmuch-show-tag-message
           (notmuch-tag-change-list notmuch-show-mark-read-tags unread))))
 
+(defun notmuch-show-seen-current-message (start end)
+  "Mark the current message read if it is open.
+
+We only mark it read once: if it is changed back then that is a
+user decision and we should not override it."
+  (when (and (notmuch-show-message-visible-p)
+            (not (notmuch-show-get-prop :seen)))
+       (notmuch-show-mark-read)
+       (notmuch-show-set-prop :seen t)))
+
+(defun notmuch-show-command-hook ()
+  (when (eq major-mode 'notmuch-show-mode)
+    ;; We need to redisplay to get window-start and window-end correct.
+    (redisplay)
+    (save-excursion
+      (funcall notmuch-show-mark-read-function (window-start) (window-end)))))
+
 ;; Functions for getting attributes of several messages in the current
 ;; thread.
 
@@ -1668,9 +1722,7 @@ If a prefix argument is given and this is the last message in the
 thread, navigate to the next thread in the parent search buffer."
   (interactive "P")
   (if (notmuch-show-goto-message-next)
-      (progn
-       (notmuch-show-mark-read)
-       (notmuch-show-message-adjust))
+      (notmuch-show-message-adjust)
     (if pop-at-end
        (notmuch-show-next-thread)
       (goto-char (point-max)))))
@@ -1681,7 +1733,6 @@ thread, navigate to the next thread in the parent search buffer."
   (if (= (point) (notmuch-show-message-top))
       (notmuch-show-goto-message-previous)
     (notmuch-show-move-to-message-top))
-  (notmuch-show-mark-read)
   (notmuch-show-message-adjust))
 
 (defun notmuch-show-next-open-message (&optional pop-at-end)
@@ -1696,9 +1747,7 @@ to show, nil otherwise."
     (while (and (setq r (notmuch-show-goto-message-next))
                (not (notmuch-show-message-visible-p))))
     (if r
-       (progn
-         (notmuch-show-mark-read)
-         (notmuch-show-message-adjust))
+       (notmuch-show-message-adjust)
       (if pop-at-end
          (notmuch-show-next-thread)
        (goto-char (point-max))))
@@ -1711,9 +1760,7 @@ to show, nil otherwise."
     (while (and (setq r (notmuch-show-goto-message-next))
                (not (notmuch-show-get-prop :match))))
     (if r
-       (progn
-         (notmuch-show-mark-read)
-         (notmuch-show-message-adjust))
+       (notmuch-show-message-adjust)
       (goto-char (point-max)))))
 
 (defun notmuch-show-open-if-matched ()
@@ -1724,8 +1771,7 @@ to show, nil otherwise."
 (defun notmuch-show-goto-first-wanted-message ()
   "Move to the first open message and mark it read"
   (goto-char (point-min))
-  (if (notmuch-show-message-visible-p)
-      (notmuch-show-mark-read)
+  (unless (notmuch-show-message-visible-p)
     (notmuch-show-next-open-message))
   (when (eobp)
     ;; There are no matched non-excluded messages so open all matched
@@ -1733,8 +1779,7 @@ to show, nil otherwise."
     (notmuch-show-mapc 'notmuch-show-open-if-matched)
     (force-window-update)
     (goto-char (point-min))
-    (if (notmuch-show-message-visible-p)
-       (notmuch-show-mark-read)
+    (unless (notmuch-show-message-visible-p)
       (notmuch-show-next-open-message))))
 
 (defun notmuch-show-previous-open-message ()
@@ -1744,7 +1789,6 @@ to show, nil otherwise."
                  (notmuch-show-goto-message-previous)
                (notmuch-show-move-to-message-top))
              (not (notmuch-show-message-visible-p))))
-  (notmuch-show-mark-read)
   (notmuch-show-message-adjust))
 
 (defun notmuch-show-view-raw-message ()
@@ -2055,16 +2099,19 @@ This presumes that the message is available at the selected Mailing List Archive
 If optional argument MLA is non-nil, use the provided key instead of prompting
 the user (see `notmuch-show-stash-mlarchive-link-alist')."
   (interactive)
-  (notmuch-common-do-stash
-   (concat (cdr (assoc
-                (or mla
-                    (let ((completion-ignore-case t))
-                      (completing-read
-                       "Mailing List Archive: "
-                       notmuch-show-stash-mlarchive-link-alist
-                       nil t nil nil notmuch-show-stash-mlarchive-link-default)))
-                notmuch-show-stash-mlarchive-link-alist))
-          (notmuch-show-get-message-id t))))
+  (let ((url (cdr (assoc
+                  (or mla
+                      (let ((completion-ignore-case t))
+                        (completing-read
+                         "Mailing List Archive: "
+                         notmuch-show-stash-mlarchive-link-alist
+                         nil t nil nil
+                         notmuch-show-stash-mlarchive-link-default)))
+                  notmuch-show-stash-mlarchive-link-alist))))
+    (notmuch-common-do-stash
+     (if (functionp url)
+        (funcall url (notmuch-show-get-message-id t))
+       (concat url (notmuch-show-get-message-id t))))))
 
 (defun notmuch-show-stash-mlarchive-link-and-go (&optional mla)
   "Copy an ML Archive URI for the current message to the kill-ring and visit it.
index 7d5f475080cbae5d6708206173b75884af3cdd86..e859cc244f8f46f501b750746416ec65f64be4f1 100644 (file)
@@ -290,22 +290,6 @@ Some useful entries are:
     (beginning-of-line)
     (get-text-property (point) :notmuch-message-properties)))
 
-;; XXX This should really be a lib function but we are trying to
-;; reduce impact on the code base.
-(defun notmuch-show-get-prop (prop &optional props)
-  "This is a tree view overridden version of notmuch-show-get-prop
-
-It gets property PROP from PROPS or, if PROPS is nil, the current
-message in either tree or show. This means that several functions
-in notmuch-show now work unchanged in tree as they just need the
-correct message properties."
-  (let ((props (or props
-                  (cond ((eq major-mode 'notmuch-show-mode)
-                         (notmuch-show-get-message-properties))
-                        ((eq major-mode 'notmuch-tree-mode)
-                         (notmuch-tree-get-message-properties))))))
-    (plist-get props prop)))
-
 (defun notmuch-tree-set-message-properties (props)
   (save-excursion
     (beginning-of-line)
@@ -897,6 +881,15 @@ the same as for the function notmuch-tree."
       (set-process-filter proc 'notmuch-tree-process-filter)
       (set-process-query-on-exit-flag proc nil))))
 
+(defun notmuch-tree-get-query ()
+  "Return the current query in this tree buffer"
+  (if notmuch-tree-query-context
+      (concat notmuch-tree-basic-query
+             " and ("
+             notmuch-tree-query-context
+             ")")
+    notmuch-tree-basic-query))
+
 (defun notmuch-tree (&optional query query-context target buffer-name open-target)
   "Display threads matching QUERY in Tree View.
 
index 1adea9c2c7d6fccf9e863d026745971185c3859b..b44a907a74c7eb7d8ea4320176417a3534d3d55f 100644 (file)
@@ -580,7 +580,8 @@ This function advances the next thread when finished."
   (when notmuch-archive-tags
     (notmuch-search-tag
      (notmuch-tag-change-list notmuch-archive-tags unarchive) beg end))
-  (notmuch-search-next-thread))
+  (when (eq beg end)
+    (notmuch-search-next-thread)))
 
 (defun notmuch-search-update-result (result &optional pos)
   "Replace the result object of the thread at POS (or point) by
@@ -649,12 +650,12 @@ of the result."
 Here is an example of how to color search results based on tags.
  (the following text would be placed in your ~/.emacs file):
 
- (setq notmuch-search-line-faces '((\"deleted\" . (:foreground \"red\"
-                                                 :background \"blue\"))
-                                   (\"unread\" . (:foreground \"green\"))))
+ (setq notmuch-search-line-faces '((\"unread\" . (:foreground \"green\"))
+                                   (\"deleted\" . (:foreground \"red\"
+                                                 :background \"blue\"))))
 
-The attributes defined for matching tags are merged, with later
-attributes overriding earlier. A message having both \"deleted\"
+The attributes defined for matching tags are merged, with earlier
+attributes overriding later. A message having both \"deleted\"
 and \"unread\" tags with the above settings would have a green
 foreground and blue background."
   :type '(alist :key-type (string) :value-type (custom-face-edit))
@@ -862,6 +863,10 @@ PROMPT is the string to prompt with."
                          (concat "tag:" (notmuch-escape-boolean-term tag)))
                        (process-lines notmuch-command "search" "--output=tags" "*")))))
     (let ((keymap (copy-keymap minibuffer-local-map))
+         (current-query (case major-mode
+                          (notmuch-search-mode (notmuch-search-get-query))
+                          (notmuch-show-mode (notmuch-show-get-query))
+                          (notmuch-tree-mode (notmuch-tree-get-query))))
          (minibuffer-completion-table
           (completion-table-dynamic
            (lambda (string)
@@ -879,7 +884,11 @@ PROMPT is the string to prompt with."
       (define-key keymap (kbd "TAB") 'minibuffer-complete)
       (let ((history-delete-duplicates t))
        (read-from-minibuffer prompt nil keymap nil
-                             'notmuch-search-history nil nil)))))
+                             'notmuch-search-history current-query nil)))))
+
+(defun notmuch-search-get-query ()
+  "Return the current query in this search buffer"
+  notmuch-search-query-string)
 
 ;;;###autoload
 (put 'notmuch-search 'notmuch-doc "Search for messages.")
index 1efb14d4a0bdc2327a366870069867ba392ad1aa..c76029067f4c28b32933e26e432713f39becd4b0 100644 (file)
@@ -54,9 +54,12 @@ typedef struct {
  *
  * Mail document
  * -------------
- * A mail document is associated with a particular email message file
- * on disk. It is indexed with the following prefixed terms which the
- * database uses to construct threads, etc.:
+ * A mail document is associated with a particular email message. It
+ * is stored in one or more files on disk (though only one has its
+ * content indexed) and is uniquely identified  by its "id" field
+ * (which is generally the message ID). It is indexed with the
+ * following prefixed terms which the database uses to construct
+ * threads, etc.:
  *
  *    Single terms of given prefix:
  *
@@ -356,7 +359,7 @@ _message_id_compressed (void *ctx, const char *message_id)
 {
     char *sha1, *compressed;
 
-    sha1 = notmuch_sha1_of_string (message_id);
+    sha1 = _notmuch_sha1_of_string (message_id);
 
     compressed = talloc_asprintf (ctx, "notmuch-sha1-%s", sha1);
     free (sha1);
@@ -774,14 +777,17 @@ notmuch_database_open (const char *path,
     return status;
 }
 
-void
+notmuch_status_t
 notmuch_database_close (notmuch_database_t *notmuch)
 {
+    notmuch_status_t status = NOTMUCH_STATUS_SUCCESS;
+
     try {
        if (notmuch->xapian_db != NULL &&
            notmuch->mode == NOTMUCH_DATABASE_MODE_READ_WRITE)
            (static_cast <Xapian::WritableDatabase *> (notmuch->xapian_db))->flush ();
     } catch (const Xapian::Error &error) {
+       status = NOTMUCH_STATUS_XAPIAN_EXCEPTION;
        if (! notmuch->exception_reported) {
            fprintf (stderr, "Error: A Xapian exception occurred flushing database: %s\n",
                     error.get_msg().c_str());
@@ -795,7 +801,9 @@ notmuch_database_close (notmuch_database_t *notmuch)
        try {
            notmuch->xapian_db->close();
        } catch (const Xapian::Error &error) {
-           /* do nothing */
+           /* don't clobber previous error status */
+           if (status == NOTMUCH_STATUS_SUCCESS)
+               status = NOTMUCH_STATUS_XAPIAN_EXCEPTION;
        }
     }
 
@@ -809,6 +817,8 @@ notmuch_database_close (notmuch_database_t *notmuch)
     notmuch->value_range_processor = NULL;
     delete notmuch->date_range_processor;
     notmuch->date_range_processor = NULL;
+
+    return status;
 }
 
 #if HAVE_XAPIAN_COMPACT
@@ -972,8 +982,15 @@ notmuch_database_compact (const char *path,
     }
 
   DONE:
-    if (notmuch)
-       notmuch_database_destroy (notmuch);
+    if (notmuch) {
+       notmuch_status_t ret2;
+
+       ret2 = notmuch_database_destroy (notmuch);
+
+       /* don't clobber previous error status */
+       if (ret == NOTMUCH_STATUS_SUCCESS && ret2 != NOTMUCH_STATUS_SUCCESS)
+           ret = ret2;
+    }
 
     talloc_free (local);
 
@@ -991,11 +1008,15 @@ notmuch_database_compact (unused (const char *path),
 }
 #endif
 
-void
+notmuch_status_t
 notmuch_database_destroy (notmuch_database_t *notmuch)
 {
-    notmuch_database_close (notmuch);
+    notmuch_status_t status;
+
+    status = notmuch_database_close (notmuch);
     talloc_free (notmuch);
+
+    return status;
 }
 
 const char *
@@ -1356,7 +1377,7 @@ _notmuch_database_get_directory_db_path (const char *path)
     int term_len = strlen (_find_prefix ("directory")) + strlen (path);
 
     if (term_len > NOTMUCH_TERM_MAX)
-       return notmuch_sha1_of_string (path);
+       return _notmuch_sha1_of_string (path);
     else
        return path;
 }
@@ -1758,12 +1779,12 @@ _notmuch_database_link_message_to_parents (notmuch_database_t *notmuch,
                                     _my_talloc_free_for_g_hash, NULL);
     this_message_id = notmuch_message_get_message_id (message);
 
-    refs = notmuch_message_file_get_header (message_file, "references");
+    refs = _notmuch_message_file_get_header (message_file, "references");
     last_ref_message_id = parse_references (message,
                                            this_message_id,
                                            parents, refs);
 
-    in_reply_to = notmuch_message_file_get_header (message_file, "in-reply-to");
+    in_reply_to = _notmuch_message_file_get_header (message_file, "in-reply-to");
     in_reply_to_message_id = parse_references (message,
                                               this_message_id,
                                               parents, in_reply_to);
@@ -1961,7 +1982,7 @@ notmuch_database_add_message (notmuch_database_t *notmuch,
     if (ret)
        return ret;
 
-    message_file = notmuch_message_file_open (filename);
+    message_file = _notmuch_message_file_open (filename);
     if (message_file == NULL)
        return NOTMUCH_STATUS_FILE_ERROR;
 
@@ -1982,9 +2003,9 @@ notmuch_database_add_message (notmuch_database_t *notmuch,
         * let's make sure that what we're looking at looks like an
         * actual email message.
         */
-       from = notmuch_message_file_get_header (message_file, "from");
-       subject = notmuch_message_file_get_header (message_file, "subject");
-       to = notmuch_message_file_get_header (message_file, "to");
+       from = _notmuch_message_file_get_header (message_file, "from");
+       subject = _notmuch_message_file_get_header (message_file, "subject");
+       to = _notmuch_message_file_get_header (message_file, "to");
 
        if ((from == NULL || *from == '\0') &&
            (subject == NULL || *subject == '\0') &&
@@ -1997,7 +2018,7 @@ notmuch_database_add_message (notmuch_database_t *notmuch,
        /* Now that we're sure it's mail, the first order of business
         * is to find a message ID (or else create one ourselves). */
 
-       header = notmuch_message_file_get_header (message_file, "message-id");
+       header = _notmuch_message_file_get_header (message_file, "message-id");
        if (header && *header != '\0') {
            message_id = _parse_message_id (message_file, header, NULL);
 
@@ -2018,7 +2039,7 @@ notmuch_database_add_message (notmuch_database_t *notmuch,
        if (message_id == NULL ) {
            /* No message-id at all, let's generate one by taking a
             * hash over the file's contents. */
-           char *sha1 = notmuch_sha1_of_file (filename);
+           char *sha1 = _notmuch_sha1_of_file (filename);
 
            /* If that failed too, something is really wrong. Give up. */
            if (sha1 == NULL) {
@@ -2058,7 +2079,7 @@ notmuch_database_add_message (notmuch_database_t *notmuch,
            if (ret)
                goto DONE;
 
-           date = notmuch_message_file_get_header (message_file, "date");
+           date = _notmuch_message_file_get_header (message_file, "date");
            _notmuch_message_set_header_values (message, date, from, subject);
 
            ret = _notmuch_message_index_file (message, message_file);
@@ -2087,7 +2108,7 @@ notmuch_database_add_message (notmuch_database_t *notmuch,
     }
 
     if (message_file)
-       notmuch_message_file_close (message_file);
+       _notmuch_message_file_close (message_file);
 
     ret2 = notmuch_database_end_atomic (notmuch);
     if ((ret == NOTMUCH_STATUS_SUCCESS ||
index 483ba1e98a1fd080f753f56e8aeae90ad1ff64e0..eda1b748e2022d73d2d8737f04151f342e0787ad 100644 (file)
@@ -99,19 +99,19 @@ _notmuch_message_file_open_ctx (void *ctx, const char *filename)
 
   FAIL:
     fprintf (stderr, "Error opening %s: %s\n", filename, strerror (errno));
-    notmuch_message_file_close (message);
+    _notmuch_message_file_close (message);
 
     return NULL;
 }
 
 notmuch_message_file_t *
-notmuch_message_file_open (const char *filename)
+_notmuch_message_file_open (const char *filename)
 {
     return _notmuch_message_file_open_ctx (NULL, filename);
 }
 
 void
-notmuch_message_file_close (notmuch_message_file_t *message)
+_notmuch_message_file_close (notmuch_message_file_t *message)
 {
     talloc_free (message);
 }
@@ -297,7 +297,7 @@ _notmuch_message_file_get_combined_header (notmuch_message_file_t *message,
 }
 
 const char *
-notmuch_message_file_get_header (notmuch_message_file_t *message,
+_notmuch_message_file_get_header (notmuch_message_file_t *message,
                                 const char *header)
 {
     const char *value;
index d0b7351e71796ad2e288a018cbc5ef00ff4fcb67..3f934265a73727feceb0f5dfa26bc70f55669dbe 100644 (file)
@@ -193,14 +193,16 @@ _notmuch_message_create (const void *talloc_owner,
  *     There is already a document with message ID 'message_id' in the
  *     database. The returned message can be used to query/modify the
  *     document.
+ *
  *   NOTMUCH_PRIVATE_STATUS_NO_DOCUMENT_FOUND:
  *
  *     No document with 'message_id' exists in the database. The
  *     returned message contains a newly created document (not yet
  *     added to the database) and a document ID that is known not to
- *     exist in the database. The caller can modify the message, and a
- *     call to _notmuch_message_sync will add * the document to the
- *     database.
+ *     exist in the database.  This message is "blank"; that is, it
+ *     contains only a message ID and no other metadata. The caller
+ *     can modify the message, and a call to _notmuch_message_sync
+ *     will add the document to the database.
  *
  * If an error occurs, this function will return NULL and *status
  * will be set as appropriate. (The status pointer argument must
@@ -439,7 +441,7 @@ notmuch_message_get_header (notmuch_message_t *message, const char *header)
     if (message->message_file == NULL)
        return NULL;
 
-    return notmuch_message_file_get_header (message->message_file, header);
+    return _notmuch_message_file_get_header (message->message_file, header);
 }
 
 /* Return the message ID from the In-Reply-To header of 'message'.
@@ -898,13 +900,13 @@ notmuch_message_get_tags (notmuch_message_t *message)
 }
 
 const char *
-notmuch_message_get_author (notmuch_message_t *message)
+_notmuch_message_get_author (notmuch_message_t *message)
 {
     return message->author;
 }
 
 void
-notmuch_message_set_author (notmuch_message_t *message,
+_notmuch_message_set_author (notmuch_message_t *message,
                            const char *author)
 {
     if (message->author)
@@ -971,7 +973,7 @@ void
 _notmuch_message_close (notmuch_message_t *message)
 {
     if (message->message_file) {
-       notmuch_message_file_close (message->message_file);
+       _notmuch_message_file_close (message->message_file);
        message->message_file = NULL;
     }
 }
@@ -1032,6 +1034,8 @@ _notmuch_message_gen_terms (notmuch_message_t *message,
        /* Create a gap between this an the next terms so they don't
         * appear to be a phrase. */
        message->termpos = term_gen->get_termpos () + 100;
+
+       _notmuch_message_invalidate_metadata (message, prefix_name);
     }
 
     term_gen->set_termpos (message->termpos);
index 703ae7bb7a014c06bb03f18d7b9994b42275ba21..17f30613bd00509e912d594101d21b7f587aae4d 100644 (file)
@@ -316,11 +316,11 @@ _notmuch_message_clear_data (notmuch_message_t *message);
 /* Set the author member of 'message' - this is the representation used
  * when displaying the message */
 void
-notmuch_message_set_author (notmuch_message_t *message, const char *author);
+_notmuch_message_set_author (notmuch_message_t *message, const char *author);
 
 /* Get the author member of 'message' */
 const char *
-notmuch_message_get_author (notmuch_message_t *message);
+_notmuch_message_get_author (notmuch_message_t *message);
 
 /* message-file.c */
 
@@ -337,7 +337,7 @@ typedef struct _notmuch_message_file notmuch_message_file_t;
  * Returns NULL if any error occurs.
  */
 notmuch_message_file_t *
-notmuch_message_file_open (const char *filename);
+_notmuch_message_file_open (const char *filename);
 
 /* Like notmuch_message_file_open but with 'ctx' as the talloc owner. */
 notmuch_message_file_t *
@@ -345,7 +345,7 @@ _notmuch_message_file_open_ctx (void *ctx, const char *filename);
 
 /* Close a notmuch message previously opened with notmuch_message_open. */
 void
-notmuch_message_file_close (notmuch_message_file_t *message);
+_notmuch_message_file_close (notmuch_message_file_t *message);
 
 /* Parse the message.
  *
@@ -386,7 +386,7 @@ _notmuch_message_file_get_mime_message (notmuch_message_file_t *message,
  * contain a header line matching 'header'.
  */
 const char *
-notmuch_message_file_get_header (notmuch_message_file_t *message,
+_notmuch_message_file_get_header (notmuch_message_file_t *message,
                                 const char *header);
 
 /* index.cc */
@@ -455,10 +455,10 @@ _notmuch_message_add_reply (notmuch_message_t *message,
 /* sha1.c */
 
 char *
-notmuch_sha1_of_string (const char *str);
+_notmuch_sha1_of_string (const char *str);
 
 char *
-notmuch_sha1_of_file (const char *filename);
+_notmuch_sha1_of_file (const char *filename);
 
 /* string-list.c */
 
index 350bed8bdbba956da7114bf969dfdcde513b5b8e..3c5ec9883b1f0aa4c088717f070d073b8495d9c9 100644 (file)
@@ -287,8 +287,16 @@ notmuch_database_open (const char *path,
  *
  * notmuch_database_close can be called multiple times.  Later calls
  * have no effect.
+ *
+ * Return value:
+ *
+ * NOTMUCH_STATUS_SUCCESS: Successfully closed the database.
+ *
+ * NOTMUCH_STATUS_XAPIAN_EXCEPTION: A Xapian exception occurred; the
+ *     database has been closed but there are no guarantees the
+ *     changes to the database, if any, have been flushed to disk.
  */
-void
+notmuch_status_t
 notmuch_database_close (notmuch_database_t *database);
 
 /**
@@ -317,8 +325,11 @@ notmuch_database_compact (const char* path,
 /**
  * Destroy the notmuch database, closing it if necessary and freeing
  * all associated resources.
+ *
+ * Return value as in notmuch_database_close if the database was open;
+ * notmuch_database_destroy itself has no failure modes.
  */
-void
+notmuch_status_t
 notmuch_database_destroy (notmuch_database_t *database);
 
 /**
index cc481086c3aca6d443dfe249e6587e90755d3196..94060d577233a4602b37a70b2343404c5dc28da3 100644 (file)
@@ -50,7 +50,7 @@ _hex_of_sha1_digest (const unsigned char digest[SHA1_DIGEST_SIZE])
  * should free() when finished.
  */
 char *
-notmuch_sha1_of_string (const char *str)
+_notmuch_sha1_of_string (const char *str)
 {
     sha1_ctx sha1;
     unsigned char digest[SHA1_DIGEST_SIZE];
@@ -74,7 +74,7 @@ notmuch_sha1_of_string (const char *str)
  * file not found, etc.), this function returns NULL.
  */
 char *
-notmuch_sha1_of_file (const char *filename)
+_notmuch_sha1_of_file (const char *filename)
 {
     FILE *file;
 #define BLOCK_SIZE 4096
index 8f53e12231f2e3f6a93f70997bb188208a990c76..8922403ea4b10c09d0982778245c69c583de7bf1 100644 (file)
@@ -284,7 +284,7 @@ _thread_add_message (notmuch_thread_t *thread,
            }
            clean_author = _thread_cleanup_author (thread, author, from);
            _thread_add_author (thread, clean_author);
-           notmuch_message_set_author (message, clean_author);
+           _notmuch_message_set_author (message, clean_author);
        }
        g_object_unref (G_OBJECT (list));
     }
@@ -373,7 +373,7 @@ _thread_add_matched_message (notmuch_thread_t *thread,
                                  NOTMUCH_MESSAGE_FLAG_MATCH, 1);
     }
 
-    _thread_add_matched_author (thread, notmuch_message_get_author (hashed_message));
+    _thread_add_matched_author (thread, _notmuch_message_get_author (hashed_message));
 }
 
 static void
index 4886d366f7e787af9d6ffbc6dc8a92efb73c2628..db487dbe828ba808b6c3223a8d33df581864d74f 100644 (file)
@@ -217,9 +217,10 @@ get_username_from_passwd_file (void *ctx)
  *     These default configuration settings are determined as
  *     follows:
  *
- *             database_path:          $HOME/mail
+ *             database_path:          $MAILDIR, otherwise $HOME/mail
  *
- *             user_name:              From /etc/passwd
+ *             user_name:              $NAME variable if set, otherwise
+ *                                     read from /etc/passwd
  *
  *             user_primary_mail:      $EMAIL variable if set, otherwise
  *                                     constructed from the username and
@@ -322,14 +323,22 @@ notmuch_config_open (void *ctx,
 
 
     if (notmuch_config_get_database_path (config) == NULL) {
-       char *path = talloc_asprintf (config, "%s/mail",
-                                     getenv ("HOME"));
+       char *path = getenv ("MAILDIR");
+       if (path)
+           path = talloc_strdup (config, path);
+       else
+           path = talloc_asprintf (config, "%s/mail",
+                                   getenv ("HOME"));
        notmuch_config_set_database_path (config, path);
        talloc_free (path);
     }
 
     if (notmuch_config_get_user_name (config) == NULL) {
-       char *name = get_name_from_passwd_file (config);
+       char *name = getenv ("NAME");
+       if (name)
+           name = talloc_strdup (config, name);
+       else
+           name = get_name_from_passwd_file (config);
        notmuch_config_set_user_name (config, name);
        talloc_free (name);
     }
index 887a20822d828ef6dab98f58f156e37bebefdf06..9c6ad7f47b0fde514a5e64c96705d598f2eee7ec 100644 (file)
@@ -212,7 +212,7 @@ notmuch_dump_command (notmuch_config_t *config, int argc, char *argv[])
     int ret;
 
     if (notmuch_database_open (notmuch_config_get_database_path (config),
-                              NOTMUCH_DATABASE_MODE_READ_ONLY, &notmuch))
+                              NOTMUCH_DATABASE_MODE_READ_WRITE, &notmuch))
        return EXIT_FAILURE;
 
     char *output_file_name = NULL;
index d269c7cd7e254badd4ae85ca026a478a8cad4655..569100560accbc393a52b124d14f55178fb6e63d 100644 (file)
@@ -923,6 +923,7 @@ notmuch_new_command (notmuch_config_t *config, int argc, char *argv[])
     notmuch_bool_t timer_is_active = FALSE;
     notmuch_bool_t no_hooks = FALSE;
     notmuch_bool_t quiet = FALSE, verbose = FALSE;
+    notmuch_status_t status;
 
     add_files_state.verbosity = VERBOSITY_NORMAL;
     add_files_state.debug = FALSE;
@@ -1019,9 +1020,16 @@ notmuch_new_command (notmuch_config_t *config, int argc, char *argv[])
            }
 
            gettimeofday (&add_files_state.tv_start, NULL);
-           notmuch_database_upgrade (notmuch,
-                                     add_files_state.verbosity >= VERBOSITY_NORMAL ? upgrade_print_progress : NULL,
-                                     &add_files_state);
+           status = notmuch_database_upgrade (
+               notmuch,
+               add_files_state.verbosity >= VERBOSITY_NORMAL ? upgrade_print_progress : NULL,
+               &add_files_state);
+           if (status) {
+               printf ("Upgrade failed: %s\n",
+                       notmuch_status_to_string (status));
+               notmuch_database_destroy (notmuch);
+               return EXIT_FAILURE;
+           }
            if (add_files_state.verbosity >= VERBOSITY_NORMAL)
                printf ("Your notmuch database has now been upgraded to database format version %u.\n",
                    notmuch_database_get_version (notmuch));
@@ -1091,7 +1099,6 @@ notmuch_new_command (notmuch_config_t *config, int argc, char *argv[])
     }
 
     for (f = add_files_state.directory_mtimes->head; f && !interrupted; f = f->next) {
-       notmuch_status_t status;
        notmuch_directory_t *directory;
        status = notmuch_database_get_directory (notmuch, f->filename, &directory);
        if (status == NOTMUCH_STATUS_SUCCESS && directory) {
index d97e56d91a121cf2ad650bb16d61fa44926c819a..3469aa3d6aadc67973efc8fd58589059cd875fb5 100644 (file)
@@ -40,4 +40,5 @@ download-corpus:
        wget -O ${TXZFILE} ${DEFAULT_URL}
 
 CLEAN := $(CLEAN) $(dir)/tmp.* $(dir)/log.*
-DISTCLEAN := $(dir)/corpus $(dir)/notmuch.cache.*
+DISTCLEAN := $(DISTCLEAN) $(dir)/corpus $(dir)/notmuch.cache.*
+DATACLEAN := $(DATACLEAN) $(TXZFILE)
index 97e0248787288e5c68039c7b48c5c5859f8c4fbd..b3b706d851886f76b4a119363b98f4bb240f4093 100644 (file)
@@ -1,9 +1,9 @@
-test-results
-corpus.mail
-smtp-dummy
-symbol-test
 arg-test
+corpus.mail
 hex-xcode
-random-corpus
 parse-time
+random-corpus
+smtp-dummy
+symbol-test
+test-results
 tmp.*
index d622eafee7865588ef4a0112225aebed8fe22bd0..916dd0bd6d347e51bf011fe23165e60576c2d91c 100644 (file)
@@ -35,30 +35,19 @@ $(dir)/symbol-test: $(dir)/symbol-test.o lib/$(LINKER_NAME)
 $(dir)/parse-time: $(dir)/parse-time.o parse-time-string/parse-time-string.o
        $(call quiet,CC) $^ -o $@
 
-$(dir)/have-compact: Makefile.config
-ifeq ($(HAVE_XAPIAN_COMPACT),1)
-       ln -sf /bin/true $@
-else
-       ln -sf /bin/false $@
-endif
-
-$(dir)/have-man: Makefile.config
-ifeq ($(HAVE_SPHINX)$(HAVE_RST2MAN),00)
-       ln -sf /bin/false $@
-else
-       ln -sf /bin/true $@
-endif
-
 .PHONY: test check
 
-TEST_BINARIES=$(dir)/arg-test \
-             $(dir)/have-compact \
-             $(dir)/have-man \
-             $(dir)/hex-xcode \
-             $(dir)/random-corpus \
-             $(dir)/parse-time \
-             $(dir)/smtp-dummy \
-             $(dir)/symbol-test
+test_main_srcs=$(dir)/arg-test.c \
+             $(dir)/hex-xcode.c \
+             $(dir)/random-corpus.c \
+             $(dir)/parse-time.c \
+             $(dir)/smtp-dummy.c \
+             $(dir)/symbol-test.cc \
+
+test_srcs=$(test_main_srcs) $(dir)/database-test.c
+
+TEST_BINARIES := $(test_main_srcs:.c=)
+TEST_BINARIES := $(TEST_BINARIES:.cc=)
 
 test-binaries: $(TEST_BINARIES)
 
@@ -67,7 +56,7 @@ test: all test-binaries
 
 check: test
 
-SRCS := $(SRCS) $(smtp_dummy_srcs)
+SRCS := $(SRCS) $(test_srcs)
 CLEAN += $(TEST_BINARIES) $(addsuffix .o,$(TEST_BINARIES)) \
         $(dir)/database-test.o \
         $(dir)/corpus.mail $(dir)/test-results $(dir)/tmp.*
index 77410bc54e66253b57fccef0013f0d4c3034acf4..caf8bdb094805f54ccbbe232a78cc26a4a504c5c 100755 (executable)
@@ -7,7 +7,7 @@ test_expect_success 'notmuch --help' 'notmuch --help'
 test_expect_success 'notmuch help' 'notmuch help'
 test_expect_success 'notmuch --version' 'notmuch --version'
 
-if ${TEST_DIRECTORY}/have-man; then
+if [ $NOTMUCH_HAVE_MAN -eq 1 ]; then
     test_expect_success 'notmuch --help tag' 'notmuch --help tag'
     test_expect_success 'notmuch help tag' 'notmuch help tag'
 else
index 77bb9632cb11ba99f0b691260157a47f119f0265..507f769857ddad7ead5912d286008f35c9af979c 100755 (executable)
@@ -10,7 +10,7 @@ notmuch tag +tag1 \*
 notmuch tag +tag2 subject:Two
 notmuch tag -tag1 +tag3 subject:Three
 
-if ! ${TEST_DIRECTORY}/have-compact; then
+if [ $NOTMUCH_HAVE_XAPIAN_COMPACT -eq 0 ]; then
     test_begin_subtest "Compact unsupported: error message"
     output=$(notmuch compact --quiet 2>&1)
     test_expect_equal "$output" "notmuch was compiled against a xapian version lacking compaction support.
index dc118f331ff054ac53476a7221e1d9f42a35d987..45471ac8de72962937761873c81b1f6bcb229609 100755 (executable)
@@ -247,8 +247,8 @@ ${TEST_DIRECTORY}/random-corpus --config-path=${NOTMUCH_CONFIG} \
 notmuch dump --format=batch-tag | sed 's/^.* -- /+common_tag -- /' | \
     sort > EXPECTED
 
-notmuch dump --format=batch-tag | sed 's/^.* -- /  -- /' | \
-    notmuch restore --format=batch-tag
+notmuch dump --format=batch-tag | sed 's/^.* -- /  -- /' > INTERMEDIATE_STEP
+notmuch restore --format=batch-tag < INTERMEDIATE_STEP
 
 notmuch tag --batch < EXPECTED
 
index 6c3a4b3f70a2b85124075e8696fc72ef061f2a12..b435d79fb0db0712c3479c407e0ac3a03bbaf951 100755 (executable)
@@ -2,31 +2,75 @@
 test_description="threading when messages received out of order"
 . ./test-lib.sh
 
-test_begin_subtest "Adding initial child message"
-generate_message [body]=foo "[in-reply-to]=\<parent-id\>" [subject]=brokenthreadtest '[date]="Sat, 01 Jan 2000 12:00:00 -0000"'
-output=$(NOTMUCH_NEW)
-test_expect_equal "$output" "Added 1 new message to the database."
+# Generate all single-root four message thread structures.  We'll use
+# this for multiple tests below.
+THREADS=$(python ${TEST_DIRECTORY}/gen-threads.py 4)
+nthreads=$(wc -l <<< "$THREADS")
 
-test_begin_subtest "Searching returns the message"
-output=$(notmuch search foo | notmuch_search_sanitize)
-test_expect_equal "$output" "thread:XXX   2000-01-01 [1/1] Notmuch Test Suite; brokenthreadtest (inbox unread)"
+test_begin_subtest "Messages with one parent get linked in all delivery orders"
+# In the first variant, this delivers messages that reference only
+# their immediate parent.  Hence, we should only expect threads to be
+# fully joined at the end.
+for ((n = 0; n < 4; n++)); do
+    # Deliver the n'th message of every thread
+    thread=0
+    while read -a parents; do
+        parent=${parents[$n]}
+        generate_message \
+            [id]=m$n@t$thread [in-reply-to]="\<m$parent@t$thread\>" \
+            [subject]=p$thread [from]=m$n
+        thread=$((thread + 1))
+    done <<< "$THREADS"
+    notmuch new > /dev/null
+done
+output=$(notmuch search --sort=newest-first '*' | notmuch_search_sanitize)
+expected=$(for ((i = 0; i < $nthreads; i++)); do
+        echo "thread:XXX   2001-01-05 [4/4] m3, m2, m1, m0; p$i (inbox unread)"
+    done)
+test_expect_equal "$output" "$expected"
 
-test_begin_subtest "Adding second child message"
-generate_message [body]=foo "[in-reply-to]=\<parent-id\>" [subject]=brokenthreadtest '[date]="Sat, 01 Jan 2000 12:00:00 -0000"'
-output=$(NOTMUCH_NEW)
-test_expect_equal "$output" "Added 1 new message to the database."
+test_begin_subtest "Messages with all parents get linked in all delivery orders"
+test_subtest_known_broken
+# Here we do the same thing as the previous test, but each message
+# references all of its parents.  Since every message references the
+# root of the thread, each thread should always be fully joined.  This
+# is currently broken because of the bug detailed in
+# id:8738h7kv2q.fsf@qmul.ac.uk.
+rm ${MAIL_DIR}/*
+notmuch new > /dev/null
+output=""
+expected=""
+for ((n = 0; n < 4; n++)); do
+    # Deliver the n'th message of every thread
+    thread=0
+    while read -a parents; do
+        references=""
+        parent=${parents[$n]}
+        while [[ $parent != None ]]; do
+            references="<m$parent@t$thread> $references"
+            parent=${parents[$parent]}
+        done
 
-test_begin_subtest "Searching returns both messages in one thread"
-output=$(notmuch search foo | notmuch_search_sanitize)
-test_expect_equal "$output" "thread:XXX   2000-01-01 [2/2] Notmuch Test Suite; brokenthreadtest (inbox unread)"
+        generate_message \
+            [id]=m$n@t$thread [references]="'$references'" \
+            [subject]=p$thread [from]=m$n
+        thread=$((thread + 1))
+    done <<< "$THREADS"
+    notmuch new > /dev/null
 
-test_begin_subtest "Adding parent message"
-generate_message [body]=foo [id]=parent-id [subject]=brokenthreadtest '[date]="Sat, 01 Jan 2000 12:00:00 -0000"'
-output=$(NOTMUCH_NEW)
-test_expect_equal "$output" "Added 1 new message to the database."
+    output="$output
+$(notmuch search --sort=newest-first '*' | notmuch_search_sanitize)"
 
-test_begin_subtest "Searching returns all three messages in one thread"
-output=$(notmuch search foo | notmuch_search_sanitize)
-test_expect_equal "$output" "thread:XXX   2000-01-01 [3/3] Notmuch Test Suite; brokenthreadtest (inbox unread)"
+    # Construct expected output
+    template="thread:XXX   2001-01-05 [$((n+1))/$((n+1))]"
+    for ((m = n; m > 0; m--)); do
+        template="$template m$m,"
+    done
+    expected="$expected
+$(for ((i = 0; i < $nthreads; i++)); do
+        echo "$template m0; p$i (inbox unread)"
+    done)"
+done
+test_expect_equal "$output" "$expected"
 
 test_done
index 1c786fa2724262dad40cc9f7b883b6269586bc1d..2daef9066fabaa854d48dd36f9dc872495e18d18 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.gdb notmuch >/dev/null 2>/dev/null
+    gdb -tty /dev/null -batch -x $TEST_DIRECTORY/atomicity.gdb notmuch 1>gdb.out 2>&1
 
     # Get the final, golden output
     notmuch search '*' > expected
index 2d698917ed992737707f7aa6618615b816f17881..9ba4cfc1d560e4c33db3b296924ddb8e834f81cb 100644 (file)
@@ -2,7 +2,7 @@
 
 Saved searches: [edit]
 
-         52 inbox           52 unread
+         52 inbox           52 unread          52 all mail
 
 Search:                                                                     .
 
index 486d0d9ae0c8cf2e69de64f8c495c79c5e974054..1c8d6eb624b97237ad458d5ed61cadc666521dcf 100644 (file)
@@ -2,7 +2,7 @@
 
 Saved searches: [edit]
 
-         52 inbox           52 unread
+         52 inbox           52 unread          52 all mail
 
 Search:                                                                     .
 
diff --git a/test/gen-threads.py b/test/gen-threads.py
new file mode 100644 (file)
index 0000000..9fbb847
--- /dev/null
@@ -0,0 +1,33 @@
+# Generate all possible single-root message thread structures of size
+# argv[1].  Each output line is a thread structure, where the n'th
+# field is either a number giving the parent of message n or "None"
+# for the root.
+
+import sys
+from itertools import chain, combinations
+
+def subsets(s):
+    return chain.from_iterable(combinations(s, r) for r in range(len(s)+1))
+
+nodes = set(range(int(sys.argv[1])))
+
+# Queue of (tree, free, to_expand) where tree is a {node: parent}
+# dictionary, free is a set of unattached nodes, and to_expand is
+# itself a queue of nodes in the tree that need to be expanded.
+# The queue starts with all single-node trees.
+queue = [({root: None}, nodes - {root}, (root,)) for root in nodes]
+
+# Process queue
+while queue:
+    tree, free, to_expand = queue.pop()
+
+    if len(to_expand) == 0:
+        # Only print full-sized trees
+        if len(free) == 0:
+            print(" ".join(map(str, [msg[1] for msg in sorted(tree.items())])))
+    else:
+        # Expand node to_expand[0] with each possible set of children
+        for children in subsets(free):
+            ntree = dict(tree, **{child: to_expand[0] for child in children})
+            nfree = free.difference(children)
+            queue.append((ntree, nfree, to_expand[1:] + tuple(children)))
index 0572e784ee7389281320b056fd1da0c262991861..ff333a1d86f75e62a394e7fb1dac3c6c18b457f1 100644 (file)
@@ -11,4 +11,4 @@ test_databases := $(dir)/database-v1.tar.xz
 
 download-test-databases: ${test_databases}
 
-DISTCLEAN := $(DISTCLEAN) ${test_databases}
+DATACLEAN := $(DATACLEAN) ${test_databases}
index 892991e2bd037c9e8745bdf3cf539585cf111d21..4903038dd9ce531ea7dda467a0d0eac6f86d8bd4 100644 (file)
@@ -38,6 +38,10 @@ find_notmuch_path ()
 # test/ subdirectory and are run in 'trash directory' subdirectory.
 TEST_DIRECTORY=$(pwd)
 notmuch_path=`find_notmuch_path "$TEST_DIRECTORY"`
+
+# configure output
+. $notmuch_path/sh.config
+
 if test -n "$valgrind"
 then
        make_symlink () {
index 437f83f44d08e8224465181a08a0f17e748a0413..36afe630e5edb35d0a007bcb3be6a2131d9e8d20 100644 (file)
 
 (defun test-output (&optional filename)
   "Save current buffer to file FILENAME.  Default FILENAME is OUTPUT."
+  (notmuch-post-command)
   (write-region (point-min) (point-max) (or filename "OUTPUT")))
 
 (defun test-visible-output (&optional filename)
   "Save visible text in current buffer to file FILENAME.  Default
 FILENAME is OUTPUT."
+  (notmuch-post-command)
   (let ((text (visible-buffer-string)))
     (with-temp-file (or filename "OUTPUT") (insert text))))
 
@@ -166,6 +168,15 @@ nothing."
      (t
       (notmuch-test-report-unexpected output expected)))))
 
+(defun notmuch-post-command ()
+  (run-hooks 'post-command-hook))
+
+(defmacro notmuch-test-progn (&rest body)
+  (cons 'progn
+       (mapcar
+        (lambda (x) `(prog1 ,x (notmuch-post-command)))
+        body)))
+
 ;; For historical reasons, we hide deleted tags by default in the test
 ;; suite
 (setq notmuch-tag-deleted-formats
index 17deaaba0d5d880df237694430743c4304e7554d..b9b8fe8ca6ad71a3f62a5d265ad1b97bef9e8b0a 100644 (file)
@@ -1138,7 +1138,7 @@ test_emacs () {
        rm -f OUTPUT
        touch OUTPUT
 
-       ${TEST_EMACSCLIENT} --socket-name="$EMACS_SERVER" --eval "(progn $@)"
+       ${TEST_EMACSCLIENT} --socket-name="$EMACS_SERVER" --eval "(notmuch-test-progn $@)"
 }
 
 test_python() {
index 3e7066cd58ea191a9e647f5a0177f5547f662388..a90501ee3e70198e652599e6da841ffe95db8f85 100644 (file)
@@ -37,6 +37,14 @@ strtok_len (char *s, const char *delim, size_t *len)
     return *len ? s : NULL;
 }
 
+const char *
+strtok_len_c (const char *s, const char *delim, size_t *len)
+{
+    /* strtok_len is already const-safe, but we can't express both
+     * versions in the C type system. */
+    return strtok_len ((char*)s, delim, len);
+}
+
 char *
 sanitize_string (const void *ctx, const char *str)
 {
index 8a3ad19eae6e9808a319aec043e78089b792d41e..e409cb3d2ab154664a24873cfe50e3ac34a23513 100644 (file)
@@ -3,6 +3,10 @@
 
 #include <string.h>
 
+#ifdef __cplusplus
+extern "C" {
+#endif
+
 /* like strtok(3), but without state, and doesn't modify s.  Return
  * value is indicated by pointer and length, not null terminator.
  *
@@ -19,6 +23,9 @@
 
 char *strtok_len (char *s, const char *delim, size_t *len);
 
+/* Const version of strtok_len. */
+const char *strtok_len_c (const char *s, const char *delim, size_t *len);
+
 /* Return a talloced string with str sanitized.
  *
  * Whitespace characters (tabs and newlines) are replaced with spaces,
@@ -57,4 +64,8 @@ int
 parse_boolean_term (void *ctx, const char *str,
                    char **prefix_out, char **term_out);
 
+#ifdef __cplusplus
+}
+#endif
+
 #endif